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