xref: /wlan-dirver/qca-wifi-host-cmn/umac/mlme/connection_mgr/core/src/wlan_cm_disconnect.c (revision 8b3dca18206e1a0461492f082fa6e270b092c035)
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, req);
419 	qdf_mem_free(req);
420 
421 	return status;
422 }
423 
424 QDF_STATUS
425 cm_disconnect_continue_after_rso_stop(struct wlan_objmgr_vdev *vdev,
426 				      struct wlan_cm_vdev_discon_req *req)
427 {
428 	struct cm_req *cm_req;
429 	QDF_STATUS status;
430 	struct qdf_mac_addr bssid = QDF_MAC_ADDR_ZERO_INIT;
431 	struct cnx_mgr *cm_ctx = cm_get_cm_ctx(vdev);
432 
433 	if (!cm_ctx)
434 		return QDF_STATUS_E_INVAL;
435 
436 	cm_req = cm_get_req_by_cm_id(cm_ctx, req->cm_id);
437 	if (!cm_req)
438 		return QDF_STATUS_E_INVAL;
439 
440 	wlan_vdev_get_bss_peer_mac(cm_ctx->vdev, &bssid);
441 
442 	qdf_copy_macaddr(&req->req.bssid, &bssid);
443 	/*
444 	 * for northbound req, bssid is not provided so update it from vdev
445 	 * in case bssid is not present
446 	 */
447 	if (qdf_is_macaddr_zero(&cm_req->discon_req.req.bssid) ||
448 	    qdf_is_macaddr_broadcast(&cm_req->discon_req.req.bssid))
449 		qdf_copy_macaddr(&cm_req->discon_req.req.bssid,
450 				 &req->req.bssid);
451 	cm_update_scan_mlme_on_disconnect(cm_ctx->vdev,
452 					  &cm_req->discon_req);
453 
454 	mlme_debug(CM_PREFIX_FMT "disconnect " QDF_MAC_ADDR_FMT
455 		   " source %d reason %d",
456 		   CM_PREFIX_REF(req->req.vdev_id, req->cm_id),
457 		   QDF_MAC_ADDR_REF(req->req.bssid.bytes),
458 		   req->req.source, req->req.reason_code);
459 
460 	status = mlme_cm_disconnect_req(cm_ctx->vdev, req);
461 	if (QDF_IS_STATUS_ERROR(status)) {
462 		mlme_err(CM_PREFIX_FMT "disconnect req fail",
463 			 CM_PREFIX_REF(req->req.vdev_id, req->cm_id));
464 		cm_send_disconnect_resp(cm_ctx, req->cm_id);
465 	}
466 
467 	return status;
468 }
469 
470 QDF_STATUS
471 cm_handle_rso_stop_rsp(struct wlan_objmgr_vdev *vdev,
472 		       struct wlan_cm_vdev_discon_req *req)
473 {
474 	struct cnx_mgr *cm_ctx = cm_get_cm_ctx(vdev);
475 	wlan_cm_id cm_id;
476 
477 	if (!cm_ctx)
478 		return QDF_STATUS_E_INVAL;
479 
480 	cm_id = cm_ctx->active_cm_id;
481 
482 	if ((CM_ID_GET_PREFIX(req->cm_id)) != DISCONNECT_REQ_PREFIX ||
483 	    cm_id != req->cm_id) {
484 		mlme_err(CM_PREFIX_FMT "active req is not disconnect req",
485 			 CM_PREFIX_REF(wlan_vdev_get_id(vdev), req->cm_id));
486 		return QDF_STATUS_E_INVAL;
487 	}
488 
489 	return cm_sm_deliver_event(vdev, WLAN_CM_SM_EV_RSO_STOP_RSP,
490 				   sizeof(*req), req);
491 }
492 
493 #ifdef CONN_MGR_ADV_FEATURE
494 static void
495 cm_inform_dlm_disconnect_complete(struct wlan_objmgr_vdev *vdev,
496 				  struct wlan_cm_discon_rsp *resp)
497 {
498 	struct wlan_objmgr_pdev *pdev;
499 
500 	pdev = wlan_vdev_get_pdev(vdev);
501 	if (!pdev) {
502 		mlme_err(CM_PREFIX_FMT "failed to find pdev",
503 			 CM_PREFIX_REF(wlan_vdev_get_id(vdev),
504 				       resp->req.cm_id));
505 		return;
506 	}
507 
508 	wlan_dlm_update_bssid_connect_params(pdev, resp->req.req.bssid,
509 					     DLM_AP_DISCONNECTED);
510 }
511 
512 #else
513 static inline void
514 cm_inform_dlm_disconnect_complete(struct wlan_objmgr_vdev *vdev,
515 				  struct wlan_cm_discon_rsp *resp)
516 {}
517 #endif
518 
519 #ifdef WLAN_FEATURE_11BE_MLO
520 #ifdef WLAN_FEATURE_11BE_MLO_ADV_FEATURE
521 static inline void
522 cm_clear_vdev_mlo_cap(struct wlan_objmgr_vdev *vdev)
523 {
524 	wlan_vdev_mlme_clear_mlo_vdev(vdev);
525 }
526 #else /*WLAN_FEATURE_11BE_MLO_ADV_FEATURE*/
527 static inline void
528 cm_clear_vdev_mlo_cap(struct wlan_objmgr_vdev *vdev)
529 {
530 	if (mlo_is_mld_sta(vdev) && ucfg_mlo_is_mld_disconnected(vdev))
531 		ucfg_mlo_mld_clear_mlo_cap(vdev);
532 }
533 #endif /*WLAN_FEATURE_11BE_MLO_ADV_FEATURE*/
534 #else /*WLAN_FEATURE_11BE_MLO*/
535 static inline void
536 cm_clear_vdev_mlo_cap(struct wlan_objmgr_vdev *vdev)
537 { }
538 #endif /*WLAN_FEATURE_11BE_MLO*/
539 
540 QDF_STATUS cm_notify_disconnect_complete(struct cnx_mgr *cm_ctx,
541 					 struct wlan_cm_discon_rsp *resp)
542 {
543 	mlme_cm_disconnect_complete_ind(cm_ctx->vdev, resp);
544 	mlo_sta_link_disconn_notify(cm_ctx->vdev, resp);
545 	mlme_cm_osif_disconnect_complete(cm_ctx->vdev, resp);
546 	cm_if_mgr_inform_disconnect_complete(cm_ctx->vdev);
547 	cm_inform_dlm_disconnect_complete(cm_ctx->vdev, resp);
548 
549 	return QDF_STATUS_SUCCESS;
550 }
551 
552 QDF_STATUS cm_disconnect_complete(struct cnx_mgr *cm_ctx,
553 				  struct wlan_cm_discon_rsp *resp)
554 {
555 	/*
556 	 * If the entry is not present in the list, it must have been cleared
557 	 * already.
558 	 */
559 	if (!cm_get_req_by_cm_id(cm_ctx, resp->req.cm_id))
560 		return QDF_STATUS_SUCCESS;
561 
562 	cm_notify_disconnect_complete(cm_ctx, resp);
563 
564 	/*
565 	 * Remove all pending disconnect if this is an active disconnect
566 	 * complete.
567 	 */
568 	if (resp->req.cm_id == cm_ctx->active_cm_id)
569 		cm_flush_pending_request(cm_ctx, DISCONNECT_REQ_PREFIX, false);
570 
571 	cm_remove_cmd(cm_ctx, &resp->req.cm_id);
572 	mlme_debug(CM_PREFIX_FMT "disconnect count %d connect count %d",
573 		   CM_PREFIX_REF(wlan_vdev_get_id(cm_ctx->vdev),
574 				 resp->req.cm_id),
575 		   cm_ctx->disconnect_count, cm_ctx->connect_count);
576 	/* Flush failed connect req as pending disconnect is completed */
577 	if (!cm_ctx->disconnect_count && cm_ctx->connect_count)
578 		cm_flush_pending_request(cm_ctx, CONNECT_REQ_PREFIX, true);
579 
580 	/* Set the disconnect wait event once all disconnect are completed */
581 	if (!cm_ctx->disconnect_count) {
582 		/* Clear MLO cap only when it is the last disconnect req */
583 		cm_clear_vdev_mlo_cap(cm_ctx->vdev);
584 		qdf_event_set(&cm_ctx->disconnect_complete);
585 	}
586 
587 	return QDF_STATUS_SUCCESS;
588 }
589 
590 QDF_STATUS
591 cm_handle_discon_req_in_non_connected_state(struct cnx_mgr *cm_ctx,
592 					struct cm_disconnect_req *cm_req,
593 					enum wlan_cm_sm_state cm_state_substate)
594 {
595 	enum wlan_cm_sm_state cur_state = cm_get_state(cm_ctx);
596 
597 	/*
598 	 * South bound and peer disconnect requests are meant for only in
599 	 * connected state, so if the state is connecting a new connect has
600 	 * been received, hence skip the non-osif disconnect request. Also allow
601 	 * MLO link vdev disconnect in connecting state, as this can be
602 	 * initiated due to disconnect on assoc vdev, which may be in connected
603 	 * state.
604 	 */
605 	if (cur_state == WLAN_CM_S_CONNECTING &&
606 	    (cm_req->req.source != CM_OSIF_DISCONNECT &&
607 	    cm_req->req.source != CM_OSIF_CFG_DISCONNECT &&
608 	    cm_req->req.source != CM_MLO_LINK_VDEV_DISCONNECT)) {
609 		mlme_info("Vdev %d ignore disconnect req from source %d in state %d",
610 			  wlan_vdev_get_id(cm_ctx->vdev), cm_req->req.source,
611 			  cm_state_substate);
612 		return QDF_STATUS_E_INVAL;
613 	}
614 
615 	switch (cm_state_substate) {
616 	case WLAN_CM_S_DISCONNECTING:
617 		/*
618 		 * There would be pending disconnect requests in the list, and
619 		 * if they are flushed as part of new disconnect
620 		 * (cm_flush_pending_request), OS_IF would inform the kernel
621 		 * about the disconnect done even though the disconnect is still
622 		 * pending. So update OS_IF with invalid CM_ID so that the resp
623 		 * of only the new disconnect req is given to kernel.
624 		 */
625 		mlme_cm_osif_update_id_and_src(cm_ctx->vdev,
626 					       CM_SOURCE_INVALID,
627 					       CM_ID_INVALID);
628 		cm_flush_pending_request(cm_ctx, DISCONNECT_REQ_PREFIX, false);
629 		/*
630 		 * Flush failed pending connect req as new req is received
631 		 * and its no longer the latest one.
632 		 */
633 		if (cm_ctx->connect_count)
634 			cm_flush_pending_request(cm_ctx, CONNECT_REQ_PREFIX,
635 						 true);
636 		break;
637 	case WLAN_CM_S_ROAMING:
638 		/* for FW roam/LFR3 remove the req from the list */
639 		if (cm_roam_offload_enabled(wlan_vdev_get_psoc(cm_ctx->vdev)))
640 			cm_flush_pending_request(cm_ctx, ROAM_REQ_PREFIX,
641 						 false);
642 		fallthrough;
643 	case WLAN_CM_SS_JOIN_ACTIVE:
644 		/*
645 		 * In join active/roaming state, there would be no pending
646 		 * command, so no action required. so for new disconnect
647 		 * request, queue disconnect and move the state to
648 		 * disconnecting.
649 		 */
650 		break;
651 	case WLAN_CM_SS_SCAN:
652 		/* In the scan state abort the ongoing scan */
653 		cm_vdev_scan_cancel(wlan_vdev_get_pdev(cm_ctx->vdev),
654 				    cm_ctx->vdev);
655 		fallthrough;
656 	case WLAN_CM_SS_JOIN_PENDING:
657 		/*
658 		 * There would be pending disconnect requests in the list, and
659 		 * if they are flushed as part of new disconnect
660 		 * (cm_flush_pending_request), OS_IF would inform the kernel
661 		 * about the disconnect done even though the disconnect is still
662 		 * pending. So update OS_IF with invalid CM_ID so that the resp
663 		 * of only the new disconnect req is given to kernel.
664 		 */
665 		mlme_cm_osif_update_id_and_src(cm_ctx->vdev,
666 					       CM_SOURCE_INVALID,
667 					       CM_ID_INVALID);
668 		/*
669 		 * In case of scan or join pending there could be a connect and
670 		 * disconnect requests pending, so flush all the requests except
671 		 * the activated request.
672 		 */
673 		cm_flush_pending_request(cm_ctx, CONNECT_REQ_PREFIX, false);
674 		cm_flush_pending_request(cm_ctx, DISCONNECT_REQ_PREFIX, false);
675 		break;
676 	case WLAN_CM_S_INIT:
677 		/*
678 		 * In this case the vdev is already disconnected and thus the
679 		 * indication to upper layer, would have been sent as part of
680 		 * previous disconnect/connect failure.
681 		 *
682 		 * If upper layer is in process of connecting, sending
683 		 * disconnect indication back again may cause it to incorrectly
684 		 * think it as a connect failure. So sending disconnect
685 		 * indication again is not advisable.
686 		 *
687 		 * So no need to do anything here, just return failure and drop
688 		 * disconnect.
689 		 */
690 		mlme_info("vdev %d droping disconnect req from source %d in INIT state",
691 			  wlan_vdev_get_id(cm_ctx->vdev), cm_req->req.source);
692 		return QDF_STATUS_E_ALREADY;
693 	default:
694 		mlme_err("Vdev %d disconnect req in invalid state %d",
695 			 wlan_vdev_get_id(cm_ctx->vdev),
696 			 cm_state_substate);
697 		return QDF_STATUS_E_FAILURE;
698 	};
699 
700 	/* Queue the new disconnect request after state specific actions */
701 	return cm_add_disconnect_req_to_list(cm_ctx, cm_req);
702 }
703 
704 QDF_STATUS cm_add_disconnect_req_to_list(struct cnx_mgr *cm_ctx,
705 					 struct cm_disconnect_req *req)
706 {
707 	QDF_STATUS status;
708 	struct cm_req *cm_req;
709 
710 	cm_req = qdf_container_of(req, struct cm_req, discon_req);
711 	req->cm_id = cm_get_cm_id(cm_ctx, req->req.source);
712 	cm_req->cm_id = req->cm_id;
713 	status = cm_add_req_to_list_and_indicate_osif(cm_ctx, cm_req,
714 						      req->req.source);
715 
716 	return status;
717 }
718 
719 QDF_STATUS cm_disconnect_start_req(struct wlan_objmgr_vdev *vdev,
720 				   struct wlan_cm_disconnect_req *req)
721 {
722 	struct cnx_mgr *cm_ctx;
723 	struct cm_req *cm_req;
724 	struct cm_disconnect_req *disconnect_req;
725 	QDF_STATUS status;
726 
727 	cm_ctx = cm_get_cm_ctx(vdev);
728 	if (!cm_ctx)
729 		return QDF_STATUS_E_INVAL;
730 
731 	/*
732 	 * This would be freed as part of removal from cm req list if adding
733 	 * to list is success after posting WLAN_CM_SM_EV_DISCONNECT_REQ.
734 	 */
735 	cm_req = qdf_mem_malloc(sizeof(*cm_req));
736 
737 	if (!cm_req)
738 		return QDF_STATUS_E_NOMEM;
739 
740 	if (wlan_vdev_mlme_is_mlo_vdev(vdev) &&
741 	    !wlan_vdev_mlme_is_mlo_link_vdev(vdev))
742 		req->is_no_disassoc_disconnect = 1;
743 
744 	disconnect_req = &cm_req->discon_req;
745 	disconnect_req->req = *req;
746 
747 	status = cm_sm_deliver_event(vdev, WLAN_CM_SM_EV_DISCONNECT_REQ,
748 				     sizeof(*disconnect_req), disconnect_req);
749 	/* free the req if disconnect is not handled */
750 	if (QDF_IS_STATUS_ERROR(status))
751 		qdf_mem_free(cm_req);
752 
753 	return status;
754 }
755 
756 QDF_STATUS cm_disconnect_start_req_sync(struct wlan_objmgr_vdev *vdev,
757 					struct wlan_cm_disconnect_req *req)
758 {
759 	struct cnx_mgr *cm_ctx;
760 	QDF_STATUS status;
761 
762 	cm_ctx = cm_get_cm_ctx(vdev);
763 	if (!cm_ctx)
764 		return QDF_STATUS_E_INVAL;
765 
766 	if (wlan_vdev_mlme_is_mlo_vdev(vdev) &&
767 	    !wlan_vdev_mlme_is_mlo_link_vdev(vdev))
768 		req->is_no_disassoc_disconnect = 1;
769 
770 	qdf_event_reset(&cm_ctx->disconnect_complete);
771 	status = cm_disconnect_start_req(vdev, req);
772 	if (QDF_IS_STATUS_ERROR(status)) {
773 		mlme_err("Disconnect failed with status %d", status);
774 		return status;
775 	}
776 
777 	status = qdf_wait_single_event(&cm_ctx->disconnect_complete,
778 				       CM_DISCONNECT_CMD_TIMEOUT);
779 	if (QDF_IS_STATUS_ERROR(status))
780 		mlme_err("Disconnect timeout with status %d", status);
781 
782 	return status;
783 }
784 
785 QDF_STATUS cm_disconnect_rsp(struct wlan_objmgr_vdev *vdev,
786 			     struct wlan_cm_discon_rsp *resp)
787 {
788 	struct cnx_mgr *cm_ctx;
789 	QDF_STATUS qdf_status;
790 	wlan_cm_id cm_id;
791 	uint32_t prefix;
792 
793 	cm_ctx = cm_get_cm_ctx(vdev);
794 	if (!cm_ctx)
795 		return QDF_STATUS_E_INVAL;
796 
797 	cm_id = cm_ctx->active_cm_id;
798 	prefix = CM_ID_GET_PREFIX(cm_id);
799 
800 	if (prefix != DISCONNECT_REQ_PREFIX || cm_id != resp->req.cm_id) {
801 		mlme_err(CM_PREFIX_FMT "Active cm_id 0x%x is different",
802 			 CM_PREFIX_REF(wlan_vdev_get_id(vdev), resp->req.cm_id),
803 			 cm_id);
804 		qdf_status = QDF_STATUS_E_FAILURE;
805 		goto disconnect_complete;
806 	}
807 	qdf_status =
808 		cm_sm_deliver_event(vdev,
809 				    WLAN_CM_SM_EV_DISCONNECT_DONE,
810 				    sizeof(*resp), resp);
811 	if (QDF_IS_STATUS_ERROR(qdf_status))
812 		goto disconnect_complete;
813 
814 	return qdf_status;
815 
816 disconnect_complete:
817 	/*
818 	 * If there is a event posting error it means the SM state is not in
819 	 * DISCONNECTING (some new cmd has changed the state of SM), so just
820 	 * complete the disconnect command.
821 	 */
822 	return cm_disconnect_complete(cm_ctx, resp);
823 }
824 
825 QDF_STATUS cm_bss_peer_delete_req(struct wlan_objmgr_vdev *vdev,
826 				  struct qdf_mac_addr *peer_mac)
827 {
828 	mlme_debug("vdev-id %d, delete peer" QDF_MAC_ADDR_FMT,
829 		   wlan_vdev_get_id(vdev), QDF_MAC_ADDR_REF(peer_mac->bytes));
830 
831 	return mlme_cm_bss_peer_delete_req(vdev);
832 }
833 
834 QDF_STATUS cm_vdev_down_req(struct wlan_objmgr_vdev *vdev, uint32_t status)
835 {
836 	mlme_debug("vdev id %d down req status %d",
837 		   wlan_vdev_get_id(vdev), status);
838 
839 	return mlme_cm_vdev_down_req(vdev);
840 }
841