1 /* 2 * Copyright (c) 2012-2015, 2020-2021 The Linux Foundation. All rights reserved. 3 * Copyright (c) 2022 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 osif_debug("ignore internal disconnect"); 55 status = QDF_STATUS_E_INVAL; 56 goto rel_lock; 57 } 58 59 /* 60 * Send to kernel only if last osif cmd type is disconnect and 61 * cookie match else drop. If cookie match reset the cookie 62 * and source 63 */ 64 if (rsp->req.cm_id != osif_priv->cm_info.last_id || 65 rsp->req.req.source != osif_priv->cm_info.last_source) { 66 osif_debug("Ignore as cm_id(0x%x)/src(%d) didn't match stored cm_id(0x%x)/src(%d)", 67 rsp->req.cm_id, rsp->req.req.source, 68 osif_priv->cm_info.last_id, 69 osif_priv->cm_info.last_source); 70 status = QDF_STATUS_E_INVAL; 71 goto rel_lock; 72 } 73 74 osif_cm_reset_id_and_src_no_lock(osif_priv); 75 rel_lock: 76 qdf_spinlock_release(&osif_priv->cm_info.cmd_id_lock); 77 78 return status; 79 } 80 81 #if defined(CFG80211_DISCONNECTED_V2) || \ 82 (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 2, 0)) 83 #ifdef WLAN_FEATURE_11BE_MLO 84 #ifdef WLAN_FEATURE_11BE_MLO_ADV_FEATURE 85 void 86 osif_cm_indicate_disconnect(struct wlan_objmgr_vdev *vdev, 87 struct net_device *dev, 88 enum ieee80211_reasoncode reason, 89 bool locally_generated, const u8 *ie, 90 size_t ie_len, gfp_t gfp) 91 { 92 if (wlan_vdev_mlme_is_mlo_vdev(vdev)) { 93 if (!wlan_vdev_mlme_is_mlo_link_vdev(vdev)) 94 cfg80211_disconnected(dev, reason, ie, 95 ie_len, locally_generated, gfp); 96 } else { 97 cfg80211_disconnected(dev, reason, ie, 98 ie_len, locally_generated, gfp); 99 } 100 } 101 #else /* WLAN_FEATURE_11BE_MLO_ADV_FEATURE */ 102 void 103 osif_cm_indicate_disconnect(struct wlan_objmgr_vdev *vdev, 104 struct net_device *dev, 105 enum ieee80211_reasoncode reason, 106 bool locally_generated, const u8 *ie, 107 size_t ie_len, gfp_t gfp) 108 { 109 struct net_device *netdev = dev; 110 struct vdev_osif_priv *osif_priv = NULL; 111 struct wlan_objmgr_vdev *assoc_vdev = NULL; 112 113 if (!wlan_vdev_mlme_is_mlo_vdev(vdev)) { 114 cfg80211_disconnected(netdev, reason, ie, ie_len, 115 locally_generated, gfp); 116 return; 117 } 118 119 if (ucfg_mlo_is_mld_disconnected(vdev)) { 120 assoc_vdev = ucfg_mlo_get_assoc_link_vdev(vdev); 121 if (!assoc_vdev) 122 return; 123 osif_priv = wlan_vdev_get_ospriv(assoc_vdev); 124 netdev = osif_priv->wdev->netdev; 125 cfg80211_disconnected(netdev, reason, 126 ie, ie_len, 127 locally_generated, gfp); 128 } 129 } 130 #endif /* WLAN_FEATURE_11BE_MLO_ADV_FEATURE */ 131 #else /* WLAN_FEATURE_11BE_MLO */ 132 void 133 osif_cm_indicate_disconnect(struct wlan_objmgr_vdev *vdev, 134 struct net_device *dev, 135 enum ieee80211_reasoncode reason, 136 bool locally_generated, const u8 *ie, 137 size_t ie_len, gfp_t gfp) 138 { 139 cfg80211_disconnected(dev, reason, ie, ie_len, locally_generated, gfp); 140 } 141 #endif /* WLAN_FEATURE_11BE_MLO */ 142 #else 143 void 144 osif_cm_indicate_disconnect(struct wlan_objmgr_vdev *vdev, 145 struct net_device *dev, 146 enum ieee80211_reasoncode reason, 147 bool locally_generated, const u8 *ie, 148 size_t ie_len, gfp_t gfp) 149 { 150 cfg80211_disconnected(dev, reason, ie, ie_len, gfp); 151 } 152 #endif 153 154 static enum ieee80211_reasoncode 155 osif_cm_get_disconnect_reason(struct vdev_osif_priv *osif_priv, uint16_t reason) 156 { 157 enum ieee80211_reasoncode ieee80211_reason = WLAN_REASON_UNSPECIFIED; 158 159 if (reason < REASON_PROP_START) 160 ieee80211_reason = reason; 161 /* 162 * Applications expect reason code as 0 for beacon miss failure 163 * due to backward compatibility. So send ieee80211_reason as 0. 164 */ 165 if (reason == REASON_BEACON_MISSED) 166 ieee80211_reason = 0; 167 168 return ieee80211_reason; 169 } 170 171 #ifdef CONN_MGR_ADV_FEATURE 172 static inline bool 173 osif_is_disconnect_locally_generated(struct wlan_cm_discon_rsp *rsp) 174 { 175 if (rsp->req.req.source == CM_PEER_DISCONNECT) 176 return false; 177 178 return true; 179 } 180 #else 181 static inline bool 182 osif_is_disconnect_locally_generated(struct wlan_cm_discon_rsp *rsp) 183 { 184 if (rsp->req.req.source == CM_PEER_DISCONNECT || 185 rsp->req.req.source == CM_SB_DISCONNECT) 186 return false; 187 188 return true; 189 } 190 #endif 191 192 QDF_STATUS osif_disconnect_handler(struct wlan_objmgr_vdev *vdev, 193 struct wlan_cm_discon_rsp *rsp) 194 { 195 enum ieee80211_reasoncode ieee80211_reason; 196 struct vdev_osif_priv *osif_priv = wlan_vdev_get_ospriv(vdev); 197 bool locally_generated = true; 198 QDF_STATUS status = QDF_STATUS_SUCCESS; 199 enum qca_disconnect_reason_codes qca_reason; 200 201 qca_reason = osif_cm_mac_to_qca_reason(rsp->req.req.reason_code); 202 ieee80211_reason = 203 osif_cm_get_disconnect_reason(osif_priv, 204 rsp->req.req.reason_code); 205 206 locally_generated = osif_is_disconnect_locally_generated(rsp); 207 208 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", 209 osif_priv->wdev->netdev->name, 210 rsp->req.req.vdev_id, 211 QDF_MAC_ADDR_REF(wlan_vdev_mlme_get_macaddr(vdev)), 212 locally_generated ? "locally-generated " : "", 213 QDF_MAC_ADDR_REF(rsp->req.req.bssid.bytes), 214 rsp->req.cm_id, rsp->req.req.source, ieee80211_reason, 215 ucfg_cm_reason_code_to_str(rsp->req.req.reason_code), 216 qca_reason, 217 osif_cm_qca_reason_to_str(qca_reason)); 218 219 /* Unlink bss if disconnect is from peer or south bound */ 220 if (rsp->req.req.source == CM_PEER_DISCONNECT || 221 rsp->req.req.source == CM_SB_DISCONNECT) 222 osif_cm_unlink_bss(vdev, osif_priv, &rsp->req.req.bssid, 223 NULL, 0); 224 225 status = osif_validate_disconnect_and_reset_src_id(osif_priv, rsp); 226 if (QDF_IS_STATUS_ERROR(status)) { 227 osif_cm_disconnect_comp_ind(vdev, rsp, OSIF_NOT_HANDLED); 228 return status; 229 } 230 231 osif_cm_disconnect_comp_ind(vdev, rsp, OSIF_PRE_USERSPACE_UPDATE); 232 osif_cm_indicate_disconnect(vdev, osif_priv->wdev->netdev, 233 ieee80211_reason, 234 locally_generated, rsp->ap_discon_ie.ptr, 235 rsp->ap_discon_ie.len, 236 qdf_mem_malloc_flags()); 237 238 osif_cm_disconnect_comp_ind(vdev, rsp, OSIF_POST_USERSPACE_UPDATE); 239 240 return status; 241 } 242