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