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