1 /* 2 * Copyright (c) 2012-2015, 2020-2021 The Linux Foundation. All rights reserved. 3 * 4 * Permission to use, copy, modify, and/or distribute this software for any 5 * purpose with or without fee is hereby granted, provided that the above 6 * copyright notice and this permission notice appear in all copies. 7 * 8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 */ 16 17 /** 18 * DOC: osif_cm_disconnect_rsp.c 19 * 20 * This file maintains definitaions of disconnect response 21 * fucntions. 22 */ 23 24 #include <wlan_cfg80211.h> 25 #include <linux/wireless.h> 26 #include "osif_cm_rsp.h" 27 #include "wlan_osif_priv.h" 28 #include "osif_cm_util.h" 29 #include "wlan_mlo_mgr_sta.h" 30 31 /** 32 * osif_validate_disconnect_and_reset_src_id() - Validate disconnection 33 * and resets source and id 34 * @osif_priv: Pointer to vdev osif priv 35 * @rsp: Disconnect response from connectin manager 36 * 37 * This function validates disconnect response and if the disconnect 38 * response is valid, resets the source and id of the command 39 * 40 * Context: Any context. Takes and releases cmd id spinlock. 41 * Return: QDF_STATUS 42 */ 43 44 static QDF_STATUS 45 osif_validate_disconnect_and_reset_src_id(struct vdev_osif_priv *osif_priv, 46 struct wlan_cm_discon_rsp *rsp) 47 { 48 QDF_STATUS status = QDF_STATUS_SUCCESS; 49 50 /* Always drop internal disconnect */ 51 qdf_spinlock_acquire(&osif_priv->cm_info.cmd_id_lock); 52 if (rsp->req.req.source == CM_INTERNAL_DISCONNECT) { 53 osif_debug("ignore internal disconnect"); 54 status = QDF_STATUS_E_INVAL; 55 goto rel_lock; 56 } 57 58 /* 59 * Send to kernel only if last osif cmd type is disconnect and 60 * cookie match else drop. If cookie match reset the cookie 61 * and source 62 */ 63 if (rsp->req.cm_id != osif_priv->cm_info.last_id || 64 rsp->req.req.source != osif_priv->cm_info.last_source) { 65 osif_debug("Ignore as cm_id(0x%x)/src(%d) didn't match stored cm_id(0x%x)/src(%d)", 66 rsp->req.cm_id, rsp->req.req.source, 67 osif_priv->cm_info.last_id, 68 osif_priv->cm_info.last_source); 69 status = QDF_STATUS_E_INVAL; 70 goto rel_lock; 71 } 72 73 osif_cm_reset_id_and_src_no_lock(osif_priv); 74 rel_lock: 75 qdf_spinlock_release(&osif_priv->cm_info.cmd_id_lock); 76 77 return status; 78 } 79 80 #if defined(CFG80211_DISCONNECTED_V2) || \ 81 (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 2, 0)) 82 #ifdef WLAN_FEATURE_11BE_MLO 83 #ifdef WLAN_FEATURE_11BE_MLO_ADV_FEATURE 84 static void 85 osif_cm_indicate_disconnect(struct wlan_objmgr_vdev *vdev, 86 struct net_device *dev, 87 enum ieee80211_reasoncode reason, 88 bool locally_generated, const u8 *ie, 89 size_t ie_len, gfp_t gfp) 90 { 91 if (wlan_vdev_mlme_is_mlo_vdev(vdev)) { 92 if (!wlan_vdev_mlme_is_mlo_link_vdev(vdev)) 93 cfg80211_disconnected(dev, reason, ie, 94 ie_len, locally_generated, gfp); 95 } else { 96 cfg80211_disconnected(dev, reason, ie, 97 ie_len, locally_generated, gfp); 98 } 99 } 100 #else /* WLAN_FEATURE_11BE_MLO_ADV_FEATURE */ 101 static void 102 osif_cm_indicate_disconnect(struct wlan_objmgr_vdev *vdev, 103 struct net_device *dev, 104 enum ieee80211_reasoncode reason, 105 bool locally_generated, const u8 *ie, 106 size_t ie_len, gfp_t gfp) 107 { 108 struct net_device *netdev = dev; 109 struct vdev_osif_priv *osif_priv = NULL; 110 struct wlan_objmgr_vdev *assoc_vdev = NULL; 111 112 if (!wlan_vdev_mlme_is_mlo_vdev(vdev)) { 113 cfg80211_disconnected(netdev, reason, ie, ie_len, 114 locally_generated, gfp); 115 return; 116 } 117 118 if (ucfg_mlo_is_mld_disconnected(vdev)) { 119 assoc_vdev = ucfg_mlo_get_assoc_link_vdev(vdev); 120 if (!assoc_vdev) 121 return; 122 osif_priv = wlan_vdev_get_ospriv(assoc_vdev); 123 netdev = osif_priv->wdev->netdev; 124 cfg80211_disconnected(netdev, reason, 125 ie, ie_len, 126 locally_generated, gfp); 127 } 128 } 129 #endif /* WLAN_FEATURE_11BE_MLO_ADV_FEATURE */ 130 #else /* WLAN_FEATURE_11BE_MLO */ 131 static void 132 osif_cm_indicate_disconnect(struct wlan_objmgr_vdev *vdev, 133 struct net_device *dev, 134 enum ieee80211_reasoncode reason, 135 bool locally_generated, const u8 *ie, 136 size_t ie_len, gfp_t gfp) 137 { 138 cfg80211_disconnected(dev, reason, ie, ie_len, locally_generated, gfp); 139 } 140 #endif /* WLAN_FEATURE_11BE_MLO */ 141 #else 142 static void 143 osif_cm_indicate_disconnect(struct wlan_objmgr_vdev *vdev, 144 struct net_device *dev, 145 enum ieee80211_reasoncode reason, 146 bool locally_generated, const u8 *ie, 147 size_t ie_len, gfp_t gfp) 148 { 149 cfg80211_disconnected(dev, reason, ie, ie_len, gfp); 150 } 151 #endif 152 153 static enum ieee80211_reasoncode 154 osif_cm_get_disconnect_reason(struct vdev_osif_priv *osif_priv, uint16_t reason) 155 { 156 enum ieee80211_reasoncode ieee80211_reason = WLAN_REASON_UNSPECIFIED; 157 158 if (reason < REASON_PROP_START) 159 ieee80211_reason = reason; 160 /* 161 * Applications expect reason code as 0 for beacon miss failure 162 * due to backward compatibility. So send ieee80211_reason as 0. 163 */ 164 if (reason == REASON_BEACON_MISSED) 165 ieee80211_reason = 0; 166 167 return ieee80211_reason; 168 } 169 170 #ifdef CONN_MGR_ADV_FEATURE 171 static inline bool 172 osif_is_disconnect_locally_generated(struct wlan_cm_discon_rsp *rsp) 173 { 174 if (rsp->req.req.source == CM_PEER_DISCONNECT) 175 return false; 176 177 return true; 178 } 179 #else 180 static inline bool 181 osif_is_disconnect_locally_generated(struct wlan_cm_discon_rsp *rsp) 182 { 183 if (rsp->req.req.source == CM_PEER_DISCONNECT || 184 rsp->req.req.source == CM_SB_DISCONNECT) 185 return false; 186 187 return true; 188 } 189 #endif 190 191 QDF_STATUS osif_disconnect_handler(struct wlan_objmgr_vdev *vdev, 192 struct wlan_cm_discon_rsp *rsp) 193 { 194 enum ieee80211_reasoncode ieee80211_reason; 195 struct vdev_osif_priv *osif_priv = wlan_vdev_get_ospriv(vdev); 196 bool locally_generated = true; 197 QDF_STATUS status = QDF_STATUS_SUCCESS; 198 enum qca_disconnect_reason_codes qca_reason; 199 200 qca_reason = osif_cm_mac_to_qca_reason(rsp->req.req.reason_code); 201 ieee80211_reason = 202 osif_cm_get_disconnect_reason(osif_priv, 203 rsp->req.req.reason_code); 204 205 locally_generated = osif_is_disconnect_locally_generated(rsp); 206 207 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", 208 osif_priv->wdev->netdev->name, 209 rsp->req.req.vdev_id, 210 QDF_MAC_ADDR_REF(wlan_vdev_mlme_get_macaddr(vdev)), 211 locally_generated ? "locally-generated " : "", 212 QDF_MAC_ADDR_REF(rsp->req.req.bssid.bytes), 213 rsp->req.cm_id, rsp->req.req.source, ieee80211_reason, 214 ucfg_cm_reason_code_to_str(rsp->req.req.reason_code), 215 qca_reason, 216 osif_cm_qca_reason_to_str(qca_reason)); 217 218 /* Unlink bss if disconnect is from peer or south bound */ 219 if (rsp->req.req.source == CM_PEER_DISCONNECT || 220 rsp->req.req.source == CM_SB_DISCONNECT) 221 osif_cm_unlink_bss(vdev, osif_priv, &rsp->req.req.bssid, 222 NULL, 0); 223 224 status = osif_validate_disconnect_and_reset_src_id(osif_priv, rsp); 225 if (QDF_IS_STATUS_ERROR(status)) { 226 osif_cm_disconnect_comp_ind(vdev, rsp, OSIF_NOT_HANDLED); 227 return status; 228 } 229 230 osif_cm_disconnect_comp_ind(vdev, rsp, OSIF_PRE_USERSPACE_UPDATE); 231 osif_cm_indicate_disconnect(vdev, osif_priv->wdev->netdev, 232 ieee80211_reason, 233 locally_generated, rsp->ap_discon_ie.ptr, 234 rsp->ap_discon_ie.len, 235 qdf_mem_malloc_flags()); 236 237 osif_cm_disconnect_comp_ind(vdev, rsp, OSIF_POST_USERSPACE_UPDATE); 238 239 return status; 240 } 241