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