1 /* 2 * Copyright (c) 2012-2015, 2020, The Linux Foundation. All rights reserved. 3 * 4 * Permission to use, copy, modify, and/or distribute this software for any 5 * purpose with or without fee is hereby granted, provided that the above 6 * copyright notice and this permission notice appear in all copies. 7 * 8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 */ 16 17 /** 18 * DOC: Implements disconnect specific apis of connection manager 19 */ 20 #include "wlan_cm_main_api.h" 21 #include "wlan_cm_sm.h" 22 #include <wlan_serialization_api.h> 23 #include "wlan_utility.h" 24 #include "wlan_scan_api.h" 25 #include "wlan_crypto_global_api.h" 26 #ifdef CONN_MGR_ADV_FEATURE 27 #include "wlan_blm_api.h" 28 #endif 29 30 void cm_send_disconnect_resp(struct cnx_mgr *cm_ctx, wlan_cm_id cm_id) 31 { 32 struct wlan_cm_discon_rsp resp; 33 QDF_STATUS status; 34 35 qdf_mem_zero(&resp, sizeof(resp)); 36 status = cm_fill_disconnect_resp_from_cm_id(cm_ctx, cm_id, &resp); 37 if (QDF_IS_STATUS_SUCCESS(status)) 38 cm_disconnect_complete(cm_ctx, &resp); 39 } 40 41 #ifdef WLAN_CM_USE_SPINLOCK 42 static QDF_STATUS cm_activate_disconnect_req_flush_cb(struct scheduler_msg *msg) 43 { 44 struct wlan_serialization_command *cmd = msg->bodyptr; 45 46 if (!cmd || !cmd->vdev) { 47 mlme_err("Null input cmd:%pK", cmd); 48 return QDF_STATUS_E_INVAL; 49 } 50 51 wlan_objmgr_vdev_release_ref(cmd->vdev, WLAN_MLME_CM_ID); 52 return QDF_STATUS_SUCCESS; 53 } 54 55 static QDF_STATUS cm_activate_disconnect_req_sched_cb(struct scheduler_msg *msg) 56 { 57 struct wlan_serialization_command *cmd = msg->bodyptr; 58 struct wlan_objmgr_vdev *vdev; 59 struct cnx_mgr *cm_ctx; 60 QDF_STATUS ret = QDF_STATUS_E_FAILURE; 61 62 if (!cmd) { 63 mlme_err("cmd is null"); 64 return QDF_STATUS_E_INVAL; 65 } 66 67 vdev = cmd->vdev; 68 if (!vdev) { 69 mlme_err("vdev is null"); 70 return QDF_STATUS_E_INVAL; 71 } 72 73 cm_ctx = cm_get_cm_ctx(vdev); 74 if (!cm_ctx) 75 return QDF_STATUS_E_INVAL; 76 77 ret = cm_sm_deliver_event( 78 cm_ctx->vdev, 79 WLAN_CM_SM_EV_DISCONNECT_ACTIVE, 80 sizeof(wlan_cm_id), 81 &cmd->cmd_id); 82 83 /* 84 * Called from scheduler context hence 85 * handle failure if posting fails 86 */ 87 if (QDF_IS_STATUS_ERROR(ret)) { 88 mlme_err(CM_PREFIX_FMT "Activation failed for cmd:%d", 89 CM_PREFIX_REF(wlan_vdev_get_id(vdev), cmd->cmd_id), 90 cmd->cmd_type); 91 cm_send_disconnect_resp(cm_ctx, cmd->cmd_id); 92 } 93 94 wlan_objmgr_vdev_release_ref(vdev, WLAN_MLME_CM_ID); 95 return ret; 96 } 97 98 static QDF_STATUS 99 cm_activate_disconnect_req(struct wlan_serialization_command *cmd) 100 { 101 struct wlan_objmgr_vdev *vdev = cmd->vdev; 102 struct scheduler_msg msg = {0}; 103 QDF_STATUS ret; 104 105 msg.bodyptr = cmd; 106 msg.callback = cm_activate_disconnect_req_sched_cb; 107 msg.flush_callback = cm_activate_disconnect_req_flush_cb; 108 109 ret = wlan_objmgr_vdev_try_get_ref(vdev, WLAN_MLME_CM_ID); 110 if (QDF_IS_STATUS_ERROR(ret)) 111 return ret; 112 113 ret = scheduler_post_message(QDF_MODULE_ID_MLME, 114 QDF_MODULE_ID_MLME, 115 QDF_MODULE_ID_MLME, &msg); 116 117 if (QDF_IS_STATUS_ERROR(ret)) { 118 mlme_err(CM_PREFIX_FMT "Failed to post scheduler_msg", 119 CM_PREFIX_REF(wlan_vdev_get_id(vdev), cmd->cmd_id)); 120 wlan_objmgr_vdev_release_ref(vdev, WLAN_MLME_CM_ID); 121 return ret; 122 } 123 mlme_debug(CM_PREFIX_FMT "Cmd act in sched cmd type:%d", 124 CM_PREFIX_REF(wlan_vdev_get_id(vdev), cmd->cmd_type); 125 126 return ret; 127 } 128 #else 129 static QDF_STATUS 130 cm_activate_disconnect_req(struct wlan_serialization_command *cmd) 131 { 132 return cm_sm_deliver_event( 133 cm_ctx->vdev, 134 WLAN_CM_SM_EV_DISCONNECT_ACTIVE, 135 sizeof(wlan_cm_id), 136 &cmd->cmd_id); 137 } 138 #endif 139 140 static QDF_STATUS 141 cm_sm_deliver_disconnect_event(struct cnx_mgr *cm_ctx, 142 struct wlan_serialization_command *cmd) 143 { 144 /* 145 * For pending to active, use async cmnd to take lock. 146 * Use sync command for direct activation as lock is already 147 * acquired. 148 */ 149 if (cmd->activation_reason == SER_PENDING_TO_ACTIVE) 150 return cm_activate_disconnect_req(cmd); 151 else 152 return cm_sm_deliver_event_sync( 153 cm_ctx, 154 WLAN_CM_SM_EV_DISCONNECT_ACTIVE, 155 sizeof(wlan_cm_id), 156 &cmd->cmd_id); 157 } 158 159 static QDF_STATUS 160 cm_ser_disconnect_cb(struct wlan_serialization_command *cmd, 161 enum wlan_serialization_cb_reason reason) 162 { 163 QDF_STATUS status = QDF_STATUS_SUCCESS; 164 struct wlan_objmgr_vdev *vdev; 165 struct cnx_mgr *cm_ctx; 166 167 if (!cmd) { 168 mlme_err("cmd is NULL, reason: %d", reason); 169 QDF_ASSERT(0); 170 return QDF_STATUS_E_NULL_VALUE; 171 } 172 173 vdev = cmd->vdev; 174 175 cm_ctx = cm_get_cm_ctx(vdev); 176 if (!cm_ctx) 177 return QDF_STATUS_E_NULL_VALUE; 178 179 switch (reason) { 180 case WLAN_SER_CB_ACTIVATE_CMD: 181 status = cm_sm_deliver_disconnect_event(cm_ctx, cmd); 182 if (QDF_IS_STATUS_SUCCESS(status)) 183 break; 184 /* 185 * Handle failure if posting fails, i.e. the SM state has 186 * changes. Disconnect should be handled in JOIN_PENDING, 187 * JOIN-SCAN state as well apart from DISCONNECTING. 188 * Also no need to check for head list as diconnect needs to be 189 * completed always once active. 190 */ 191 192 cm_send_disconnect_resp(cm_ctx, cmd->cmd_id); 193 break; 194 case WLAN_SER_CB_CANCEL_CMD: 195 /* command removed from pending list. */ 196 break; 197 case WLAN_SER_CB_ACTIVE_CMD_TIMEOUT: 198 mlme_err(CM_PREFIX_FMT "Active command timeout", 199 CM_PREFIX_REF(wlan_vdev_get_id(vdev), cmd->cmd_id)); 200 QDF_ASSERT(0); 201 cm_send_disconnect_resp(cm_ctx, cmd->cmd_id); 202 break; 203 case WLAN_SER_CB_RELEASE_MEM_CMD: 204 cm_reset_active_cm_id(vdev, cmd->cmd_id); 205 wlan_objmgr_vdev_release_ref(vdev, WLAN_MLME_CM_ID); 206 break; 207 default: 208 QDF_ASSERT(0); 209 status = QDF_STATUS_E_INVAL; 210 break; 211 } 212 213 return status; 214 } 215 216 static QDF_STATUS cm_ser_disconnect_req(struct wlan_objmgr_pdev *pdev, 217 struct cnx_mgr *cm_ctx, 218 struct cm_disconnect_req *req) 219 { 220 struct wlan_serialization_command cmd = {0, }; 221 enum wlan_serialization_status ser_cmd_status; 222 QDF_STATUS status; 223 uint8_t vdev_id = wlan_vdev_get_id(cm_ctx->vdev); 224 225 status = wlan_objmgr_vdev_try_get_ref(cm_ctx->vdev, WLAN_MLME_CM_ID); 226 if (QDF_IS_STATUS_ERROR(status)) { 227 mlme_err(CM_PREFIX_FMT "unable to get reference", 228 CM_PREFIX_REF(vdev_id, req->cm_id)); 229 return status; 230 } 231 232 cmd.cmd_type = WLAN_SER_CMD_VDEV_DISCONNECT; 233 cmd.cmd_id = req->cm_id; 234 cmd.cmd_cb = cm_ser_disconnect_cb; 235 cmd.source = WLAN_UMAC_COMP_MLME; 236 cmd.is_high_priority = false; 237 cmd.cmd_timeout_duration = DISCONNECT_TIMEOUT; 238 cmd.vdev = cm_ctx->vdev; 239 cmd.is_blocking = cm_ser_get_blocking_cmd(); 240 241 ser_cmd_status = wlan_serialization_request(&cmd); 242 switch (ser_cmd_status) { 243 case WLAN_SER_CMD_PENDING: 244 /* command moved to pending list.Do nothing */ 245 break; 246 case WLAN_SER_CMD_ACTIVE: 247 /* command moved to active list. Do nothing */ 248 break; 249 default: 250 mlme_err(CM_PREFIX_FMT "ser cmd status %d", 251 CM_PREFIX_REF(vdev_id, req->cm_id), ser_cmd_status); 252 wlan_objmgr_vdev_release_ref(cm_ctx->vdev, WLAN_MLME_CM_ID); 253 254 return QDF_STATUS_E_FAILURE; 255 } 256 257 return QDF_STATUS_SUCCESS; 258 } 259 260 #ifdef WLAN_FEATURE_INTERFACE_MGR 261 static void 262 cm_inform_if_mgr_disconnect_complete(struct wlan_objmgr_vdev *vdev) 263 { 264 struct if_mgr_event_data *disconnect_complete; 265 266 disconnect_complete = qdf_mem_malloc(sizeof(*disconnect_complete)); 267 if (!disconnect_complete) 268 return; 269 270 disconnect_complete->status = QDF_STATUS_SUCCESS; 271 272 if_mgr_deliver_event(vdev, WLAN_IF_MGR_EV_DISCONNECT_COMPLETE, 273 disconnect_complete); 274 qdf_mem_free(disconnect_complete); 275 } 276 277 static void 278 cm_inform_if_mgr_disconnect_start(struct wlan_objmgr_vdev *vdev) 279 { 280 struct if_mgr_event_data *disconnect_start; 281 282 disconnect_start = qdf_mem_malloc(sizeof(*disconnect_start)); 283 if (!disconnect_start) 284 return; 285 286 disconnect_start->status = QDF_STATUS_SUCCESS; 287 288 if_mgr_deliver_event(vdev, WLAN_IF_MGR_EV_DISCONNECT_START, 289 disconnect_start); 290 qdf_mem_free(disconnect_start); 291 } 292 293 #else 294 static inline void 295 cm_inform_if_mgr_disconnect_complete(struct wlan_objmgr_vdev *vdev) 296 { 297 } 298 299 static inline void 300 cm_inform_if_mgr_disconnect_start(struct wlan_objmgr_vdev *vdev) 301 { 302 } 303 #endif 304 305 void cm_initiate_internal_disconnect(struct cnx_mgr *cm_ctx) 306 { 307 struct cm_req *cm_req; 308 struct cm_disconnect_req *disconnect_req; 309 QDF_STATUS status; 310 311 cm_req = qdf_mem_malloc(sizeof(*cm_req)); 312 313 if (!cm_req) 314 return; 315 316 disconnect_req = &cm_req->discon_req; 317 disconnect_req->req.vdev_id = wlan_vdev_get_id(cm_ctx->vdev); 318 disconnect_req->req.source = CM_INTERNAL_DISCONNECT; 319 320 status = cm_add_disconnect_req_to_list(cm_ctx, disconnect_req); 321 if (QDF_IS_STATUS_ERROR(status)) { 322 mlme_err(CM_PREFIX_FMT "failed to add disconnect req", 323 CM_PREFIX_REF(disconnect_req->req.vdev_id, 324 disconnect_req->cm_id)); 325 qdf_mem_free(cm_req); 326 return; 327 } 328 329 cm_disconnect_start(cm_ctx, disconnect_req); 330 } 331 332 QDF_STATUS cm_disconnect_start(struct cnx_mgr *cm_ctx, 333 struct cm_disconnect_req *req) 334 { 335 struct wlan_objmgr_pdev *pdev; 336 QDF_STATUS status; 337 338 pdev = wlan_vdev_get_pdev(cm_ctx->vdev); 339 if (!pdev) { 340 cm_send_disconnect_resp(cm_ctx, req->cm_id); 341 return QDF_STATUS_E_INVAL; 342 } 343 344 /* disconnect TDLS, P2P roc cleanup */ 345 cm_inform_if_mgr_disconnect_start(cm_ctx->vdev); 346 cm_vdev_scan_cancel(wlan_vdev_get_pdev(cm_ctx->vdev), cm_ctx->vdev); 347 mlme_cm_osif_disconnect_start_ind(cm_ctx->vdev); 348 349 /* Serialize disconnect req, Handle failure status */ 350 status = cm_ser_disconnect_req(pdev, cm_ctx, req); 351 352 if (QDF_IS_STATUS_ERROR(status)) 353 cm_send_disconnect_resp(cm_ctx, req->cm_id); 354 355 return QDF_STATUS_SUCCESS; 356 } 357 358 static void 359 cm_update_scan_mlme_on_disconnect(struct wlan_objmgr_vdev *vdev, 360 struct cm_disconnect_req *req) 361 { 362 struct wlan_objmgr_pdev *pdev; 363 struct bss_info bss_info; 364 struct mlme_info mlme; 365 struct wlan_channel *chan; 366 QDF_STATUS status; 367 368 pdev = wlan_vdev_get_pdev(vdev); 369 if (!pdev) { 370 mlme_err(CM_PREFIX_FMT "failed to find pdev", 371 CM_PREFIX_REF(req->req.vdev_id, req->cm_id)); 372 return; 373 } 374 375 chan = wlan_vdev_get_active_channel(vdev); 376 if (!chan) { 377 mlme_err(CM_PREFIX_FMT "failed to get active channel", 378 CM_PREFIX_REF(req->req.vdev_id, req->cm_id)); 379 return; 380 } 381 382 status = wlan_vdev_mlme_get_ssid(vdev, bss_info.ssid.ssid, 383 &bss_info.ssid.length); 384 385 if (QDF_IS_STATUS_ERROR(status)) { 386 mlme_err(CM_PREFIX_FMT "failed to get ssid", 387 CM_PREFIX_REF(req->req.vdev_id, req->cm_id)); 388 return; 389 } 390 391 mlme.assoc_state = SCAN_ENTRY_CON_STATE_NONE; 392 qdf_copy_macaddr(&bss_info.bssid, &req->req.bssid); 393 394 bss_info.freq = chan->ch_freq; 395 396 wlan_scan_update_mlme_by_bssinfo(pdev, &bss_info, &mlme); 397 } 398 399 QDF_STATUS cm_disconnect_active(struct cnx_mgr *cm_ctx, wlan_cm_id *cm_id) 400 { 401 struct wlan_cm_vdev_discon_req *req; 402 struct cm_req *cm_req; 403 struct qdf_mac_addr bssid; 404 QDF_STATUS status; 405 406 cm_req = cm_get_req_by_cm_id(cm_ctx, *cm_id); 407 if (!cm_req) 408 return QDF_STATUS_E_INVAL; 409 410 req = qdf_mem_malloc(sizeof(*req)); 411 if (!req) 412 return QDF_STATUS_E_NOMEM; 413 414 cm_ctx->active_cm_id = *cm_id; 415 416 wlan_vdev_get_bss_peer_mac(cm_ctx->vdev, &bssid); 417 qdf_copy_macaddr(&req->req.bssid, &bssid); 418 419 req->cm_id = *cm_id; 420 req->req.vdev_id = wlan_vdev_get_id(cm_ctx->vdev); 421 req->req.source = cm_req->discon_req.req.source; 422 req->req.reason_code = cm_req->discon_req.req.reason_code; 423 424 cm_update_scan_mlme_on_disconnect(cm_ctx->vdev, 425 &cm_req->discon_req); 426 427 mlme_debug(CM_PREFIX_FMT "disconnect " QDF_MAC_ADDR_FMT " source %d reason %d", 428 CM_PREFIX_REF(req->req.vdev_id, req->cm_id), 429 QDF_MAC_ADDR_REF(req->req.bssid.bytes), 430 req->req.source, req->req.reason_code); 431 status = mlme_cm_disconnect_req(cm_ctx->vdev, req); 432 if (QDF_IS_STATUS_ERROR(status)) { 433 mlme_err(CM_PREFIX_FMT "disconnect req fail", 434 CM_PREFIX_REF(req->req.vdev_id, req->cm_id)); 435 cm_send_disconnect_resp(cm_ctx, req->cm_id); 436 } 437 qdf_mem_free(req); 438 439 return status; 440 } 441 442 #ifdef CONN_MGR_ADV_FEATURE 443 static void 444 cm_inform_blm_disconnect_complete(struct wlan_objmgr_vdev *vdev, 445 struct wlan_cm_discon_rsp *resp) 446 { 447 struct wlan_objmgr_pdev *pdev; 448 449 pdev = wlan_vdev_get_pdev(vdev); 450 if (!pdev) { 451 mlme_err(CM_PREFIX_FMT "failed to find pdev", 452 CM_PREFIX_REF(wlan_vdev_get_id(vdev), 453 resp->req.cm_id)); 454 return; 455 } 456 457 wlan_blm_update_bssid_connect_params(pdev, resp->req.req.bssid, 458 BLM_AP_DISCONNECTED); 459 } 460 #else 461 static inline void 462 cm_inform_blm_disconnect_complete(struct wlan_objmgr_vdev *vdev, 463 struct wlan_cm_discon_rsp *resp) 464 {} 465 #endif 466 467 QDF_STATUS cm_disconnect_complete(struct cnx_mgr *cm_ctx, 468 struct wlan_cm_discon_rsp *resp) 469 { 470 /* 471 * If the entry is not present in the list, it must have been cleared 472 * already. 473 */ 474 if (!cm_get_req_by_cm_id(cm_ctx, resp->req.cm_id)) 475 return QDF_STATUS_SUCCESS; 476 477 mlme_cm_disconnect_complete_ind(cm_ctx->vdev, resp); 478 mlme_cm_osif_disconnect_complete(cm_ctx->vdev, resp); 479 wlan_crypto_free_vdev_key(cm_ctx->vdev); 480 cm_inform_if_mgr_disconnect_complete(cm_ctx->vdev); 481 cm_inform_blm_disconnect_complete(cm_ctx->vdev, resp); 482 483 /* 484 * Remove all pending disconnect if this is an active disconnect 485 * complete. 486 */ 487 if (resp->req.cm_id == cm_ctx->active_cm_id) 488 cm_flush_pending_request(cm_ctx, DISCONNECT_REQ_PREFIX, false); 489 490 cm_remove_cmd(cm_ctx, resp->req.cm_id); 491 mlme_debug(CM_PREFIX_FMT "disconnect count %d connect count %d", 492 CM_PREFIX_REF(wlan_vdev_get_id(cm_ctx->vdev), 493 resp->req.cm_id), 494 cm_ctx->disconnect_count, cm_ctx->connect_count); 495 /* Flush failed connect req as pending disconnect is completed */ 496 if (!cm_ctx->disconnect_count && cm_ctx->connect_count) 497 cm_flush_pending_request(cm_ctx, CONNECT_REQ_PREFIX, true); 498 499 /* Set the disconnect wait event once all disconnect are completed */ 500 if (!cm_ctx->disconnect_count) 501 qdf_event_set(&cm_ctx->disconnect_complete); 502 503 return QDF_STATUS_SUCCESS; 504 } 505 506 QDF_STATUS 507 cm_handle_discon_req_in_non_connected_state(struct cnx_mgr *cm_ctx, 508 struct cm_disconnect_req *cm_req, 509 enum wlan_cm_sm_state cm_state_substate) 510 { 511 enum wlan_cm_sm_state cur_state = cm_get_state(cm_ctx); 512 513 /* 514 * South bound and peer disconnect requests are meant for only in 515 * connected state, so if the state is connecting a new connect has 516 * been received, hence skip the non-osif disconnect request. 517 */ 518 if (cur_state == WLAN_CM_S_CONNECTING && 519 cm_req->req.source != CM_OSIF_DISCONNECT) { 520 mlme_info("Vdev %d ignore disconnect req from source %d in state %d", 521 wlan_vdev_get_id(cm_ctx->vdev), cm_req->req.source, 522 cm_state_substate); 523 return QDF_STATUS_E_INVAL; 524 } 525 526 switch (cm_state_substate) { 527 case WLAN_CM_S_DISCONNECTING: 528 /* 529 * There would be pending disconnect requests in the list, and 530 * if they are flushed as part of new disconnect 531 * (cm_flush_pending_request), OS_IF would inform the kernel 532 * about the disconnect done even though the disconnect is still 533 * pending. So update OS_IF with invalid CM_ID so that the resp 534 * of only the new disconnect req is given to kernel. 535 */ 536 mlme_cm_osif_update_id_and_src(cm_ctx->vdev, 537 CM_SOURCE_INVALID, 538 CM_ID_INVALID); 539 cm_flush_pending_request(cm_ctx, DISCONNECT_REQ_PREFIX, false); 540 /* 541 * Flush failed pending connect req as new req is received 542 * and its no longer the latest one. 543 */ 544 if (cm_ctx->connect_count) 545 cm_flush_pending_request(cm_ctx, CONNECT_REQ_PREFIX, 546 true); 547 break; 548 case WLAN_CM_SS_JOIN_ACTIVE: 549 /* 550 * In join active state, there would be no pending command, so 551 * for new disconnect request, queue disconnect. 552 * In disconnecting state queue the new disconnect request, and 553 * when the old diconnect moves the SM to init it would be 554 * dropped and required callbacks would be called. 555 */ 556 break; 557 case WLAN_CM_SS_SCAN: 558 /* In the scan state abort the ongoing scan */ 559 cm_vdev_scan_cancel(wlan_vdev_get_pdev(cm_ctx->vdev), 560 cm_ctx->vdev); 561 /* fallthrough */ 562 case WLAN_CM_SS_JOIN_PENDING: 563 /* 564 * There would be pending disconnect requests in the list, and 565 * if they are flushed as part of new disconnect 566 * (cm_flush_pending_request), OS_IF would inform the kernel 567 * about the disconnect done even though the disconnect is still 568 * pending. So update OS_IF with invalid CM_ID so that the resp 569 * of only the new disconnect req is given to kernel. 570 */ 571 mlme_cm_osif_update_id_and_src(cm_ctx->vdev, 572 CM_SOURCE_INVALID, 573 CM_ID_INVALID); 574 /* 575 * In case of scan or join pending there could be a connect and 576 * disconnect requests pending, so flush all the requests except 577 * the activated request. 578 */ 579 cm_flush_pending_request(cm_ctx, CONNECT_REQ_PREFIX, false); 580 cm_flush_pending_request(cm_ctx, DISCONNECT_REQ_PREFIX, false); 581 break; 582 default: 583 mlme_err("Vdev %d disconnect req in invalid state %d", 584 wlan_vdev_get_id(cm_ctx->vdev), 585 cm_state_substate); 586 return QDF_STATUS_E_FAILURE; 587 }; 588 589 /* Queue the new disconnect request after state specific actions */ 590 return cm_add_disconnect_req_to_list(cm_ctx, cm_req); 591 } 592 593 QDF_STATUS cm_add_disconnect_req_to_list(struct cnx_mgr *cm_ctx, 594 struct cm_disconnect_req *req) 595 { 596 QDF_STATUS status; 597 struct cm_req *cm_req; 598 599 cm_req = qdf_container_of(req, struct cm_req, discon_req); 600 req->cm_id = cm_get_cm_id(cm_ctx, req->req.source); 601 cm_req->cm_id = req->cm_id; 602 status = cm_add_req_to_list_and_indicate_osif(cm_ctx, cm_req, 603 req->req.source); 604 605 return status; 606 } 607 608 QDF_STATUS cm_disconnect_start_req(struct wlan_objmgr_vdev *vdev, 609 struct wlan_cm_disconnect_req *req) 610 { 611 struct cnx_mgr *cm_ctx; 612 struct cm_req *cm_req; 613 struct cm_disconnect_req *disconnect_req; 614 QDF_STATUS status; 615 616 cm_ctx = cm_get_cm_ctx(vdev); 617 if (!cm_ctx) 618 return QDF_STATUS_E_INVAL; 619 620 /* 621 * This would be freed as part of removal from cm req list if adding 622 * to list is success after posting WLAN_CM_SM_EV_DISCONNECT_REQ. 623 */ 624 cm_req = qdf_mem_malloc(sizeof(*cm_req)); 625 626 if (!cm_req) 627 return QDF_STATUS_E_NOMEM; 628 629 disconnect_req = &cm_req->discon_req; 630 disconnect_req->req = *req; 631 632 status = cm_sm_deliver_event(vdev, WLAN_CM_SM_EV_DISCONNECT_REQ, 633 sizeof(*disconnect_req), disconnect_req); 634 /* free the req if disconnect is not handled */ 635 if (QDF_IS_STATUS_ERROR(status)) 636 qdf_mem_free(cm_req); 637 638 return status; 639 } 640 641 QDF_STATUS cm_disconnect_start_req_sync(struct wlan_objmgr_vdev *vdev, 642 struct wlan_cm_disconnect_req *req) 643 { 644 struct cnx_mgr *cm_ctx; 645 QDF_STATUS status; 646 647 cm_ctx = cm_get_cm_ctx(vdev); 648 if (!cm_ctx) 649 return QDF_STATUS_E_INVAL; 650 651 qdf_event_reset(&cm_ctx->disconnect_complete); 652 status = cm_disconnect_start_req(vdev, req); 653 if (QDF_IS_STATUS_ERROR(status)) { 654 mlme_err("Disconnect failed with status %d", status); 655 return status; 656 } 657 658 status = qdf_wait_single_event(&cm_ctx->disconnect_complete, 659 CM_DISCONNECT_CMD_TIMEOUT); 660 if (QDF_IS_STATUS_ERROR(status)) 661 mlme_err("Disconnect timeout with status %d", status); 662 663 return status; 664 } 665 666 QDF_STATUS cm_disconnect_rsp(struct wlan_objmgr_vdev *vdev, 667 struct wlan_cm_discon_rsp *resp) 668 { 669 struct cnx_mgr *cm_ctx; 670 QDF_STATUS qdf_status; 671 wlan_cm_id cm_id; 672 uint32_t prefix; 673 674 cm_ctx = cm_get_cm_ctx(vdev); 675 if (!cm_ctx) 676 return QDF_STATUS_E_INVAL; 677 678 cm_id = cm_ctx->active_cm_id; 679 prefix = CM_ID_GET_PREFIX(cm_id); 680 681 if (prefix != DISCONNECT_REQ_PREFIX || cm_id != resp->req.cm_id) { 682 mlme_err(CM_PREFIX_FMT "Active cm_id 0x%x is different", 683 CM_PREFIX_REF(wlan_vdev_get_id(vdev), resp->req.cm_id), 684 cm_id); 685 qdf_status = QDF_STATUS_E_FAILURE; 686 goto disconnect_complete; 687 } 688 qdf_status = 689 cm_sm_deliver_event(vdev, 690 WLAN_CM_SM_EV_DISCONNECT_DONE, 691 sizeof(*resp), resp); 692 if (QDF_IS_STATUS_ERROR(qdf_status)) 693 goto disconnect_complete; 694 695 return qdf_status; 696 697 disconnect_complete: 698 /* 699 * If there is a event posting error it means the SM state is not in 700 * DISCONNECTING (some new cmd has changed the state of SM), so just 701 * complete the disconnect command. 702 */ 703 return cm_disconnect_complete(cm_ctx, resp); 704 } 705 706 QDF_STATUS cm_bss_peer_delete_req(struct wlan_objmgr_vdev *vdev, 707 struct qdf_mac_addr *peer_mac) 708 { 709 mlme_debug("vdev-id %d, delete peer" QDF_MAC_ADDR_FMT, 710 wlan_vdev_get_id(vdev), QDF_MAC_ADDR_REF(peer_mac->bytes)); 711 712 return mlme_cm_bss_peer_delete_req(vdev); 713 } 714 715 QDF_STATUS cm_vdev_down_req(struct wlan_objmgr_vdev *vdev, uint32_t status) 716 { 717 mlme_debug("vdev id %d down req status %d", 718 wlan_vdev_get_id(vdev), status); 719 720 return mlme_cm_vdev_down_req(vdev); 721 } 722