xref: /wlan-dirver/qca-wifi-host-cmn/umac/mlme/connection_mgr/core/src/wlan_cm_disconnect.c (revision 8c3c4172fbd442a68f7b879958acb6794236aee0)
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_blm_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 	status = cm_add_disconnect_req_to_list(cm_ctx, disconnect_req);
299 	if (QDF_IS_STATUS_ERROR(status)) {
300 		mlme_err(CM_PREFIX_FMT "failed to add disconnect req",
301 			 CM_PREFIX_REF(disconnect_req->req.vdev_id,
302 				       disconnect_req->cm_id));
303 		qdf_mem_free(cm_req);
304 		return;
305 	}
306 
307 	cm_disconnect_start(cm_ctx, disconnect_req);
308 }
309 
310 QDF_STATUS cm_disconnect_start(struct cnx_mgr *cm_ctx,
311 			       struct cm_disconnect_req *req)
312 {
313 	struct wlan_objmgr_pdev *pdev;
314 	QDF_STATUS status;
315 
316 	pdev = wlan_vdev_get_pdev(cm_ctx->vdev);
317 	if (!pdev) {
318 		cm_send_disconnect_resp(cm_ctx, req->cm_id);
319 		return QDF_STATUS_E_INVAL;
320 	}
321 	cm_vdev_scan_cancel(pdev, cm_ctx->vdev);
322 	mlme_cm_disconnect_start_ind(cm_ctx->vdev, &req->req);
323 	cm_if_mgr_inform_disconnect_start(cm_ctx->vdev);
324 	mlme_cm_osif_disconnect_start_ind(cm_ctx->vdev);
325 
326 	/* Serialize disconnect req, Handle failure status */
327 	status = cm_ser_disconnect_req(pdev, cm_ctx, req);
328 
329 	if (QDF_IS_STATUS_ERROR(status))
330 		cm_send_disconnect_resp(cm_ctx, req->cm_id);
331 
332 	return QDF_STATUS_SUCCESS;
333 }
334 
335 void
336 cm_update_scan_mlme_on_disconnect(struct wlan_objmgr_vdev *vdev,
337 				  struct cm_disconnect_req *req)
338 {
339 	struct wlan_objmgr_pdev *pdev;
340 	struct bss_info bss_info;
341 	struct mlme_info mlme;
342 	struct wlan_channel *chan;
343 	QDF_STATUS status;
344 
345 	pdev = wlan_vdev_get_pdev(vdev);
346 	if (!pdev) {
347 		mlme_err(CM_PREFIX_FMT "failed to find pdev",
348 			 CM_PREFIX_REF(req->req.vdev_id, req->cm_id));
349 		return;
350 	}
351 
352 	chan = wlan_vdev_get_active_channel(vdev);
353 	if (!chan) {
354 		mlme_err(CM_PREFIX_FMT "failed to get active channel",
355 			 CM_PREFIX_REF(req->req.vdev_id, req->cm_id));
356 		return;
357 	}
358 
359 	status = wlan_vdev_mlme_get_ssid(vdev, bss_info.ssid.ssid,
360 					 &bss_info.ssid.length);
361 
362 	if (QDF_IS_STATUS_ERROR(status)) {
363 		mlme_err(CM_PREFIX_FMT "failed to get ssid",
364 			 CM_PREFIX_REF(req->req.vdev_id, req->cm_id));
365 		return;
366 	}
367 
368 	mlme.assoc_state = SCAN_ENTRY_CON_STATE_NONE;
369 	qdf_copy_macaddr(&bss_info.bssid, &req->req.bssid);
370 
371 	bss_info.freq = chan->ch_freq;
372 
373 	wlan_scan_update_mlme_by_bssinfo(pdev, &bss_info, &mlme);
374 }
375 
376 QDF_STATUS cm_disconnect_active(struct cnx_mgr *cm_ctx, wlan_cm_id *cm_id)
377 {
378 	struct wlan_cm_vdev_discon_req *req;
379 	struct cm_req *cm_req;
380 	struct qdf_mac_addr bssid = QDF_MAC_ADDR_ZERO_INIT;
381 	QDF_STATUS status;
382 
383 	cm_req = cm_get_req_by_cm_id(cm_ctx, *cm_id);
384 	if (!cm_req)
385 		return QDF_STATUS_E_INVAL;
386 
387 	req = qdf_mem_malloc(sizeof(*req));
388 	if (!req)
389 		return QDF_STATUS_E_NOMEM;
390 
391 	cm_ctx->active_cm_id = *cm_id;
392 	wlan_vdev_get_bss_peer_mac(cm_ctx->vdev, &bssid);
393 	/*
394 	 * for northbound req, bssid is not provided so update it from vdev
395 	 * in case bssid is not present
396 	 */
397 	if (qdf_is_macaddr_zero(&cm_req->discon_req.req.bssid) ||
398 	    qdf_is_macaddr_broadcast(&cm_req->discon_req.req.bssid))
399 		qdf_copy_macaddr(&cm_req->discon_req.req.bssid, &bssid);
400 
401 	qdf_copy_macaddr(&req->req.bssid, &bssid);
402 
403 	req->cm_id = *cm_id;
404 	req->req.vdev_id = wlan_vdev_get_id(cm_ctx->vdev);
405 	req->req.source = cm_req->discon_req.req.source;
406 	req->req.reason_code = cm_req->discon_req.req.reason_code;
407 	req->req.is_no_disassoc_disconnect =
408 			cm_req->discon_req.req.is_no_disassoc_disconnect;
409 
410 	cm_update_scan_mlme_on_disconnect(cm_ctx->vdev,
411 					  &cm_req->discon_req);
412 
413 	mlme_debug(CM_PREFIX_FMT "disconnect " QDF_MAC_ADDR_FMT " source %d reason %d",
414 		   CM_PREFIX_REF(req->req.vdev_id, req->cm_id),
415 		   QDF_MAC_ADDR_REF(req->req.bssid.bytes),
416 		   req->req.source, req->req.reason_code);
417 	status = mlme_cm_disconnect_req(cm_ctx->vdev, req);
418 	if (QDF_IS_STATUS_ERROR(status)) {
419 		mlme_err(CM_PREFIX_FMT "disconnect req fail",
420 			 CM_PREFIX_REF(req->req.vdev_id, req->cm_id));
421 		cm_send_disconnect_resp(cm_ctx, req->cm_id);
422 	}
423 	qdf_mem_free(req);
424 
425 	return status;
426 }
427 
428 #ifdef CONN_MGR_ADV_FEATURE
429 static void
430 cm_inform_blm_disconnect_complete(struct wlan_objmgr_vdev *vdev,
431 				  struct wlan_cm_discon_rsp *resp)
432 {
433 	struct wlan_objmgr_pdev *pdev;
434 
435 	pdev = wlan_vdev_get_pdev(vdev);
436 	if (!pdev) {
437 		mlme_err(CM_PREFIX_FMT "failed to find pdev",
438 			 CM_PREFIX_REF(wlan_vdev_get_id(vdev),
439 				       resp->req.cm_id));
440 		return;
441 	}
442 
443 	wlan_dlm_update_bssid_connect_params(pdev, resp->req.req.bssid,
444 					     DLM_AP_DISCONNECTED);
445 }
446 
447 #else
448 static inline void
449 cm_inform_blm_disconnect_complete(struct wlan_objmgr_vdev *vdev,
450 				  struct wlan_cm_discon_rsp *resp)
451 {}
452 #endif
453 
454 #ifdef WLAN_FEATURE_11BE_MLO
455 #ifdef WLAN_FEATURE_11BE_MLO_ADV_FEATURE
456 static inline void
457 cm_clear_vdev_mlo_cap(struct wlan_objmgr_vdev *vdev)
458 {
459 	wlan_vdev_mlme_feat_ext2_cap_clear(vdev, WLAN_VDEV_FEXT2_MLO);
460 }
461 #else /*WLAN_FEATURE_11BE_MLO_ADV_FEATURE*/
462 static inline void
463 cm_clear_vdev_mlo_cap(struct wlan_objmgr_vdev *vdev)
464 {
465 	if (mlo_is_mld_sta(vdev) && ucfg_mlo_is_mld_disconnected(vdev))
466 		ucfg_mlo_mld_clear_mlo_cap(vdev);
467 }
468 #endif /*WLAN_FEATURE_11BE_MLO_ADV_FEATURE*/
469 #else /*WLAN_FEATURE_11BE_MLO*/
470 static inline void
471 cm_clear_vdev_mlo_cap(struct wlan_objmgr_vdev *vdev)
472 { }
473 #endif /*WLAN_FEATURE_11BE_MLO*/
474 
475 QDF_STATUS cm_notify_disconnect_complete(struct cnx_mgr *cm_ctx,
476 					 struct wlan_cm_discon_rsp *resp)
477 {
478 	mlme_cm_disconnect_complete_ind(cm_ctx->vdev, resp);
479 	mlo_sta_link_disconn_notify(cm_ctx->vdev, resp);
480 	mlme_cm_osif_disconnect_complete(cm_ctx->vdev, resp);
481 	cm_if_mgr_inform_disconnect_complete(cm_ctx->vdev);
482 	cm_inform_blm_disconnect_complete(cm_ctx->vdev, resp);
483 
484 	/* Clear MLO cap only when it is the last disconnect req
485 	 * as osif would not have informed userspace for other disconnect
486 	 * req because of cm_id mismatch
487 	 */
488 	if (cm_ctx->disconnect_count == 1)
489 		cm_clear_vdev_mlo_cap(cm_ctx->vdev);
490 
491 	return QDF_STATUS_SUCCESS;
492 }
493 
494 QDF_STATUS cm_disconnect_complete(struct cnx_mgr *cm_ctx,
495 				  struct wlan_cm_discon_rsp *resp)
496 {
497 	/*
498 	 * If the entry is not present in the list, it must have been cleared
499 	 * already.
500 	 */
501 	if (!cm_get_req_by_cm_id(cm_ctx, resp->req.cm_id))
502 		return QDF_STATUS_SUCCESS;
503 
504 	cm_notify_disconnect_complete(cm_ctx, resp);
505 
506 	/*
507 	 * Remove all pending disconnect if this is an active disconnect
508 	 * complete.
509 	 */
510 	if (resp->req.cm_id == cm_ctx->active_cm_id)
511 		cm_flush_pending_request(cm_ctx, DISCONNECT_REQ_PREFIX, false);
512 
513 	cm_remove_cmd(cm_ctx, &resp->req.cm_id);
514 	mlme_debug(CM_PREFIX_FMT "disconnect count %d connect count %d",
515 		   CM_PREFIX_REF(wlan_vdev_get_id(cm_ctx->vdev),
516 				 resp->req.cm_id),
517 		   cm_ctx->disconnect_count, cm_ctx->connect_count);
518 	/* Flush failed connect req as pending disconnect is completed */
519 	if (!cm_ctx->disconnect_count && cm_ctx->connect_count)
520 		cm_flush_pending_request(cm_ctx, CONNECT_REQ_PREFIX, true);
521 
522 	/* Set the disconnect wait event once all disconnect are completed */
523 	if (!cm_ctx->disconnect_count)
524 		qdf_event_set(&cm_ctx->disconnect_complete);
525 
526 	return QDF_STATUS_SUCCESS;
527 }
528 
529 QDF_STATUS
530 cm_handle_discon_req_in_non_connected_state(struct cnx_mgr *cm_ctx,
531 					struct cm_disconnect_req *cm_req,
532 					enum wlan_cm_sm_state cm_state_substate)
533 {
534 	enum wlan_cm_sm_state cur_state = cm_get_state(cm_ctx);
535 
536 	/*
537 	 * South bound and peer disconnect requests are meant for only in
538 	 * connected state, so if the state is connecting a new connect has
539 	 * been received, hence skip the non-osif disconnect request. Also allow
540 	 * MLO link vdev disconnect in connecting state, as this can be
541 	 * initiated due to disconnect on assoc vdev, which may be in connected
542 	 * state.
543 	 */
544 	if (cur_state == WLAN_CM_S_CONNECTING &&
545 	    (cm_req->req.source != CM_OSIF_DISCONNECT &&
546 	    cm_req->req.source != CM_OSIF_CFG_DISCONNECT &&
547 	    cm_req->req.source != CM_MLO_LINK_VDEV_DISCONNECT)) {
548 		mlme_info("Vdev %d ignore disconnect req from source %d in state %d",
549 			  wlan_vdev_get_id(cm_ctx->vdev), cm_req->req.source,
550 			  cm_state_substate);
551 		return QDF_STATUS_E_INVAL;
552 	}
553 
554 	switch (cm_state_substate) {
555 	case WLAN_CM_S_DISCONNECTING:
556 		/*
557 		 * There would be pending disconnect requests in the list, and
558 		 * if they are flushed as part of new disconnect
559 		 * (cm_flush_pending_request), OS_IF would inform the kernel
560 		 * about the disconnect done even though the disconnect is still
561 		 * pending. So update OS_IF with invalid CM_ID so that the resp
562 		 * of only the new disconnect req is given to kernel.
563 		 */
564 		mlme_cm_osif_update_id_and_src(cm_ctx->vdev,
565 					       CM_SOURCE_INVALID,
566 					       CM_ID_INVALID);
567 		cm_flush_pending_request(cm_ctx, DISCONNECT_REQ_PREFIX, false);
568 		/*
569 		 * Flush failed pending connect req as new req is received
570 		 * and its no longer the latest one.
571 		 */
572 		if (cm_ctx->connect_count)
573 			cm_flush_pending_request(cm_ctx, CONNECT_REQ_PREFIX,
574 						 true);
575 		break;
576 	case WLAN_CM_S_ROAMING:
577 		/* for FW roam/LFR3 remove the req from the list */
578 		if (cm_roam_offload_enabled(wlan_vdev_get_psoc(cm_ctx->vdev)))
579 			cm_flush_pending_request(cm_ctx, ROAM_REQ_PREFIX,
580 						 false);
581 		/* fallthrough */
582 	case WLAN_CM_SS_JOIN_ACTIVE:
583 		/*
584 		 * In join active/roaming state, there would be no pending
585 		 * command, so no action required. so for new disconnect
586 		 * request, queue disconnect and move the state to
587 		 * disconnecting.
588 		 */
589 		break;
590 	case WLAN_CM_SS_SCAN:
591 		/* In the scan state abort the ongoing scan */
592 		cm_vdev_scan_cancel(wlan_vdev_get_pdev(cm_ctx->vdev),
593 				    cm_ctx->vdev);
594 		/* fallthrough */
595 	case WLAN_CM_SS_JOIN_PENDING:
596 		/*
597 		 * There would be pending disconnect requests in the list, and
598 		 * if they are flushed as part of new disconnect
599 		 * (cm_flush_pending_request), OS_IF would inform the kernel
600 		 * about the disconnect done even though the disconnect is still
601 		 * pending. So update OS_IF with invalid CM_ID so that the resp
602 		 * of only the new disconnect req is given to kernel.
603 		 */
604 		mlme_cm_osif_update_id_and_src(cm_ctx->vdev,
605 					       CM_SOURCE_INVALID,
606 					       CM_ID_INVALID);
607 		/*
608 		 * In case of scan or join pending there could be a connect and
609 		 * disconnect requests pending, so flush all the requests except
610 		 * the activated request.
611 		 */
612 		cm_flush_pending_request(cm_ctx, CONNECT_REQ_PREFIX, false);
613 		cm_flush_pending_request(cm_ctx, DISCONNECT_REQ_PREFIX, false);
614 		break;
615 	case WLAN_CM_S_INIT:
616 		/*
617 		 * In this case the vdev is already disconnected and thus the
618 		 * indication to upper layer, would have been sent as part of
619 		 * previous disconnect/connect failure.
620 		 *
621 		 * If upper layer is in process of connecting, sending
622 		 * disconnect indication back again may cause it to incorrectly
623 		 * think it as a connect failure. So sending disconnect
624 		 * indication again is not advisable.
625 		 *
626 		 * So no need to do anything here, just return failure and drop
627 		 * disconnect.
628 		 */
629 		mlme_info("vdev %d droping disconnect req from source %d in INIT state",
630 			  wlan_vdev_get_id(cm_ctx->vdev), cm_req->req.source);
631 		return QDF_STATUS_E_ALREADY;
632 	default:
633 		mlme_err("Vdev %d disconnect req in invalid state %d",
634 			 wlan_vdev_get_id(cm_ctx->vdev),
635 			 cm_state_substate);
636 		return QDF_STATUS_E_FAILURE;
637 	};
638 
639 	/* Queue the new disconnect request after state specific actions */
640 	return cm_add_disconnect_req_to_list(cm_ctx, cm_req);
641 }
642 
643 QDF_STATUS cm_add_disconnect_req_to_list(struct cnx_mgr *cm_ctx,
644 					 struct cm_disconnect_req *req)
645 {
646 	QDF_STATUS status;
647 	struct cm_req *cm_req;
648 
649 	cm_req = qdf_container_of(req, struct cm_req, discon_req);
650 	req->cm_id = cm_get_cm_id(cm_ctx, req->req.source);
651 	cm_req->cm_id = req->cm_id;
652 	status = cm_add_req_to_list_and_indicate_osif(cm_ctx, cm_req,
653 						      req->req.source);
654 
655 	return status;
656 }
657 
658 QDF_STATUS cm_disconnect_start_req(struct wlan_objmgr_vdev *vdev,
659 				   struct wlan_cm_disconnect_req *req)
660 {
661 	struct cnx_mgr *cm_ctx;
662 	struct cm_req *cm_req;
663 	struct cm_disconnect_req *disconnect_req;
664 	QDF_STATUS status;
665 
666 	cm_ctx = cm_get_cm_ctx(vdev);
667 	if (!cm_ctx)
668 		return QDF_STATUS_E_INVAL;
669 
670 	/*
671 	 * This would be freed as part of removal from cm req list if adding
672 	 * to list is success after posting WLAN_CM_SM_EV_DISCONNECT_REQ.
673 	 */
674 	cm_req = qdf_mem_malloc(sizeof(*cm_req));
675 
676 	if (!cm_req)
677 		return QDF_STATUS_E_NOMEM;
678 
679 	if (wlan_vdev_mlme_is_mlo_vdev(vdev) &&
680 	    !wlan_vdev_mlme_is_mlo_link_vdev(vdev))
681 		req->is_no_disassoc_disconnect = 1;
682 
683 	disconnect_req = &cm_req->discon_req;
684 	disconnect_req->req = *req;
685 
686 	status = cm_sm_deliver_event(vdev, WLAN_CM_SM_EV_DISCONNECT_REQ,
687 				     sizeof(*disconnect_req), disconnect_req);
688 	/* free the req if disconnect is not handled */
689 	if (QDF_IS_STATUS_ERROR(status))
690 		qdf_mem_free(cm_req);
691 
692 	return status;
693 }
694 
695 QDF_STATUS cm_disconnect_start_req_sync(struct wlan_objmgr_vdev *vdev,
696 					struct wlan_cm_disconnect_req *req)
697 {
698 	struct cnx_mgr *cm_ctx;
699 	QDF_STATUS status;
700 
701 	cm_ctx = cm_get_cm_ctx(vdev);
702 	if (!cm_ctx)
703 		return QDF_STATUS_E_INVAL;
704 
705 	if (wlan_vdev_mlme_is_mlo_vdev(vdev) &&
706 	    !wlan_vdev_mlme_is_mlo_link_vdev(vdev))
707 		req->is_no_disassoc_disconnect = 1;
708 
709 	qdf_event_reset(&cm_ctx->disconnect_complete);
710 	status = cm_disconnect_start_req(vdev, req);
711 	if (QDF_IS_STATUS_ERROR(status)) {
712 		mlme_err("Disconnect failed with status %d", status);
713 		return status;
714 	}
715 
716 	status = qdf_wait_single_event(&cm_ctx->disconnect_complete,
717 				       CM_DISCONNECT_CMD_TIMEOUT);
718 	if (QDF_IS_STATUS_ERROR(status))
719 		mlme_err("Disconnect timeout with status %d", status);
720 
721 	return status;
722 }
723 
724 QDF_STATUS cm_disconnect_rsp(struct wlan_objmgr_vdev *vdev,
725 			     struct wlan_cm_discon_rsp *resp)
726 {
727 	struct cnx_mgr *cm_ctx;
728 	QDF_STATUS qdf_status;
729 	wlan_cm_id cm_id;
730 	uint32_t prefix;
731 
732 	cm_ctx = cm_get_cm_ctx(vdev);
733 	if (!cm_ctx)
734 		return QDF_STATUS_E_INVAL;
735 
736 	cm_id = cm_ctx->active_cm_id;
737 	prefix = CM_ID_GET_PREFIX(cm_id);
738 
739 	if (prefix != DISCONNECT_REQ_PREFIX || cm_id != resp->req.cm_id) {
740 		mlme_err(CM_PREFIX_FMT "Active cm_id 0x%x is different",
741 			 CM_PREFIX_REF(wlan_vdev_get_id(vdev), resp->req.cm_id),
742 			 cm_id);
743 		qdf_status = QDF_STATUS_E_FAILURE;
744 		goto disconnect_complete;
745 	}
746 	qdf_status =
747 		cm_sm_deliver_event(vdev,
748 				    WLAN_CM_SM_EV_DISCONNECT_DONE,
749 				    sizeof(*resp), resp);
750 	if (QDF_IS_STATUS_ERROR(qdf_status))
751 		goto disconnect_complete;
752 
753 	return qdf_status;
754 
755 disconnect_complete:
756 	/*
757 	 * If there is a event posting error it means the SM state is not in
758 	 * DISCONNECTING (some new cmd has changed the state of SM), so just
759 	 * complete the disconnect command.
760 	 */
761 	return cm_disconnect_complete(cm_ctx, resp);
762 }
763 
764 QDF_STATUS cm_bss_peer_delete_req(struct wlan_objmgr_vdev *vdev,
765 				  struct qdf_mac_addr *peer_mac)
766 {
767 	mlme_debug("vdev-id %d, delete peer" QDF_MAC_ADDR_FMT,
768 		   wlan_vdev_get_id(vdev), QDF_MAC_ADDR_REF(peer_mac->bytes));
769 
770 	return mlme_cm_bss_peer_delete_req(vdev);
771 }
772 
773 QDF_STATUS cm_vdev_down_req(struct wlan_objmgr_vdev *vdev, uint32_t status)
774 {
775 	mlme_debug("vdev id %d down req status %d",
776 		   wlan_vdev_get_id(vdev), status);
777 
778 	return mlme_cm_vdev_down_req(vdev);
779 }
780