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