xref: /wlan-driver/qca-wifi-host-cmn/os_if/linux/mlme/src/osif_cm_disconnect_rsp.c (revision 5113495b16420b49004c444715d2daae2066e7dc)
1 /*
2  * Copyright (c) 2012-2015, 2020-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 any
6  * purpose with or without fee is hereby granted, provided that the above
7  * copyright notice and this permission notice appear in all copies.
8  *
9  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16  */
17 
18 /**
19  * DOC: osif_cm_disconnect_rsp.c
20  *
21  * This file maintains definitaions of disconnect response
22  * functions.
23  */
24 
25 #include <wlan_cfg80211.h>
26 #include <linux/wireless.h>
27 #include "osif_cm_rsp.h"
28 #include "wlan_osif_priv.h"
29 #include "osif_cm_util.h"
30 #include "wlan_mlo_mgr_sta.h"
31 
32 #define DRIVER_DISCONNECT_REASON \
33 	QCA_WLAN_VENDOR_ATTR_GET_STATION_INFO_DRIVER_DISCONNECT_REASON
34 #define DRIVER_DISCONNECT_REASON_INDEX \
35 	QCA_NL80211_VENDOR_SUBCMD_DRIVER_DISCONNECT_REASON_INDEX
36 /**
37  * osif_validate_disconnect_and_reset_src_id() - Validate disconnection
38  * and resets source and id
39  * @osif_priv: Pointer to vdev osif priv
40  * @rsp: Disconnect response from connectin manager
41  *
42  * This function validates disconnect response and if the disconnect
43  * response is valid, resets the source and id of the command
44  *
45  * Context: Any context. Takes and releases cmd id spinlock.
46  * Return: QDF_STATUS
47  */
48 
49 static QDF_STATUS
osif_validate_disconnect_and_reset_src_id(struct vdev_osif_priv * osif_priv,struct wlan_cm_discon_rsp * rsp)50 osif_validate_disconnect_and_reset_src_id(struct vdev_osif_priv *osif_priv,
51 					  struct wlan_cm_discon_rsp *rsp)
52 {
53 	QDF_STATUS status = QDF_STATUS_SUCCESS;
54 
55 	/* Always drop internal disconnect */
56 	qdf_spinlock_acquire(&osif_priv->cm_info.cmd_id_lock);
57 	if (rsp->req.req.source == CM_INTERNAL_DISCONNECT ||
58 	    rsp->req.req.source == CM_MLO_ROAM_INTERNAL_DISCONNECT ||
59 	    ucfg_cm_is_link_switch_disconnect_resp(rsp)) {
60 		osif_debug("ignore internal disconnect");
61 		status = QDF_STATUS_E_INVAL;
62 		goto rel_lock;
63 	}
64 
65 	/*
66 	 * Send to kernel only if last osif cmd type is disconnect and
67 	 * cookie match else drop. If cookie match reset the cookie
68 	 * and source
69 	 */
70 	if (rsp->req.cm_id != osif_priv->cm_info.last_id ||
71 	    rsp->req.req.source != osif_priv->cm_info.last_source) {
72 		osif_debug("Ignore as cm_id(0x%x)/src(%d) didn't match stored cm_id(0x%x)/src(%d)",
73 			   rsp->req.cm_id, rsp->req.req.source,
74 			   osif_priv->cm_info.last_id,
75 			   osif_priv->cm_info.last_source);
76 		status = QDF_STATUS_E_INVAL;
77 		goto rel_lock;
78 	}
79 
80 	osif_cm_reset_id_and_src_no_lock(osif_priv);
81 rel_lock:
82 	qdf_spinlock_release(&osif_priv->cm_info.cmd_id_lock);
83 
84 	return status;
85 }
86 
87 #if defined(CFG80211_DISCONNECTED_V2) || \
88 (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 2, 0))
89 #ifdef CONN_MGR_ADV_FEATURE
90 static void
osif_cm_indicate_disconnect_result(struct net_device * dev,enum ieee80211_reasoncode reason,const u8 * ie,size_t ie_len,bool locally_generated,int link_id,gfp_t gfp)91 osif_cm_indicate_disconnect_result(struct net_device *dev,
92 				   enum ieee80211_reasoncode reason,
93 				   const u8 *ie, size_t ie_len,
94 				   bool locally_generated, int link_id,
95 				   gfp_t gfp)
96 {
97 	cfg80211_disconnected(dev, reason, ie,
98 			      ie_len, locally_generated, gfp);
99 }
100 #else
101 #ifdef WLAN_SUPPORT_CFG80211_DISCONNECT_LINK_PARAM
102 static void
osif_cm_indicate_disconnect_result(struct net_device * dev,enum ieee80211_reasoncode reason,const u8 * ie,size_t ie_len,bool locally_generated,int link_id,gfp_t gfp)103 osif_cm_indicate_disconnect_result(struct net_device *dev,
104 				   enum ieee80211_reasoncode reason,
105 				   const u8 *ie, size_t ie_len,
106 				   bool locally_generated, int link_id,
107 				   gfp_t gfp)
108 {
109 	cfg80211_disconnected(dev, reason, ie,
110 			      ie_len, locally_generated, link_id, gfp);
111 }
112 #else
113 static void
osif_cm_indicate_disconnect_result(struct net_device * dev,enum ieee80211_reasoncode reason,const u8 * ie,size_t ie_len,bool locally_generated,int link_id,gfp_t gfp)114 osif_cm_indicate_disconnect_result(struct net_device *dev,
115 				   enum ieee80211_reasoncode reason,
116 				   const u8 *ie, size_t ie_len,
117 				   bool locally_generated, int link_id,
118 				   gfp_t gfp)
119 {
120 	cfg80211_disconnected(dev, reason, ie,
121 			      ie_len, locally_generated, gfp);
122 }
123 #endif /* WLAN_SUPPORT_CFG80211_DISCONNECT_LINK_PARAM */
124 #endif
125 
126 #ifdef WLAN_FEATURE_11BE_MLO
127 #ifdef WLAN_FEATURE_11BE_MLO_ADV_FEATURE
128 void
osif_cm_indicate_disconnect(struct wlan_objmgr_vdev * vdev,struct net_device * dev,enum ieee80211_reasoncode reason,bool locally_generated,const u8 * ie,size_t ie_len,int link_id,gfp_t gfp)129 osif_cm_indicate_disconnect(struct wlan_objmgr_vdev *vdev,
130 			    struct net_device *dev,
131 			    enum ieee80211_reasoncode reason,
132 			    bool locally_generated, const u8 *ie,
133 			    size_t ie_len, int link_id, gfp_t gfp)
134 {
135 	if (wlan_vdev_mlme_is_mlo_vdev(vdev)) {
136 		if (!wlan_vdev_mlme_is_mlo_link_vdev(vdev))
137 			osif_cm_indicate_disconnect_result(
138 					dev, reason, ie,
139 					ie_len, locally_generated,
140 					link_id, gfp);
141 	} else {
142 		osif_cm_indicate_disconnect_result(
143 				dev, reason, ie,
144 				ie_len, locally_generated,
145 				link_id, gfp);
146 	}
147 }
148 #else /* WLAN_FEATURE_11BE_MLO_ADV_FEATURE */
149 
150 /**
151  * osif_cm_get_anchor_vdev() - API to get the anchor vdev
152  * @vdev: Pointer to vdev
153  *
154  * Return: If the assoc vdev is available, return it. Otherwise, if the MLD is
155  * disconnected, return the current vdev. If neither is available, return NULL.
156  */
osif_cm_get_anchor_vdev(struct wlan_objmgr_vdev * vdev)157 static struct wlan_objmgr_vdev *osif_cm_get_anchor_vdev(
158 		struct wlan_objmgr_vdev *vdev)
159 {
160 	struct wlan_objmgr_vdev *assoc_vdev = NULL;
161 
162 	assoc_vdev = ucfg_mlo_get_assoc_link_vdev(vdev);
163 	if (assoc_vdev)
164 		return assoc_vdev;
165 	else if (ucfg_mlo_is_mld_disconnected(vdev))
166 		return vdev;
167 	else
168 		return NULL;
169 }
170 
171 #if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 4, 213)) && \
172 	(LINUX_VERSION_CODE < KERNEL_VERSION(6, 0, 0))
173 /**
174  * osif_cm_indicate_disconnect_for_non_assoc_link() - Wrapper API to clear
175  * current bss param of non-assoc link
176  * @netdev: Pointer to netdev of non-assoc link vdev
177  * @vdev: Pointer to non-assoc link vdev
178  *
179  * Return: None
180  */
osif_cm_indicate_disconnect_for_non_assoc_link(struct net_device * netdev,struct wlan_objmgr_vdev * vdev)181 static void osif_cm_indicate_disconnect_for_non_assoc_link(
182 		struct net_device *netdev,
183 		struct wlan_objmgr_vdev *vdev)
184 {
185 	int ret;
186 
187 	ret = cfg80211_clear_current_bss(netdev);
188 	if (ret)
189 		osif_err("cfg80211_clear_current_bss failed for psoc:%d pdev:%d vdev:%d",
190 			 wlan_vdev_get_psoc_id(vdev),
191 			 wlan_objmgr_pdev_get_pdev_id(wlan_vdev_get_pdev(vdev)),
192 			 wlan_vdev_get_id(vdev));
193 }
194 #else
osif_cm_indicate_disconnect_for_non_assoc_link(struct net_device * netdev,struct wlan_objmgr_vdev * vdev)195 static void osif_cm_indicate_disconnect_for_non_assoc_link(
196 		struct net_device *netdev,
197 		struct wlan_objmgr_vdev *vdev)
198 {
199 }
200 #endif
201 
202 void
osif_cm_indicate_disconnect(struct wlan_objmgr_vdev * vdev,struct net_device * dev,enum ieee80211_reasoncode reason,bool locally_generated,const u8 * ie,size_t ie_len,int link_id,gfp_t gfp)203 osif_cm_indicate_disconnect(struct wlan_objmgr_vdev *vdev,
204 			    struct net_device *dev,
205 			    enum ieee80211_reasoncode reason,
206 			    bool locally_generated, const u8 *ie,
207 			    size_t ie_len, int link_id, gfp_t gfp)
208 {
209 	struct net_device *netdev = dev;
210 	struct vdev_osif_priv *osif_priv = NULL;
211 	struct wlan_objmgr_vdev *anchor_vdev;
212 
213 	if (!wlan_vdev_mlme_is_mlo_vdev(vdev) || (link_id != -1)) {
214 		osif_cm_indicate_disconnect_result(
215 				netdev, reason, ie, ie_len,
216 				locally_generated, link_id, gfp);
217 		return;
218 	}
219 
220 	anchor_vdev = osif_cm_get_anchor_vdev(vdev);
221 
222 	if (vdev != anchor_vdev)
223 		osif_cm_indicate_disconnect_for_non_assoc_link(netdev, vdev);
224 
225 	if (anchor_vdev && ucfg_mlo_is_mld_disconnected(vdev)) {
226 		/**
227 		 * Kernel maintains some extra state on the assoc netdev.
228 		 * If the assoc vdev exists, send disconnected event on the
229 		 * assoc netdev so that kernel cleans up the extra state.
230 		 * If the assoc vdev was already removed, kernel would have
231 		 * already cleaned up the extra state while processing the
232 		 * disconnected event sent as part of the link removal.
233 		 */
234 		osif_priv = wlan_vdev_get_ospriv(anchor_vdev);
235 		netdev = osif_priv->wdev->netdev;
236 
237 		osif_cm_indicate_disconnect_result(
238 				netdev, reason,
239 				ie, ie_len,
240 				locally_generated, link_id, gfp);
241 	}
242 }
243 #endif /* WLAN_FEATURE_11BE_MLO_ADV_FEATURE */
244 #else /* WLAN_FEATURE_11BE_MLO */
245 void
osif_cm_indicate_disconnect(struct wlan_objmgr_vdev * vdev,struct net_device * dev,enum ieee80211_reasoncode reason,bool locally_generated,const u8 * ie,size_t ie_len,int link_id,gfp_t gfp)246 osif_cm_indicate_disconnect(struct wlan_objmgr_vdev *vdev,
247 			    struct net_device *dev,
248 			    enum ieee80211_reasoncode reason,
249 			    bool locally_generated, const u8 *ie,
250 			    size_t ie_len, int link_id, gfp_t gfp)
251 {
252 	osif_cm_indicate_disconnect_result(dev, reason, ie,
253 					   ie_len, locally_generated,
254 					   link_id, gfp);
255 }
256 #endif /* WLAN_FEATURE_11BE_MLO */
257 #else
258 void
osif_cm_indicate_disconnect(struct wlan_objmgr_vdev * vdev,struct net_device * dev,enum ieee80211_reasoncode reason,bool locally_generated,const u8 * ie,size_t ie_len,int link_id,gfp_t gfp)259 osif_cm_indicate_disconnect(struct wlan_objmgr_vdev *vdev,
260 			    struct net_device *dev,
261 			    enum ieee80211_reasoncode reason,
262 			    bool locally_generated, const u8 *ie,
263 			    size_t ie_len, int link_id, gfp_t gfp)
264 {
265 	cfg80211_disconnected(dev, reason, ie, ie_len, gfp);
266 }
267 #endif
268 
269 static enum ieee80211_reasoncode
osif_cm_get_disconnect_reason(struct vdev_osif_priv * osif_priv,uint16_t reason)270 osif_cm_get_disconnect_reason(struct vdev_osif_priv *osif_priv, uint16_t reason)
271 {
272 	enum ieee80211_reasoncode ieee80211_reason = WLAN_REASON_UNSPECIFIED;
273 
274 	if (reason < REASON_PROP_START)
275 		ieee80211_reason = reason;
276 	/*
277 	 * Applications expect reason code as 0 for beacon miss failure
278 	 * due to backward compatibility. So send ieee80211_reason as 0.
279 	 */
280 	if (reason == REASON_BEACON_MISSED)
281 		ieee80211_reason = 0;
282 
283 	return ieee80211_reason;
284 }
285 
286 #ifdef CONN_MGR_ADV_FEATURE
287 static inline bool
osif_is_disconnect_locally_generated(struct wlan_cm_discon_rsp * rsp)288 osif_is_disconnect_locally_generated(struct wlan_cm_discon_rsp *rsp)
289 {
290 	if (rsp->req.req.source == CM_PEER_DISCONNECT)
291 		return false;
292 
293 	return true;
294 }
295 #else
296 static inline bool
osif_is_disconnect_locally_generated(struct wlan_cm_discon_rsp * rsp)297 osif_is_disconnect_locally_generated(struct wlan_cm_discon_rsp *rsp)
298 {
299 	if (rsp->req.req.source == CM_PEER_DISCONNECT ||
300 	    rsp->req.req.source == CM_SB_DISCONNECT)
301 		return false;
302 
303 	return true;
304 }
305 #endif
306 
307 #ifdef CONN_MGR_ADV_FEATURE
308 /**
309  * osif_cm_indicate_qca_reason: Send driver disconnect reason to user space
310  * @osif_priv: osif_priv pointer
311  * @qca_reason: qca disconnect reason codes
312  *
313  * Return: void
314  */
315 
316 static void
osif_cm_indicate_qca_reason(struct vdev_osif_priv * osif_priv,enum qca_disconnect_reason_codes qca_reason)317 osif_cm_indicate_qca_reason(struct vdev_osif_priv *osif_priv,
318 			    enum qca_disconnect_reason_codes qca_reason)
319 {
320 	struct sk_buff *vendor_event;
321 
322 	vendor_event = wlan_cfg80211_vendor_event_alloc(
323 					osif_priv->wdev->wiphy, osif_priv->wdev,
324 					NLMSG_HDRLEN + sizeof(qca_reason) +
325 					NLMSG_HDRLEN,
326 					DRIVER_DISCONNECT_REASON_INDEX,
327 					GFP_KERNEL);
328 	if (!vendor_event) {
329 		osif_err("cfg80211_vendor_event_alloc failed");
330 		return;
331 	}
332 	if (nla_put_u32(vendor_event, DRIVER_DISCONNECT_REASON, qca_reason)) {
333 		osif_err("DISCONNECT_REASON put fail");
334 		kfree_skb(vendor_event);
335 		return;
336 	}
337 
338 	wlan_cfg80211_vendor_event(vendor_event, GFP_KERNEL);
339 }
340 #else
341 static inline void
osif_cm_indicate_qca_reason(struct vdev_osif_priv * osif_priv,enum qca_disconnect_reason_codes qca_reason)342 osif_cm_indicate_qca_reason(struct vdev_osif_priv *osif_priv,
343 			    enum qca_disconnect_reason_codes qca_reason)
344 {
345 }
346 #endif
347 
osif_disconnect_handler(struct wlan_objmgr_vdev * vdev,struct wlan_cm_discon_rsp * rsp)348 QDF_STATUS osif_disconnect_handler(struct wlan_objmgr_vdev *vdev,
349 				   struct wlan_cm_discon_rsp *rsp)
350 {
351 	enum ieee80211_reasoncode ieee80211_reason;
352 	struct vdev_osif_priv *osif_priv = wlan_vdev_get_ospriv(vdev);
353 	bool locally_generated;
354 	QDF_STATUS status = QDF_STATUS_SUCCESS;
355 	enum qca_disconnect_reason_codes qca_reason;
356 	int link_id = -1;
357 
358 	qca_reason = osif_cm_mac_to_qca_reason(rsp->req.req.reason_code);
359 	ieee80211_reason =
360 		osif_cm_get_disconnect_reason(osif_priv,
361 					      rsp->req.req.reason_code);
362 
363 	locally_generated = osif_is_disconnect_locally_generated(rsp);
364 
365 	osif_nofl_info("%s(vdevid-%d): " QDF_MAC_ADDR_FMT " %s disconnect " QDF_MAC_ADDR_FMT " cmid 0x%x src %d reason:%u %s vendor:%u %s",
366 		       osif_priv->wdev->netdev->name,
367 		       rsp->req.req.vdev_id,
368 		       QDF_MAC_ADDR_REF(wlan_vdev_mlme_get_macaddr(vdev)),
369 		       locally_generated ? "locally-generated" : "",
370 		       QDF_MAC_ADDR_REF(rsp->req.req.bssid.bytes),
371 		       rsp->req.cm_id, rsp->req.req.source, ieee80211_reason,
372 		       ucfg_cm_reason_code_to_str(rsp->req.req.reason_code),
373 		       qca_reason,
374 		       osif_cm_qca_reason_to_str(qca_reason));
375 
376 	/* Unlink bss if disconnect is from peer or south bound */
377 	if (rsp->req.req.source == CM_PEER_DISCONNECT ||
378 	    rsp->req.req.source == CM_SB_DISCONNECT)
379 		osif_cm_unlink_bss(vdev, &rsp->req.req.bssid);
380 
381 	status = osif_validate_disconnect_and_reset_src_id(osif_priv, rsp);
382 	if (QDF_IS_STATUS_ERROR(status)) {
383 		osif_cm_disconnect_comp_ind(vdev, rsp, OSIF_NOT_HANDLED);
384 		return status;
385 	}
386 
387 	/* Send driver disconnect Reason */
388 	osif_cm_indicate_qca_reason(osif_priv, qca_reason);
389 
390 	/* If disconnect due to ML Reconfig, fill link id */
391 	if (rsp->req.req.reason_code == REASON_HOST_TRIGGERED_LINK_DELETE)
392 		link_id = wlan_vdev_get_link_id(vdev);
393 
394 	osif_cm_disconnect_comp_ind(vdev, rsp, OSIF_PRE_USERSPACE_UPDATE);
395 	osif_cm_indicate_disconnect(vdev, osif_priv->wdev->netdev,
396 				    ieee80211_reason,
397 				    locally_generated, rsp->ap_discon_ie.ptr,
398 				    rsp->ap_discon_ie.len,
399 				    link_id,
400 				    qdf_mem_malloc_flags());
401 
402 	osif_cm_disconnect_comp_ind(vdev, rsp, OSIF_POST_USERSPACE_UPDATE);
403 
404 	return status;
405 }
406