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