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