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