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