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