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