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