1 /* 2 * Copyright (c) 2012-2015,2020-2021 The Linux Foundation. All rights reserved. 3 * Copyright (c) 2021-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: Implements disconnect specific apis of connection manager 20 */ 21 #include "wlan_cm_main_api.h" 22 #include "wlan_cm_sm.h" 23 #include "wlan_cm_roam.h" 24 #include <wlan_serialization_api.h> 25 #include "wlan_utility.h" 26 #include "wlan_scan_api.h" 27 #include "wlan_crypto_global_api.h" 28 #ifdef CONN_MGR_ADV_FEATURE 29 #include "wlan_blm_api.h" 30 #endif 31 #include <wlan_mlo_mgr_sta.h> 32 33 void cm_send_disconnect_resp(struct cnx_mgr *cm_ctx, wlan_cm_id cm_id) 34 { 35 struct wlan_cm_discon_rsp resp; 36 QDF_STATUS status; 37 38 qdf_mem_zero(&resp, sizeof(resp)); 39 status = cm_fill_disconnect_resp_from_cm_id(cm_ctx, cm_id, &resp); 40 if (QDF_IS_STATUS_SUCCESS(status)) 41 cm_disconnect_complete(cm_ctx, &resp); 42 } 43 44 #ifdef WLAN_CM_USE_SPINLOCK 45 static QDF_STATUS cm_activate_disconnect_req_sched_cb(struct scheduler_msg *msg) 46 { 47 struct wlan_serialization_command *cmd = msg->bodyptr; 48 struct wlan_objmgr_vdev *vdev; 49 struct cnx_mgr *cm_ctx; 50 QDF_STATUS ret = QDF_STATUS_E_FAILURE; 51 52 if (!cmd) { 53 mlme_err("cmd is null"); 54 return QDF_STATUS_E_INVAL; 55 } 56 57 vdev = cmd->vdev; 58 if (!vdev) { 59 mlme_err("vdev is null"); 60 return QDF_STATUS_E_INVAL; 61 } 62 63 cm_ctx = cm_get_cm_ctx(vdev); 64 if (!cm_ctx) 65 return QDF_STATUS_E_INVAL; 66 67 ret = cm_sm_deliver_event( 68 cm_ctx->vdev, 69 WLAN_CM_SM_EV_DISCONNECT_ACTIVE, 70 sizeof(wlan_cm_id), 71 &cmd->cmd_id); 72 73 /* 74 * Called from scheduler context hence 75 * handle failure if posting fails 76 */ 77 if (QDF_IS_STATUS_ERROR(ret)) { 78 mlme_err(CM_PREFIX_FMT "Activation failed for cmd:%d", 79 CM_PREFIX_REF(wlan_vdev_get_id(vdev), cmd->cmd_id), 80 cmd->cmd_type); 81 cm_send_disconnect_resp(cm_ctx, cmd->cmd_id); 82 } 83 84 wlan_objmgr_vdev_release_ref(vdev, WLAN_MLME_CM_ID); 85 return ret; 86 } 87 88 static QDF_STATUS 89 cm_activate_disconnect_req(struct wlan_serialization_command *cmd) 90 { 91 struct wlan_objmgr_vdev *vdev = cmd->vdev; 92 struct scheduler_msg msg = {0}; 93 QDF_STATUS ret; 94 95 msg.bodyptr = cmd; 96 msg.callback = cm_activate_disconnect_req_sched_cb; 97 msg.flush_callback = cm_activate_cmd_req_flush_cb; 98 99 ret = wlan_objmgr_vdev_try_get_ref(vdev, WLAN_MLME_CM_ID); 100 if (QDF_IS_STATUS_ERROR(ret)) 101 return ret; 102 103 ret = scheduler_post_message(QDF_MODULE_ID_MLME, 104 QDF_MODULE_ID_MLME, 105 QDF_MODULE_ID_MLME, &msg); 106 107 if (QDF_IS_STATUS_ERROR(ret)) { 108 mlme_err(CM_PREFIX_FMT "Failed to post scheduler_msg", 109 CM_PREFIX_REF(wlan_vdev_get_id(vdev), cmd->cmd_id)); 110 wlan_objmgr_vdev_release_ref(vdev, WLAN_MLME_CM_ID); 111 return ret; 112 } 113 mlme_debug(CM_PREFIX_FMT "Cmd act in sched cmd type:%d", 114 CM_PREFIX_REF(wlan_vdev_get_id(vdev), cmd->cmd_id), 115 cmd->cmd_type); 116 117 return ret; 118 } 119 #else 120 static QDF_STATUS 121 cm_activate_disconnect_req(struct wlan_serialization_command *cmd) 122 { 123 return cm_sm_deliver_event( 124 cmd->vdev, 125 WLAN_CM_SM_EV_DISCONNECT_ACTIVE, 126 sizeof(wlan_cm_id), 127 &cmd->cmd_id); 128 } 129 #endif 130 131 static QDF_STATUS 132 cm_sm_deliver_disconnect_event(struct cnx_mgr *cm_ctx, 133 struct wlan_serialization_command *cmd) 134 { 135 /* 136 * For pending to active, use async cmnd to take lock. 137 * Use sync command for direct activation as lock is already 138 * acquired. 139 */ 140 if (cmd->activation_reason == SER_PENDING_TO_ACTIVE) 141 return cm_activate_disconnect_req(cmd); 142 else 143 return cm_sm_deliver_event_sync( 144 cm_ctx, 145 WLAN_CM_SM_EV_DISCONNECT_ACTIVE, 146 sizeof(wlan_cm_id), 147 &cmd->cmd_id); 148 } 149 150 static QDF_STATUS 151 cm_ser_disconnect_cb(struct wlan_serialization_command *cmd, 152 enum wlan_serialization_cb_reason reason) 153 { 154 QDF_STATUS status = QDF_STATUS_SUCCESS; 155 struct wlan_objmgr_vdev *vdev; 156 struct cnx_mgr *cm_ctx; 157 158 if (!cmd) { 159 mlme_err("cmd is NULL, reason: %d", reason); 160 QDF_ASSERT(0); 161 return QDF_STATUS_E_NULL_VALUE; 162 } 163 164 vdev = cmd->vdev; 165 166 cm_ctx = cm_get_cm_ctx(vdev); 167 if (!cm_ctx) 168 return QDF_STATUS_E_NULL_VALUE; 169 170 switch (reason) { 171 case WLAN_SER_CB_ACTIVATE_CMD: 172 status = cm_sm_deliver_disconnect_event(cm_ctx, cmd); 173 if (QDF_IS_STATUS_SUCCESS(status)) 174 break; 175 /* 176 * Handle failure if posting fails, i.e. the SM state has 177 * changes. Disconnect should be handled in JOIN_PENDING, 178 * JOIN-SCAN state as well apart from DISCONNECTING. 179 * Also no need to check for head list as diconnect needs to be 180 * completed always once active. 181 */ 182 183 cm_send_disconnect_resp(cm_ctx, cmd->cmd_id); 184 break; 185 case WLAN_SER_CB_CANCEL_CMD: 186 /* command removed from pending list. */ 187 break; 188 case WLAN_SER_CB_ACTIVE_CMD_TIMEOUT: 189 mlme_err(CM_PREFIX_FMT "Active command timeout", 190 CM_PREFIX_REF(wlan_vdev_get_id(vdev), cmd->cmd_id)); 191 cm_trigger_panic_on_cmd_timeout(cm_ctx->vdev); 192 cm_send_disconnect_resp(cm_ctx, cmd->cmd_id); 193 break; 194 case WLAN_SER_CB_RELEASE_MEM_CMD: 195 cm_reset_active_cm_id(vdev, cmd->cmd_id); 196 wlan_objmgr_vdev_release_ref(vdev, WLAN_MLME_CM_ID); 197 break; 198 default: 199 QDF_ASSERT(0); 200 status = QDF_STATUS_E_INVAL; 201 break; 202 } 203 204 return status; 205 } 206 207 static QDF_STATUS cm_ser_disconnect_req(struct wlan_objmgr_pdev *pdev, 208 struct cnx_mgr *cm_ctx, 209 struct cm_disconnect_req *req) 210 { 211 struct wlan_serialization_command cmd = {0, }; 212 enum wlan_serialization_status ser_cmd_status; 213 QDF_STATUS status; 214 uint8_t vdev_id = wlan_vdev_get_id(cm_ctx->vdev); 215 216 status = wlan_objmgr_vdev_try_get_ref(cm_ctx->vdev, WLAN_MLME_CM_ID); 217 if (QDF_IS_STATUS_ERROR(status)) { 218 mlme_err(CM_PREFIX_FMT "unable to get reference", 219 CM_PREFIX_REF(vdev_id, req->cm_id)); 220 return status; 221 } 222 223 cmd.cmd_type = WLAN_SER_CMD_VDEV_DISCONNECT; 224 cmd.cmd_id = req->cm_id; 225 cmd.cmd_cb = cm_ser_disconnect_cb; 226 cmd.source = WLAN_UMAC_COMP_MLME; 227 cmd.is_high_priority = false; 228 cmd.cmd_timeout_duration = DISCONNECT_TIMEOUT; 229 cmd.vdev = cm_ctx->vdev; 230 cmd.is_blocking = cm_ser_get_blocking_cmd(); 231 232 ser_cmd_status = wlan_serialization_request(&cmd); 233 switch (ser_cmd_status) { 234 case WLAN_SER_CMD_PENDING: 235 /* command moved to pending list.Do nothing */ 236 break; 237 case WLAN_SER_CMD_ACTIVE: 238 /* command moved to active list. Do nothing */ 239 break; 240 default: 241 mlme_err(CM_PREFIX_FMT "ser cmd status %d", 242 CM_PREFIX_REF(vdev_id, req->cm_id), ser_cmd_status); 243 wlan_objmgr_vdev_release_ref(cm_ctx->vdev, WLAN_MLME_CM_ID); 244 245 return QDF_STATUS_E_FAILURE; 246 } 247 248 return QDF_STATUS_SUCCESS; 249 } 250 251 static void 252 cm_if_mgr_inform_disconnect_complete(struct wlan_objmgr_vdev *vdev) 253 { 254 struct if_mgr_event_data *disconnect_complete; 255 256 disconnect_complete = qdf_mem_malloc(sizeof(*disconnect_complete)); 257 if (!disconnect_complete) 258 return; 259 260 disconnect_complete->status = QDF_STATUS_SUCCESS; 261 262 if_mgr_deliver_event(vdev, WLAN_IF_MGR_EV_DISCONNECT_COMPLETE, 263 disconnect_complete); 264 qdf_mem_free(disconnect_complete); 265 } 266 267 static void 268 cm_if_mgr_inform_disconnect_start(struct wlan_objmgr_vdev *vdev) 269 { 270 struct if_mgr_event_data *disconnect_start; 271 272 disconnect_start = qdf_mem_malloc(sizeof(*disconnect_start)); 273 if (!disconnect_start) 274 return; 275 276 disconnect_start->status = QDF_STATUS_SUCCESS; 277 278 if_mgr_deliver_event(vdev, WLAN_IF_MGR_EV_DISCONNECT_START, 279 disconnect_start); 280 qdf_mem_free(disconnect_start); 281 } 282 283 void cm_initiate_internal_disconnect(struct cnx_mgr *cm_ctx) 284 { 285 struct cm_req *cm_req; 286 struct cm_disconnect_req *disconnect_req; 287 QDF_STATUS status; 288 289 cm_req = qdf_mem_malloc(sizeof(*cm_req)); 290 291 if (!cm_req) 292 return; 293 294 disconnect_req = &cm_req->discon_req; 295 disconnect_req->req.vdev_id = wlan_vdev_get_id(cm_ctx->vdev); 296 disconnect_req->req.source = CM_INTERNAL_DISCONNECT; 297 298 status = cm_add_disconnect_req_to_list(cm_ctx, disconnect_req); 299 if (QDF_IS_STATUS_ERROR(status)) { 300 mlme_err(CM_PREFIX_FMT "failed to add disconnect req", 301 CM_PREFIX_REF(disconnect_req->req.vdev_id, 302 disconnect_req->cm_id)); 303 qdf_mem_free(cm_req); 304 return; 305 } 306 307 cm_disconnect_start(cm_ctx, disconnect_req); 308 } 309 310 QDF_STATUS cm_disconnect_start(struct cnx_mgr *cm_ctx, 311 struct cm_disconnect_req *req) 312 { 313 struct wlan_objmgr_pdev *pdev; 314 QDF_STATUS status; 315 316 pdev = wlan_vdev_get_pdev(cm_ctx->vdev); 317 if (!pdev) { 318 cm_send_disconnect_resp(cm_ctx, req->cm_id); 319 return QDF_STATUS_E_INVAL; 320 } 321 cm_vdev_scan_cancel(pdev, cm_ctx->vdev); 322 mlme_cm_disconnect_start_ind(cm_ctx->vdev, &req->req); 323 cm_if_mgr_inform_disconnect_start(cm_ctx->vdev); 324 mlme_cm_osif_disconnect_start_ind(cm_ctx->vdev); 325 326 /* Serialize disconnect req, Handle failure status */ 327 status = cm_ser_disconnect_req(pdev, cm_ctx, req); 328 329 if (QDF_IS_STATUS_ERROR(status)) 330 cm_send_disconnect_resp(cm_ctx, req->cm_id); 331 332 return QDF_STATUS_SUCCESS; 333 } 334 335 void 336 cm_update_scan_mlme_on_disconnect(struct wlan_objmgr_vdev *vdev, 337 struct cm_disconnect_req *req) 338 { 339 struct wlan_objmgr_pdev *pdev; 340 struct bss_info bss_info; 341 struct mlme_info mlme; 342 struct wlan_channel *chan; 343 QDF_STATUS status; 344 345 pdev = wlan_vdev_get_pdev(vdev); 346 if (!pdev) { 347 mlme_err(CM_PREFIX_FMT "failed to find pdev", 348 CM_PREFIX_REF(req->req.vdev_id, req->cm_id)); 349 return; 350 } 351 352 chan = wlan_vdev_get_active_channel(vdev); 353 if (!chan) { 354 mlme_err(CM_PREFIX_FMT "failed to get active channel", 355 CM_PREFIX_REF(req->req.vdev_id, req->cm_id)); 356 return; 357 } 358 359 status = wlan_vdev_mlme_get_ssid(vdev, bss_info.ssid.ssid, 360 &bss_info.ssid.length); 361 362 if (QDF_IS_STATUS_ERROR(status)) { 363 mlme_err(CM_PREFIX_FMT "failed to get ssid", 364 CM_PREFIX_REF(req->req.vdev_id, req->cm_id)); 365 return; 366 } 367 368 mlme.assoc_state = SCAN_ENTRY_CON_STATE_NONE; 369 qdf_copy_macaddr(&bss_info.bssid, &req->req.bssid); 370 371 bss_info.freq = chan->ch_freq; 372 373 wlan_scan_update_mlme_by_bssinfo(pdev, &bss_info, &mlme); 374 } 375 376 QDF_STATUS cm_disconnect_active(struct cnx_mgr *cm_ctx, wlan_cm_id *cm_id) 377 { 378 struct wlan_cm_vdev_discon_req *req; 379 struct cm_req *cm_req; 380 struct qdf_mac_addr bssid = QDF_MAC_ADDR_ZERO_INIT; 381 QDF_STATUS status; 382 383 cm_req = cm_get_req_by_cm_id(cm_ctx, *cm_id); 384 if (!cm_req) 385 return QDF_STATUS_E_INVAL; 386 387 req = qdf_mem_malloc(sizeof(*req)); 388 if (!req) 389 return QDF_STATUS_E_NOMEM; 390 391 cm_ctx->active_cm_id = *cm_id; 392 wlan_vdev_get_bss_peer_mac(cm_ctx->vdev, &bssid); 393 /* 394 * for northbound req, bssid is not provided so update it from vdev 395 * in case bssid is not present 396 */ 397 if (qdf_is_macaddr_zero(&cm_req->discon_req.req.bssid) || 398 qdf_is_macaddr_broadcast(&cm_req->discon_req.req.bssid)) 399 qdf_copy_macaddr(&cm_req->discon_req.req.bssid, &bssid); 400 401 qdf_copy_macaddr(&req->req.bssid, &bssid); 402 403 req->cm_id = *cm_id; 404 req->req.vdev_id = wlan_vdev_get_id(cm_ctx->vdev); 405 req->req.source = cm_req->discon_req.req.source; 406 req->req.reason_code = cm_req->discon_req.req.reason_code; 407 req->req.is_no_disassoc_disconnect = 408 cm_req->discon_req.req.is_no_disassoc_disconnect; 409 410 cm_update_scan_mlme_on_disconnect(cm_ctx->vdev, 411 &cm_req->discon_req); 412 413 mlme_debug(CM_PREFIX_FMT "disconnect " QDF_MAC_ADDR_FMT " source %d reason %d", 414 CM_PREFIX_REF(req->req.vdev_id, req->cm_id), 415 QDF_MAC_ADDR_REF(req->req.bssid.bytes), 416 req->req.source, req->req.reason_code); 417 status = mlme_cm_disconnect_req(cm_ctx->vdev, req); 418 if (QDF_IS_STATUS_ERROR(status)) { 419 mlme_err(CM_PREFIX_FMT "disconnect req fail", 420 CM_PREFIX_REF(req->req.vdev_id, req->cm_id)); 421 cm_send_disconnect_resp(cm_ctx, req->cm_id); 422 } 423 qdf_mem_free(req); 424 425 return status; 426 } 427 428 #ifdef CONN_MGR_ADV_FEATURE 429 static void 430 cm_inform_blm_disconnect_complete(struct wlan_objmgr_vdev *vdev, 431 struct wlan_cm_discon_rsp *resp) 432 { 433 struct wlan_objmgr_pdev *pdev; 434 435 pdev = wlan_vdev_get_pdev(vdev); 436 if (!pdev) { 437 mlme_err(CM_PREFIX_FMT "failed to find pdev", 438 CM_PREFIX_REF(wlan_vdev_get_id(vdev), 439 resp->req.cm_id)); 440 return; 441 } 442 443 wlan_dlm_update_bssid_connect_params(pdev, resp->req.req.bssid, 444 DLM_AP_DISCONNECTED); 445 } 446 447 #else 448 static inline void 449 cm_inform_blm_disconnect_complete(struct wlan_objmgr_vdev *vdev, 450 struct wlan_cm_discon_rsp *resp) 451 {} 452 #endif 453 454 #ifdef WLAN_FEATURE_11BE_MLO 455 #ifdef WLAN_FEATURE_11BE_MLO_ADV_FEATURE 456 static inline void 457 cm_clear_vdev_mlo_cap(struct wlan_objmgr_vdev *vdev) 458 { 459 wlan_vdev_mlme_feat_ext2_cap_clear(vdev, WLAN_VDEV_FEXT2_MLO); 460 } 461 #else /*WLAN_FEATURE_11BE_MLO_ADV_FEATURE*/ 462 static inline void 463 cm_clear_vdev_mlo_cap(struct wlan_objmgr_vdev *vdev) 464 { 465 if (mlo_is_mld_sta(vdev) && ucfg_mlo_is_mld_disconnected(vdev)) 466 ucfg_mlo_mld_clear_mlo_cap(vdev); 467 } 468 #endif /*WLAN_FEATURE_11BE_MLO_ADV_FEATURE*/ 469 #else /*WLAN_FEATURE_11BE_MLO*/ 470 static inline void 471 cm_clear_vdev_mlo_cap(struct wlan_objmgr_vdev *vdev) 472 { } 473 #endif /*WLAN_FEATURE_11BE_MLO*/ 474 475 QDF_STATUS cm_notify_disconnect_complete(struct cnx_mgr *cm_ctx, 476 struct wlan_cm_discon_rsp *resp) 477 { 478 mlme_cm_disconnect_complete_ind(cm_ctx->vdev, resp); 479 mlo_sta_link_disconn_notify(cm_ctx->vdev, resp); 480 mlme_cm_osif_disconnect_complete(cm_ctx->vdev, resp); 481 cm_if_mgr_inform_disconnect_complete(cm_ctx->vdev); 482 cm_inform_blm_disconnect_complete(cm_ctx->vdev, resp); 483 484 /* Clear MLO cap only when it is the last disconnect req 485 * as osif would not have informed userspace for other disconnect 486 * req because of cm_id mismatch 487 */ 488 if (cm_ctx->disconnect_count == 1) 489 cm_clear_vdev_mlo_cap(cm_ctx->vdev); 490 491 return QDF_STATUS_SUCCESS; 492 } 493 494 QDF_STATUS cm_disconnect_complete(struct cnx_mgr *cm_ctx, 495 struct wlan_cm_discon_rsp *resp) 496 { 497 /* 498 * If the entry is not present in the list, it must have been cleared 499 * already. 500 */ 501 if (!cm_get_req_by_cm_id(cm_ctx, resp->req.cm_id)) 502 return QDF_STATUS_SUCCESS; 503 504 cm_notify_disconnect_complete(cm_ctx, resp); 505 506 /* 507 * Remove all pending disconnect if this is an active disconnect 508 * complete. 509 */ 510 if (resp->req.cm_id == cm_ctx->active_cm_id) 511 cm_flush_pending_request(cm_ctx, DISCONNECT_REQ_PREFIX, false); 512 513 cm_remove_cmd(cm_ctx, &resp->req.cm_id); 514 mlme_debug(CM_PREFIX_FMT "disconnect count %d connect count %d", 515 CM_PREFIX_REF(wlan_vdev_get_id(cm_ctx->vdev), 516 resp->req.cm_id), 517 cm_ctx->disconnect_count, cm_ctx->connect_count); 518 /* Flush failed connect req as pending disconnect is completed */ 519 if (!cm_ctx->disconnect_count && cm_ctx->connect_count) 520 cm_flush_pending_request(cm_ctx, CONNECT_REQ_PREFIX, true); 521 522 /* Set the disconnect wait event once all disconnect are completed */ 523 if (!cm_ctx->disconnect_count) 524 qdf_event_set(&cm_ctx->disconnect_complete); 525 526 return QDF_STATUS_SUCCESS; 527 } 528 529 QDF_STATUS 530 cm_handle_discon_req_in_non_connected_state(struct cnx_mgr *cm_ctx, 531 struct cm_disconnect_req *cm_req, 532 enum wlan_cm_sm_state cm_state_substate) 533 { 534 enum wlan_cm_sm_state cur_state = cm_get_state(cm_ctx); 535 536 /* 537 * South bound and peer disconnect requests are meant for only in 538 * connected state, so if the state is connecting a new connect has 539 * been received, hence skip the non-osif disconnect request. Also allow 540 * MLO link vdev disconnect in connecting state, as this can be 541 * initiated due to disconnect on assoc vdev, which may be in connected 542 * state. 543 */ 544 if (cur_state == WLAN_CM_S_CONNECTING && 545 (cm_req->req.source != CM_OSIF_DISCONNECT && 546 cm_req->req.source != CM_OSIF_CFG_DISCONNECT && 547 cm_req->req.source != CM_MLO_LINK_VDEV_DISCONNECT)) { 548 mlme_info("Vdev %d ignore disconnect req from source %d in state %d", 549 wlan_vdev_get_id(cm_ctx->vdev), cm_req->req.source, 550 cm_state_substate); 551 return QDF_STATUS_E_INVAL; 552 } 553 554 switch (cm_state_substate) { 555 case WLAN_CM_S_DISCONNECTING: 556 /* 557 * There would be pending disconnect requests in the list, and 558 * if they are flushed as part of new disconnect 559 * (cm_flush_pending_request), OS_IF would inform the kernel 560 * about the disconnect done even though the disconnect is still 561 * pending. So update OS_IF with invalid CM_ID so that the resp 562 * of only the new disconnect req is given to kernel. 563 */ 564 mlme_cm_osif_update_id_and_src(cm_ctx->vdev, 565 CM_SOURCE_INVALID, 566 CM_ID_INVALID); 567 cm_flush_pending_request(cm_ctx, DISCONNECT_REQ_PREFIX, false); 568 /* 569 * Flush failed pending connect req as new req is received 570 * and its no longer the latest one. 571 */ 572 if (cm_ctx->connect_count) 573 cm_flush_pending_request(cm_ctx, CONNECT_REQ_PREFIX, 574 true); 575 break; 576 case WLAN_CM_S_ROAMING: 577 /* for FW roam/LFR3 remove the req from the list */ 578 if (cm_roam_offload_enabled(wlan_vdev_get_psoc(cm_ctx->vdev))) 579 cm_flush_pending_request(cm_ctx, ROAM_REQ_PREFIX, 580 false); 581 /* fallthrough */ 582 case WLAN_CM_SS_JOIN_ACTIVE: 583 /* 584 * In join active/roaming state, there would be no pending 585 * command, so no action required. so for new disconnect 586 * request, queue disconnect and move the state to 587 * disconnecting. 588 */ 589 break; 590 case WLAN_CM_SS_SCAN: 591 /* In the scan state abort the ongoing scan */ 592 cm_vdev_scan_cancel(wlan_vdev_get_pdev(cm_ctx->vdev), 593 cm_ctx->vdev); 594 /* fallthrough */ 595 case WLAN_CM_SS_JOIN_PENDING: 596 /* 597 * There would be pending disconnect requests in the list, and 598 * if they are flushed as part of new disconnect 599 * (cm_flush_pending_request), OS_IF would inform the kernel 600 * about the disconnect done even though the disconnect is still 601 * pending. So update OS_IF with invalid CM_ID so that the resp 602 * of only the new disconnect req is given to kernel. 603 */ 604 mlme_cm_osif_update_id_and_src(cm_ctx->vdev, 605 CM_SOURCE_INVALID, 606 CM_ID_INVALID); 607 /* 608 * In case of scan or join pending there could be a connect and 609 * disconnect requests pending, so flush all the requests except 610 * the activated request. 611 */ 612 cm_flush_pending_request(cm_ctx, CONNECT_REQ_PREFIX, false); 613 cm_flush_pending_request(cm_ctx, DISCONNECT_REQ_PREFIX, false); 614 break; 615 case WLAN_CM_S_INIT: 616 /* 617 * In this case the vdev is already disconnected and thus the 618 * indication to upper layer, would have been sent as part of 619 * previous disconnect/connect failure. 620 * 621 * If upper layer is in process of connecting, sending 622 * disconnect indication back again may cause it to incorrectly 623 * think it as a connect failure. So sending disconnect 624 * indication again is not advisable. 625 * 626 * So no need to do anything here, just return failure and drop 627 * disconnect. 628 */ 629 mlme_info("vdev %d droping disconnect req from source %d in INIT state", 630 wlan_vdev_get_id(cm_ctx->vdev), cm_req->req.source); 631 return QDF_STATUS_E_ALREADY; 632 default: 633 mlme_err("Vdev %d disconnect req in invalid state %d", 634 wlan_vdev_get_id(cm_ctx->vdev), 635 cm_state_substate); 636 return QDF_STATUS_E_FAILURE; 637 }; 638 639 /* Queue the new disconnect request after state specific actions */ 640 return cm_add_disconnect_req_to_list(cm_ctx, cm_req); 641 } 642 643 QDF_STATUS cm_add_disconnect_req_to_list(struct cnx_mgr *cm_ctx, 644 struct cm_disconnect_req *req) 645 { 646 QDF_STATUS status; 647 struct cm_req *cm_req; 648 649 cm_req = qdf_container_of(req, struct cm_req, discon_req); 650 req->cm_id = cm_get_cm_id(cm_ctx, req->req.source); 651 cm_req->cm_id = req->cm_id; 652 status = cm_add_req_to_list_and_indicate_osif(cm_ctx, cm_req, 653 req->req.source); 654 655 return status; 656 } 657 658 QDF_STATUS cm_disconnect_start_req(struct wlan_objmgr_vdev *vdev, 659 struct wlan_cm_disconnect_req *req) 660 { 661 struct cnx_mgr *cm_ctx; 662 struct cm_req *cm_req; 663 struct cm_disconnect_req *disconnect_req; 664 QDF_STATUS status; 665 666 cm_ctx = cm_get_cm_ctx(vdev); 667 if (!cm_ctx) 668 return QDF_STATUS_E_INVAL; 669 670 /* 671 * This would be freed as part of removal from cm req list if adding 672 * to list is success after posting WLAN_CM_SM_EV_DISCONNECT_REQ. 673 */ 674 cm_req = qdf_mem_malloc(sizeof(*cm_req)); 675 676 if (!cm_req) 677 return QDF_STATUS_E_NOMEM; 678 679 if (wlan_vdev_mlme_is_mlo_vdev(vdev) && 680 !wlan_vdev_mlme_is_mlo_link_vdev(vdev)) 681 req->is_no_disassoc_disconnect = 1; 682 683 disconnect_req = &cm_req->discon_req; 684 disconnect_req->req = *req; 685 686 status = cm_sm_deliver_event(vdev, WLAN_CM_SM_EV_DISCONNECT_REQ, 687 sizeof(*disconnect_req), disconnect_req); 688 /* free the req if disconnect is not handled */ 689 if (QDF_IS_STATUS_ERROR(status)) 690 qdf_mem_free(cm_req); 691 692 return status; 693 } 694 695 QDF_STATUS cm_disconnect_start_req_sync(struct wlan_objmgr_vdev *vdev, 696 struct wlan_cm_disconnect_req *req) 697 { 698 struct cnx_mgr *cm_ctx; 699 QDF_STATUS status; 700 701 cm_ctx = cm_get_cm_ctx(vdev); 702 if (!cm_ctx) 703 return QDF_STATUS_E_INVAL; 704 705 if (wlan_vdev_mlme_is_mlo_vdev(vdev) && 706 !wlan_vdev_mlme_is_mlo_link_vdev(vdev)) 707 req->is_no_disassoc_disconnect = 1; 708 709 qdf_event_reset(&cm_ctx->disconnect_complete); 710 status = cm_disconnect_start_req(vdev, req); 711 if (QDF_IS_STATUS_ERROR(status)) { 712 mlme_err("Disconnect failed with status %d", status); 713 return status; 714 } 715 716 status = qdf_wait_single_event(&cm_ctx->disconnect_complete, 717 CM_DISCONNECT_CMD_TIMEOUT); 718 if (QDF_IS_STATUS_ERROR(status)) 719 mlme_err("Disconnect timeout with status %d", status); 720 721 return status; 722 } 723 724 QDF_STATUS cm_disconnect_rsp(struct wlan_objmgr_vdev *vdev, 725 struct wlan_cm_discon_rsp *resp) 726 { 727 struct cnx_mgr *cm_ctx; 728 QDF_STATUS qdf_status; 729 wlan_cm_id cm_id; 730 uint32_t prefix; 731 732 cm_ctx = cm_get_cm_ctx(vdev); 733 if (!cm_ctx) 734 return QDF_STATUS_E_INVAL; 735 736 cm_id = cm_ctx->active_cm_id; 737 prefix = CM_ID_GET_PREFIX(cm_id); 738 739 if (prefix != DISCONNECT_REQ_PREFIX || cm_id != resp->req.cm_id) { 740 mlme_err(CM_PREFIX_FMT "Active cm_id 0x%x is different", 741 CM_PREFIX_REF(wlan_vdev_get_id(vdev), resp->req.cm_id), 742 cm_id); 743 qdf_status = QDF_STATUS_E_FAILURE; 744 goto disconnect_complete; 745 } 746 qdf_status = 747 cm_sm_deliver_event(vdev, 748 WLAN_CM_SM_EV_DISCONNECT_DONE, 749 sizeof(*resp), resp); 750 if (QDF_IS_STATUS_ERROR(qdf_status)) 751 goto disconnect_complete; 752 753 return qdf_status; 754 755 disconnect_complete: 756 /* 757 * If there is a event posting error it means the SM state is not in 758 * DISCONNECTING (some new cmd has changed the state of SM), so just 759 * complete the disconnect command. 760 */ 761 return cm_disconnect_complete(cm_ctx, resp); 762 } 763 764 QDF_STATUS cm_bss_peer_delete_req(struct wlan_objmgr_vdev *vdev, 765 struct qdf_mac_addr *peer_mac) 766 { 767 mlme_debug("vdev-id %d, delete peer" QDF_MAC_ADDR_FMT, 768 wlan_vdev_get_id(vdev), QDF_MAC_ADDR_REF(peer_mac->bytes)); 769 770 return mlme_cm_bss_peer_delete_req(vdev); 771 } 772 773 QDF_STATUS cm_vdev_down_req(struct wlan_objmgr_vdev *vdev, uint32_t status) 774 { 775 mlme_debug("vdev id %d down req status %d", 776 wlan_vdev_get_id(vdev), status); 777 778 return mlme_cm_vdev_down_req(vdev); 779 } 780