1 /* 2 * Copyright (c) 2012-2015,2020-2021 The Linux Foundation. All rights reserved. 3 * Copyright (c) 2021-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: 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_dlm_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 if (wlan_vdev_mlme_is_mlo_vdev(cm_ctx->vdev)) 299 mlo_internal_disconnect_links(cm_ctx->vdev); 300 301 status = cm_add_disconnect_req_to_list(cm_ctx, disconnect_req); 302 if (QDF_IS_STATUS_ERROR(status)) { 303 mlme_err(CM_PREFIX_FMT "failed to add disconnect req", 304 CM_PREFIX_REF(disconnect_req->req.vdev_id, 305 disconnect_req->cm_id)); 306 qdf_mem_free(cm_req); 307 return; 308 } 309 310 cm_disconnect_start(cm_ctx, disconnect_req); 311 } 312 313 QDF_STATUS cm_disconnect_start(struct cnx_mgr *cm_ctx, 314 struct cm_disconnect_req *req) 315 { 316 struct wlan_objmgr_pdev *pdev; 317 QDF_STATUS status; 318 319 pdev = wlan_vdev_get_pdev(cm_ctx->vdev); 320 if (!pdev) { 321 cm_send_disconnect_resp(cm_ctx, req->cm_id); 322 return QDF_STATUS_E_INVAL; 323 } 324 325 if (wlan_vdev_mlme_is_mlo_vdev(cm_ctx->vdev)) 326 mlo_internal_disconnect_links(cm_ctx->vdev); 327 328 cm_vdev_scan_cancel(pdev, cm_ctx->vdev); 329 mlme_cm_disconnect_start_ind(cm_ctx->vdev, &req->req); 330 cm_if_mgr_inform_disconnect_start(cm_ctx->vdev); 331 mlme_cm_osif_disconnect_start_ind(cm_ctx->vdev); 332 333 /* Serialize disconnect req, Handle failure status */ 334 status = cm_ser_disconnect_req(pdev, cm_ctx, req); 335 336 if (QDF_IS_STATUS_ERROR(status)) 337 cm_send_disconnect_resp(cm_ctx, req->cm_id); 338 339 return QDF_STATUS_SUCCESS; 340 } 341 342 void 343 cm_update_scan_mlme_on_disconnect(struct wlan_objmgr_vdev *vdev, 344 struct cm_disconnect_req *req) 345 { 346 struct wlan_objmgr_pdev *pdev; 347 struct bss_info bss_info; 348 struct mlme_info mlme; 349 struct wlan_channel *chan; 350 QDF_STATUS status; 351 352 pdev = wlan_vdev_get_pdev(vdev); 353 if (!pdev) { 354 mlme_err(CM_PREFIX_FMT "failed to find pdev", 355 CM_PREFIX_REF(req->req.vdev_id, req->cm_id)); 356 return; 357 } 358 359 chan = wlan_vdev_get_active_channel(vdev); 360 if (!chan) { 361 mlme_err(CM_PREFIX_FMT "failed to get active channel", 362 CM_PREFIX_REF(req->req.vdev_id, req->cm_id)); 363 return; 364 } 365 366 status = wlan_vdev_mlme_get_ssid(vdev, bss_info.ssid.ssid, 367 &bss_info.ssid.length); 368 369 if (QDF_IS_STATUS_ERROR(status)) { 370 mlme_err(CM_PREFIX_FMT "failed to get ssid", 371 CM_PREFIX_REF(req->req.vdev_id, req->cm_id)); 372 return; 373 } 374 375 mlme.assoc_state = SCAN_ENTRY_CON_STATE_NONE; 376 qdf_copy_macaddr(&bss_info.bssid, &req->req.bssid); 377 378 bss_info.freq = chan->ch_freq; 379 380 wlan_scan_update_mlme_by_bssinfo(pdev, &bss_info, &mlme); 381 } 382 383 QDF_STATUS cm_disconnect_active(struct cnx_mgr *cm_ctx, wlan_cm_id *cm_id) 384 { 385 struct wlan_cm_vdev_discon_req *req; 386 struct cm_req *cm_req; 387 QDF_STATUS status = QDF_STATUS_E_NOSUPPORT; 388 389 cm_ctx->active_cm_id = *cm_id; 390 cm_req = cm_get_req_by_cm_id(cm_ctx, *cm_id); 391 if (!cm_req) { 392 /* 393 * Remove the command from serialization active queue, if 394 * disconnect req was not found, to avoid active cmd timeout. 395 * This can happen if a thread tried to flush the pending 396 * disconnect request and while doing so, it removed the 397 * CM pending request, but before it tried to remove pending 398 * command from serialization, the command becomes active in 399 * another thread. 400 */ 401 cm_remove_cmd_from_serialization(cm_ctx, *cm_id); 402 return QDF_STATUS_E_INVAL; 403 } 404 405 if (wlan_vdev_mlme_get_opmode(cm_ctx->vdev) == QDF_STA_MODE) 406 status = mlme_cm_rso_stop_req(cm_ctx->vdev); 407 408 if (status != QDF_STATUS_E_NOSUPPORT) 409 return status; 410 411 req = qdf_mem_malloc(sizeof(*req)); 412 if (!req) 413 return QDF_STATUS_E_NOMEM; 414 415 req->cm_id = *cm_id; 416 req->req.vdev_id = wlan_vdev_get_id(cm_ctx->vdev); 417 req->req.source = cm_req->discon_req.req.source; 418 req->req.reason_code = cm_req->discon_req.req.reason_code; 419 req->req.is_no_disassoc_disconnect = 420 cm_req->discon_req.req.is_no_disassoc_disconnect; 421 422 cm_disconnect_continue_after_rso_stop(cm_ctx->vdev, req); 423 qdf_mem_free(req); 424 425 return status; 426 } 427 428 QDF_STATUS 429 cm_disconnect_continue_after_rso_stop(struct wlan_objmgr_vdev *vdev, 430 struct wlan_cm_vdev_discon_req *req) 431 { 432 struct cm_req *cm_req; 433 QDF_STATUS status; 434 struct qdf_mac_addr bssid = QDF_MAC_ADDR_ZERO_INIT; 435 struct cnx_mgr *cm_ctx = cm_get_cm_ctx(vdev); 436 437 if (!cm_ctx) 438 return QDF_STATUS_E_INVAL; 439 440 cm_req = cm_get_req_by_cm_id(cm_ctx, req->cm_id); 441 if (!cm_req) 442 return QDF_STATUS_E_INVAL; 443 444 wlan_vdev_get_bss_peer_mac(cm_ctx->vdev, &bssid); 445 446 qdf_copy_macaddr(&req->req.bssid, &bssid); 447 /* 448 * for northbound req, bssid is not provided so update it from vdev 449 * in case bssid is not present 450 */ 451 if (qdf_is_macaddr_zero(&cm_req->discon_req.req.bssid) || 452 qdf_is_macaddr_broadcast(&cm_req->discon_req.req.bssid)) 453 qdf_copy_macaddr(&cm_req->discon_req.req.bssid, 454 &req->req.bssid); 455 cm_update_scan_mlme_on_disconnect(cm_ctx->vdev, 456 &cm_req->discon_req); 457 458 mlme_debug(CM_PREFIX_FMT "disconnect " QDF_MAC_ADDR_FMT 459 " source %d reason %d", 460 CM_PREFIX_REF(req->req.vdev_id, req->cm_id), 461 QDF_MAC_ADDR_REF(req->req.bssid.bytes), 462 req->req.source, req->req.reason_code); 463 464 status = mlme_cm_disconnect_req(cm_ctx->vdev, req); 465 if (QDF_IS_STATUS_ERROR(status)) { 466 mlme_err(CM_PREFIX_FMT "disconnect req fail", 467 CM_PREFIX_REF(req->req.vdev_id, req->cm_id)); 468 cm_send_disconnect_resp(cm_ctx, req->cm_id); 469 } 470 471 return status; 472 } 473 474 QDF_STATUS 475 cm_handle_rso_stop_rsp(struct wlan_objmgr_vdev *vdev, 476 struct wlan_cm_vdev_discon_req *req) 477 { 478 struct cnx_mgr *cm_ctx = cm_get_cm_ctx(vdev); 479 wlan_cm_id cm_id; 480 481 if (!cm_ctx) 482 return QDF_STATUS_E_INVAL; 483 484 cm_id = cm_ctx->active_cm_id; 485 486 if ((CM_ID_GET_PREFIX(req->cm_id)) != DISCONNECT_REQ_PREFIX || 487 cm_id != req->cm_id) { 488 mlme_err(CM_PREFIX_FMT "active req is not disconnect req", 489 CM_PREFIX_REF(wlan_vdev_get_id(vdev), req->cm_id)); 490 return QDF_STATUS_E_INVAL; 491 } 492 493 return cm_sm_deliver_event(vdev, WLAN_CM_SM_EV_RSO_STOP_RSP, 494 sizeof(*req), req); 495 } 496 497 #ifdef CONN_MGR_ADV_FEATURE 498 static void 499 cm_inform_dlm_disconnect_complete(struct wlan_objmgr_vdev *vdev, 500 struct wlan_cm_discon_rsp *resp) 501 { 502 struct wlan_objmgr_pdev *pdev; 503 504 pdev = wlan_vdev_get_pdev(vdev); 505 if (!pdev) { 506 mlme_err(CM_PREFIX_FMT "failed to find pdev", 507 CM_PREFIX_REF(wlan_vdev_get_id(vdev), 508 resp->req.cm_id)); 509 return; 510 } 511 512 wlan_dlm_update_bssid_connect_params(pdev, resp->req.req.bssid, 513 DLM_AP_DISCONNECTED); 514 } 515 516 #else 517 static inline void 518 cm_inform_dlm_disconnect_complete(struct wlan_objmgr_vdev *vdev, 519 struct wlan_cm_discon_rsp *resp) 520 {} 521 #endif 522 523 #ifdef WLAN_FEATURE_11BE_MLO 524 #ifdef WLAN_FEATURE_11BE_MLO_ADV_FEATURE 525 static inline void 526 cm_clear_vdev_mlo_cap(struct wlan_objmgr_vdev *vdev, 527 struct wlan_cm_discon_rsp *rsp) 528 { 529 wlan_vdev_mlme_clear_mlo_vdev(vdev); 530 } 531 #else /*WLAN_FEATURE_11BE_MLO_ADV_FEATURE*/ 532 static inline void 533 cm_clear_vdev_mlo_cap(struct wlan_objmgr_vdev *vdev, 534 struct wlan_cm_discon_rsp *rsp) 535 { 536 if (mlo_is_mld_sta(vdev) && ucfg_mlo_is_mld_disconnected(vdev)) 537 ucfg_mlo_mld_clear_mlo_cap(vdev); 538 if (rsp->req.req.reason_code == REASON_HOST_TRIGGERED_LINK_DELETE) { 539 wlan_vdev_mlme_clear_mlo_vdev(vdev); 540 wlan_vdev_mlme_clear_mlo_link_vdev(vdev); 541 } 542 } 543 #endif /*WLAN_FEATURE_11BE_MLO_ADV_FEATURE*/ 544 #else /*WLAN_FEATURE_11BE_MLO*/ 545 static inline void 546 cm_clear_vdev_mlo_cap(struct wlan_objmgr_vdev *vdev, 547 struct wlan_cm_discon_rsp *rsp) 548 { } 549 #endif /*WLAN_FEATURE_11BE_MLO*/ 550 551 QDF_STATUS cm_notify_disconnect_complete(struct cnx_mgr *cm_ctx, 552 struct wlan_cm_discon_rsp *resp) 553 { 554 mlme_cm_disconnect_complete_ind(cm_ctx->vdev, resp); 555 mlo_sta_link_disconn_notify(cm_ctx->vdev, resp); 556 mlme_cm_osif_disconnect_complete(cm_ctx->vdev, resp); 557 cm_if_mgr_inform_disconnect_complete(cm_ctx->vdev); 558 cm_inform_dlm_disconnect_complete(cm_ctx->vdev, resp); 559 560 return QDF_STATUS_SUCCESS; 561 } 562 563 QDF_STATUS cm_disconnect_complete(struct cnx_mgr *cm_ctx, 564 struct wlan_cm_discon_rsp *resp) 565 { 566 /* 567 * If the entry is not present in the list, it must have been cleared 568 * already. 569 */ 570 if (!cm_get_req_by_cm_id(cm_ctx, resp->req.cm_id)) 571 return QDF_STATUS_SUCCESS; 572 573 cm_notify_disconnect_complete(cm_ctx, resp); 574 575 /* 576 * Remove all pending disconnect if this is an active disconnect 577 * complete. 578 */ 579 if (resp->req.cm_id == cm_ctx->active_cm_id) 580 cm_flush_pending_request(cm_ctx, DISCONNECT_REQ_PREFIX, false); 581 582 cm_remove_cmd(cm_ctx, &resp->req.cm_id); 583 mlme_debug(CM_PREFIX_FMT "disconnect count %d connect count %d", 584 CM_PREFIX_REF(wlan_vdev_get_id(cm_ctx->vdev), 585 resp->req.cm_id), 586 cm_ctx->disconnect_count, cm_ctx->connect_count); 587 /* Flush failed connect req as pending disconnect is completed */ 588 if (!cm_ctx->disconnect_count && cm_ctx->connect_count) 589 cm_flush_pending_request(cm_ctx, CONNECT_REQ_PREFIX, true); 590 591 /* Set the disconnect wait event once all disconnect are completed */ 592 if (!cm_ctx->disconnect_count) { 593 /* 594 * Clear MLO cap only when it is the last disconnect req 595 * For 1x/owe roaming, link vdev mlo flags are not cleared 596 * as connect req is queued on link vdev after this. 597 */ 598 if (!wlan_cm_check_mlo_roam_auth_status(cm_ctx->vdev)) 599 cm_clear_vdev_mlo_cap(cm_ctx->vdev, resp); 600 qdf_event_set(&cm_ctx->disconnect_complete); 601 } 602 603 return QDF_STATUS_SUCCESS; 604 } 605 606 QDF_STATUS 607 cm_handle_discon_req_in_non_connected_state(struct cnx_mgr *cm_ctx, 608 struct cm_disconnect_req *cm_req, 609 enum wlan_cm_sm_state cm_state_substate) 610 { 611 enum wlan_cm_sm_state cur_state = cm_get_state(cm_ctx); 612 613 /* 614 * South bound and peer disconnect requests are meant for only in 615 * connected state, so if the state is connecting a new connect has 616 * been received, hence skip the non-osif disconnect request. Also allow 617 * MLO link vdev disconnect in connecting state, as this can be 618 * initiated due to disconnect on assoc vdev, which may be in connected 619 * state. 620 */ 621 if (cur_state == WLAN_CM_S_CONNECTING && 622 (cm_req->req.source != CM_OSIF_DISCONNECT && 623 cm_req->req.source != CM_OSIF_CFG_DISCONNECT && 624 cm_req->req.source != CM_MLO_LINK_VDEV_DISCONNECT)) { 625 mlme_info("Vdev %d ignore disconnect req from source %d in state %d", 626 wlan_vdev_get_id(cm_ctx->vdev), cm_req->req.source, 627 cm_state_substate); 628 return QDF_STATUS_E_INVAL; 629 } 630 631 switch (cm_state_substate) { 632 case WLAN_CM_S_DISCONNECTING: 633 /* 634 * There would be pending disconnect requests in the list, and 635 * if they are flushed as part of new disconnect 636 * (cm_flush_pending_request), OS_IF would inform the kernel 637 * about the disconnect done even though the disconnect is still 638 * pending. So update OS_IF with invalid CM_ID so that the resp 639 * of only the new disconnect req is given to kernel. 640 */ 641 mlme_cm_osif_update_id_and_src(cm_ctx->vdev, 642 CM_SOURCE_INVALID, 643 CM_ID_INVALID); 644 cm_flush_pending_request(cm_ctx, DISCONNECT_REQ_PREFIX, false); 645 /* 646 * Flush failed pending connect req as new req is received 647 * and its no longer the latest one. 648 */ 649 if (cm_ctx->connect_count) 650 cm_flush_pending_request(cm_ctx, CONNECT_REQ_PREFIX, 651 true); 652 break; 653 case WLAN_CM_S_ROAMING: 654 /* for FW roam/LFR3 remove the req from the list */ 655 if (cm_roam_offload_enabled(wlan_vdev_get_psoc(cm_ctx->vdev))) 656 cm_flush_pending_request(cm_ctx, ROAM_REQ_PREFIX, 657 false); 658 fallthrough; 659 case WLAN_CM_SS_JOIN_ACTIVE: 660 /* 661 * In join active/roaming state, there would be no pending 662 * command, so no action required. so for new disconnect 663 * request, queue disconnect and move the state to 664 * disconnecting. 665 */ 666 break; 667 case WLAN_CM_SS_SCAN: 668 /* In the scan state abort the ongoing scan */ 669 cm_vdev_scan_cancel(wlan_vdev_get_pdev(cm_ctx->vdev), 670 cm_ctx->vdev); 671 fallthrough; 672 case WLAN_CM_SS_JOIN_PENDING: 673 /* 674 * There would be pending disconnect requests in the list, and 675 * if they are flushed as part of new disconnect 676 * (cm_flush_pending_request), OS_IF would inform the kernel 677 * about the disconnect done even though the disconnect is still 678 * pending. So update OS_IF with invalid CM_ID so that the resp 679 * of only the new disconnect req is given to kernel. 680 */ 681 mlme_cm_osif_update_id_and_src(cm_ctx->vdev, 682 CM_SOURCE_INVALID, 683 CM_ID_INVALID); 684 /* 685 * In case of scan or join pending there could be a connect and 686 * disconnect requests pending, so flush all the requests except 687 * the activated request. 688 */ 689 cm_flush_pending_request(cm_ctx, CONNECT_REQ_PREFIX, false); 690 cm_flush_pending_request(cm_ctx, DISCONNECT_REQ_PREFIX, false); 691 break; 692 case WLAN_CM_S_INIT: 693 /* 694 * In this case the vdev is already disconnected and thus the 695 * indication to upper layer, would have been sent as part of 696 * previous disconnect/connect failure. 697 * 698 * If upper layer is in process of connecting, sending 699 * disconnect indication back again may cause it to incorrectly 700 * think it as a connect failure. So sending disconnect 701 * indication again is not advisable. 702 * 703 * So no need to do anything here, just return failure and drop 704 * disconnect. 705 */ 706 mlme_info("vdev %d dropping disconnect req from source %d in INIT state", 707 wlan_vdev_get_id(cm_ctx->vdev), cm_req->req.source); 708 return QDF_STATUS_E_ALREADY; 709 default: 710 mlme_err("Vdev %d disconnect req in invalid state %d", 711 wlan_vdev_get_id(cm_ctx->vdev), 712 cm_state_substate); 713 return QDF_STATUS_E_FAILURE; 714 }; 715 716 /* Queue the new disconnect request after state specific actions */ 717 return cm_add_disconnect_req_to_list(cm_ctx, cm_req); 718 } 719 720 QDF_STATUS cm_add_disconnect_req_to_list(struct cnx_mgr *cm_ctx, 721 struct cm_disconnect_req *req) 722 { 723 QDF_STATUS status; 724 struct cm_req *cm_req; 725 726 cm_req = qdf_container_of(req, struct cm_req, discon_req); 727 req->cm_id = cm_get_cm_id(cm_ctx, req->req.source); 728 cm_req->cm_id = req->cm_id; 729 status = cm_add_req_to_list_and_indicate_osif(cm_ctx, cm_req, 730 req->req.source); 731 732 return status; 733 } 734 735 QDF_STATUS cm_disconnect_start_req(struct wlan_objmgr_vdev *vdev, 736 struct wlan_cm_disconnect_req *req) 737 { 738 struct cnx_mgr *cm_ctx; 739 struct cm_req *cm_req; 740 struct cm_disconnect_req *disconnect_req; 741 QDF_STATUS status; 742 743 cm_ctx = cm_get_cm_ctx(vdev); 744 if (!cm_ctx) 745 return QDF_STATUS_E_INVAL; 746 747 /* 748 * This would be freed as part of removal from cm req list if adding 749 * to list is success after posting WLAN_CM_SM_EV_DISCONNECT_REQ. 750 */ 751 cm_req = qdf_mem_malloc(sizeof(*cm_req)); 752 753 if (!cm_req) 754 return QDF_STATUS_E_NOMEM; 755 756 if (wlan_vdev_mlme_is_mlo_vdev(vdev) && 757 !wlan_vdev_mlme_is_mlo_link_vdev(vdev)) 758 req->is_no_disassoc_disconnect = 1; 759 760 disconnect_req = &cm_req->discon_req; 761 disconnect_req->req = *req; 762 763 status = cm_sm_deliver_event(vdev, WLAN_CM_SM_EV_DISCONNECT_REQ, 764 sizeof(*disconnect_req), disconnect_req); 765 /* free the req if disconnect is not handled */ 766 if (QDF_IS_STATUS_ERROR(status)) 767 qdf_mem_free(cm_req); 768 769 return status; 770 } 771 772 QDF_STATUS cm_disconnect_start_req_sync(struct wlan_objmgr_vdev *vdev, 773 struct wlan_cm_disconnect_req *req) 774 { 775 struct cnx_mgr *cm_ctx; 776 QDF_STATUS status; 777 778 cm_ctx = cm_get_cm_ctx(vdev); 779 if (!cm_ctx) 780 return QDF_STATUS_E_INVAL; 781 782 if (wlan_vdev_mlme_is_mlo_vdev(vdev) && 783 !wlan_vdev_mlme_is_mlo_link_vdev(vdev)) 784 req->is_no_disassoc_disconnect = 1; 785 786 qdf_event_reset(&cm_ctx->disconnect_complete); 787 status = cm_disconnect_start_req(vdev, req); 788 if (QDF_IS_STATUS_ERROR(status)) { 789 mlme_err("Disconnect failed with status %d", status); 790 return status; 791 } 792 793 status = qdf_wait_single_event(&cm_ctx->disconnect_complete, 794 CM_DISCONNECT_CMD_TIMEOUT); 795 if (QDF_IS_STATUS_ERROR(status)) 796 mlme_err("Disconnect timeout with status %d", status); 797 798 return status; 799 } 800 801 QDF_STATUS cm_disconnect_rsp(struct wlan_objmgr_vdev *vdev, 802 struct wlan_cm_discon_rsp *resp) 803 { 804 struct cnx_mgr *cm_ctx; 805 QDF_STATUS qdf_status; 806 wlan_cm_id cm_id; 807 uint32_t prefix; 808 809 cm_ctx = cm_get_cm_ctx(vdev); 810 if (!cm_ctx) 811 return QDF_STATUS_E_INVAL; 812 813 cm_id = cm_ctx->active_cm_id; 814 prefix = CM_ID_GET_PREFIX(cm_id); 815 816 if (prefix != DISCONNECT_REQ_PREFIX || cm_id != resp->req.cm_id) { 817 mlme_err(CM_PREFIX_FMT "Active cm_id 0x%x is different", 818 CM_PREFIX_REF(wlan_vdev_get_id(vdev), resp->req.cm_id), 819 cm_id); 820 qdf_status = QDF_STATUS_E_FAILURE; 821 goto disconnect_complete; 822 } 823 qdf_status = 824 cm_sm_deliver_event(vdev, 825 WLAN_CM_SM_EV_DISCONNECT_DONE, 826 sizeof(*resp), resp); 827 if (QDF_IS_STATUS_ERROR(qdf_status)) 828 goto disconnect_complete; 829 830 return qdf_status; 831 832 disconnect_complete: 833 /* 834 * If there is a event posting error it means the SM state is not in 835 * DISCONNECTING (some new cmd has changed the state of SM), so just 836 * complete the disconnect command. 837 */ 838 return cm_disconnect_complete(cm_ctx, resp); 839 } 840 841 QDF_STATUS cm_bss_peer_delete_req(struct wlan_objmgr_vdev *vdev, 842 struct qdf_mac_addr *peer_mac) 843 { 844 mlme_debug("vdev-id %d, delete peer" QDF_MAC_ADDR_FMT, 845 wlan_vdev_get_id(vdev), QDF_MAC_ADDR_REF(peer_mac->bytes)); 846 847 return mlme_cm_bss_peer_delete_req(vdev); 848 } 849 850 QDF_STATUS cm_vdev_down_req(struct wlan_objmgr_vdev *vdev, uint32_t status) 851 { 852 mlme_debug("vdev id %d down req status %d", 853 wlan_vdev_get_id(vdev), status); 854 855 return mlme_cm_vdev_down_req(vdev); 856 } 857