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 /** 33 * osif_validate_disconnect_and_reset_src_id() - Validate disconnection 34 * and resets source and id 35 * @osif_priv: Pointer to vdev osif priv 36 * @rsp: Disconnect response from connectin manager 37 * 38 * This function validates disconnect response and if the disconnect 39 * response is valid, resets the source and id of the command 40 * 41 * Context: Any context. Takes and releases cmd id spinlock. 42 * Return: QDF_STATUS 43 */ 44 45 static QDF_STATUS 46 osif_validate_disconnect_and_reset_src_id(struct vdev_osif_priv *osif_priv, 47 struct wlan_cm_discon_rsp *rsp) 48 { 49 QDF_STATUS status = QDF_STATUS_SUCCESS; 50 51 /* Always drop internal disconnect */ 52 qdf_spinlock_acquire(&osif_priv->cm_info.cmd_id_lock); 53 if (rsp->req.req.source == CM_INTERNAL_DISCONNECT || 54 rsp->req.req.source == CM_MLO_ROAM_INTERNAL_DISCONNECT) { 55 osif_debug("ignore internal disconnect"); 56 status = QDF_STATUS_E_INVAL; 57 goto rel_lock; 58 } 59 60 /* 61 * Send to kernel only if last osif cmd type is disconnect and 62 * cookie match else drop. If cookie match reset the cookie 63 * and source 64 */ 65 if (rsp->req.cm_id != osif_priv->cm_info.last_id || 66 rsp->req.req.source != osif_priv->cm_info.last_source) { 67 osif_debug("Ignore as cm_id(0x%x)/src(%d) didn't match stored cm_id(0x%x)/src(%d)", 68 rsp->req.cm_id, rsp->req.req.source, 69 osif_priv->cm_info.last_id, 70 osif_priv->cm_info.last_source); 71 status = QDF_STATUS_E_INVAL; 72 goto rel_lock; 73 } 74 75 osif_cm_reset_id_and_src_no_lock(osif_priv); 76 rel_lock: 77 qdf_spinlock_release(&osif_priv->cm_info.cmd_id_lock); 78 79 return status; 80 } 81 82 #if defined(CFG80211_DISCONNECTED_V2) || \ 83 (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 2, 0)) 84 #ifdef CONN_MGR_ADV_FEATURE 85 static void 86 osif_cm_indicate_disconnect_result(struct net_device *dev, 87 enum ieee80211_reasoncode reason, 88 const u8 *ie, size_t ie_len, 89 bool locally_generated, int link_id, 90 gfp_t gfp) 91 { 92 cfg80211_disconnected(dev, reason, ie, 93 ie_len, locally_generated, gfp); 94 } 95 #else 96 #ifdef WLAN_SUPPORT_CFG80211_DISCONNECT_LINK_PARAM 97 static void 98 osif_cm_indicate_disconnect_result(struct net_device *dev, 99 enum ieee80211_reasoncode reason, 100 const u8 *ie, size_t ie_len, 101 bool locally_generated, int link_id, 102 gfp_t gfp) 103 { 104 cfg80211_disconnected(dev, reason, ie, 105 ie_len, locally_generated, link_id, gfp); 106 } 107 #else 108 static void 109 osif_cm_indicate_disconnect_result(struct net_device *dev, 110 enum ieee80211_reasoncode reason, 111 const u8 *ie, size_t ie_len, 112 bool locally_generated, int link_id, 113 gfp_t gfp) 114 { 115 cfg80211_disconnected(dev, reason, ie, 116 ie_len, locally_generated, gfp); 117 } 118 #endif /* WLAN_SUPPORT_CFG80211_DISCONNECT_LINK_PARAM */ 119 #endif 120 121 #ifdef WLAN_FEATURE_11BE_MLO 122 #ifdef WLAN_FEATURE_11BE_MLO_ADV_FEATURE 123 void 124 osif_cm_indicate_disconnect(struct wlan_objmgr_vdev *vdev, 125 struct net_device *dev, 126 enum ieee80211_reasoncode reason, 127 bool locally_generated, const u8 *ie, 128 size_t ie_len, int link_id, gfp_t gfp) 129 { 130 if (wlan_vdev_mlme_is_mlo_vdev(vdev)) { 131 if (!wlan_vdev_mlme_is_mlo_link_vdev(vdev)) 132 osif_cm_indicate_disconnect_result( 133 dev, reason, ie, 134 ie_len, locally_generated, 135 link_id, gfp); 136 } else { 137 osif_cm_indicate_disconnect_result( 138 dev, reason, ie, 139 ie_len, locally_generated, 140 link_id, gfp); 141 } 142 } 143 #else /* WLAN_FEATURE_11BE_MLO_ADV_FEATURE */ 144 void 145 osif_cm_indicate_disconnect(struct wlan_objmgr_vdev *vdev, 146 struct net_device *dev, 147 enum ieee80211_reasoncode reason, 148 bool locally_generated, const u8 *ie, 149 size_t ie_len, int link_id, gfp_t gfp) 150 { 151 struct net_device *netdev = dev; 152 struct vdev_osif_priv *osif_priv = NULL; 153 struct wlan_objmgr_vdev *assoc_vdev = NULL; 154 155 if (!wlan_vdev_mlme_is_mlo_vdev(vdev) || (link_id != -1)) { 156 osif_cm_indicate_disconnect_result( 157 netdev, reason, ie, ie_len, 158 locally_generated, link_id, gfp); 159 return; 160 } 161 162 if (ucfg_mlo_is_mld_disconnected(vdev)) { 163 assoc_vdev = ucfg_mlo_get_assoc_link_vdev(vdev); 164 if (!assoc_vdev) 165 return; 166 osif_priv = wlan_vdev_get_ospriv(assoc_vdev); 167 netdev = osif_priv->wdev->netdev; 168 osif_cm_indicate_disconnect_result( 169 netdev, reason, 170 ie, ie_len, 171 locally_generated, link_id, gfp); 172 } 173 } 174 #endif /* WLAN_FEATURE_11BE_MLO_ADV_FEATURE */ 175 #else /* WLAN_FEATURE_11BE_MLO */ 176 void 177 osif_cm_indicate_disconnect(struct wlan_objmgr_vdev *vdev, 178 struct net_device *dev, 179 enum ieee80211_reasoncode reason, 180 bool locally_generated, const u8 *ie, 181 size_t ie_len, int link_id, gfp_t gfp) 182 { 183 osif_cm_indicate_disconnect_result(dev, reason, ie, 184 ie_len, locally_generated, 185 link_id, gfp); 186 } 187 #endif /* WLAN_FEATURE_11BE_MLO */ 188 #else 189 void 190 osif_cm_indicate_disconnect(struct wlan_objmgr_vdev *vdev, 191 struct net_device *dev, 192 enum ieee80211_reasoncode reason, 193 bool locally_generated, const u8 *ie, 194 size_t ie_len, int link_id, gfp_t gfp) 195 { 196 cfg80211_disconnected(dev, reason, ie, ie_len, gfp); 197 } 198 #endif 199 200 static enum ieee80211_reasoncode 201 osif_cm_get_disconnect_reason(struct vdev_osif_priv *osif_priv, uint16_t reason) 202 { 203 enum ieee80211_reasoncode ieee80211_reason = WLAN_REASON_UNSPECIFIED; 204 205 if (reason < REASON_PROP_START) 206 ieee80211_reason = reason; 207 /* 208 * Applications expect reason code as 0 for beacon miss failure 209 * due to backward compatibility. So send ieee80211_reason as 0. 210 */ 211 if (reason == REASON_BEACON_MISSED) 212 ieee80211_reason = 0; 213 214 return ieee80211_reason; 215 } 216 217 #ifdef CONN_MGR_ADV_FEATURE 218 static inline bool 219 osif_is_disconnect_locally_generated(struct wlan_cm_discon_rsp *rsp) 220 { 221 if (rsp->req.req.source == CM_PEER_DISCONNECT) 222 return false; 223 224 return true; 225 } 226 #else 227 static inline bool 228 osif_is_disconnect_locally_generated(struct wlan_cm_discon_rsp *rsp) 229 { 230 if (rsp->req.req.source == CM_PEER_DISCONNECT || 231 rsp->req.req.source == CM_SB_DISCONNECT) 232 return false; 233 234 return true; 235 } 236 #endif 237 238 QDF_STATUS osif_disconnect_handler(struct wlan_objmgr_vdev *vdev, 239 struct wlan_cm_discon_rsp *rsp) 240 { 241 enum ieee80211_reasoncode ieee80211_reason; 242 struct vdev_osif_priv *osif_priv = wlan_vdev_get_ospriv(vdev); 243 bool locally_generated = true; 244 QDF_STATUS status = QDF_STATUS_SUCCESS; 245 enum qca_disconnect_reason_codes qca_reason; 246 int link_id = -1; 247 248 qca_reason = osif_cm_mac_to_qca_reason(rsp->req.req.reason_code); 249 ieee80211_reason = 250 osif_cm_get_disconnect_reason(osif_priv, 251 rsp->req.req.reason_code); 252 253 locally_generated = osif_is_disconnect_locally_generated(rsp); 254 255 osif_nofl_info("%s(vdevid-%d): " QDF_MAC_ADDR_FMT " %sdisconnect " QDF_MAC_ADDR_FMT " cm_id 0x%x source %d reason:%u %s vendor:%u %s", 256 osif_priv->wdev->netdev->name, 257 rsp->req.req.vdev_id, 258 QDF_MAC_ADDR_REF(wlan_vdev_mlme_get_macaddr(vdev)), 259 locally_generated ? "locally-generated " : "", 260 QDF_MAC_ADDR_REF(rsp->req.req.bssid.bytes), 261 rsp->req.cm_id, rsp->req.req.source, ieee80211_reason, 262 ucfg_cm_reason_code_to_str(rsp->req.req.reason_code), 263 qca_reason, 264 osif_cm_qca_reason_to_str(qca_reason)); 265 266 /* Unlink bss if disconnect is from peer or south bound */ 267 if (rsp->req.req.source == CM_PEER_DISCONNECT || 268 rsp->req.req.source == CM_SB_DISCONNECT) 269 osif_cm_unlink_bss(vdev, osif_priv, &rsp->req.req.bssid, 270 NULL, 0); 271 272 status = osif_validate_disconnect_and_reset_src_id(osif_priv, rsp); 273 if (QDF_IS_STATUS_ERROR(status)) { 274 osif_cm_disconnect_comp_ind(vdev, rsp, OSIF_NOT_HANDLED); 275 return status; 276 } 277 278 /* If disconnect due to ML Reconfig, fill link id */ 279 if (rsp->req.req.reason_code == REASON_HOST_TRIGGERED_LINK_DELETE) 280 link_id = wlan_vdev_get_link_id(vdev); 281 282 osif_cm_disconnect_comp_ind(vdev, rsp, OSIF_PRE_USERSPACE_UPDATE); 283 osif_cm_indicate_disconnect(vdev, osif_priv->wdev->netdev, 284 ieee80211_reason, 285 locally_generated, rsp->ap_discon_ie.ptr, 286 rsp->ap_discon_ie.len, 287 link_id, 288 qdf_mem_malloc_flags()); 289 290 osif_cm_disconnect_comp_ind(vdev, rsp, OSIF_POST_USERSPACE_UPDATE); 291 292 return status; 293 } 294