xref: /wlan-driver/qca-wifi-host-cmn/umac/mlme/connection_mgr/core/src/wlan_cm_disconnect.c (revision 5113495b16420b49004c444715d2daae2066e7dc)
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 
cm_send_disconnect_resp(struct cnx_mgr * cm_ctx,wlan_cm_id cm_id)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
cm_activate_disconnect_req_sched_cb(struct scheduler_msg * msg)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
cm_activate_disconnect_req(struct wlan_serialization_command * cmd)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
cm_activate_disconnect_req(struct wlan_serialization_command * cmd)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
cm_sm_deliver_disconnect_event(struct cnx_mgr * cm_ctx,struct wlan_serialization_command * cmd)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
cm_ser_disconnect_cb(struct wlan_serialization_command * cmd,enum wlan_serialization_cb_reason reason)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 
cm_ser_disconnect_req(struct wlan_objmgr_pdev * pdev,struct cnx_mgr * cm_ctx,struct cm_disconnect_req * req)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
cm_if_mgr_inform_disconnect_complete(struct wlan_objmgr_vdev * vdev)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
cm_if_mgr_inform_disconnect_start(struct wlan_objmgr_vdev * vdev)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 
cm_initiate_internal_disconnect(struct cnx_mgr * cm_ctx)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 
cm_disconnect_start(struct cnx_mgr * cm_ctx,struct cm_disconnect_req * req)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
cm_update_scan_mlme_on_disconnect(struct wlan_objmgr_vdev * vdev,struct cm_disconnect_req * req)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 
cm_disconnect_active(struct cnx_mgr * cm_ctx,wlan_cm_id * cm_id)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 	    cm_req->discon_req.req.source != CM_MLO_ROAM_INTERNAL_DISCONNECT)
430 		status = mlme_cm_rso_stop_req(cm_ctx->vdev);
431 
432 	if (status != QDF_STATUS_E_NOSUPPORT)
433 		return status;
434 
435 	req = qdf_mem_malloc(sizeof(*req));
436 	if (!req)
437 		return QDF_STATUS_E_NOMEM;
438 
439 	req->cm_id = *cm_id;
440 	req->req.vdev_id = wlan_vdev_get_id(cm_ctx->vdev);
441 	req->req.source = cm_req->discon_req.req.source;
442 	req->req.reason_code = cm_req->discon_req.req.reason_code;
443 	req->req.is_no_disassoc_disconnect =
444 			cm_req->discon_req.req.is_no_disassoc_disconnect;
445 
446 	cm_disconnect_continue_after_rso_stop(cm_ctx->vdev, req);
447 	qdf_mem_free(req);
448 
449 	return status;
450 }
451 
452 QDF_STATUS
cm_disconnect_continue_after_rso_stop(struct wlan_objmgr_vdev * vdev,struct wlan_cm_vdev_discon_req * req)453 cm_disconnect_continue_after_rso_stop(struct wlan_objmgr_vdev *vdev,
454 				      struct wlan_cm_vdev_discon_req *req)
455 {
456 	struct cm_req *cm_req;
457 	QDF_STATUS status;
458 	struct qdf_mac_addr bssid = QDF_MAC_ADDR_ZERO_INIT;
459 	struct cnx_mgr *cm_ctx = cm_get_cm_ctx(vdev);
460 
461 	if (!cm_ctx)
462 		return QDF_STATUS_E_INVAL;
463 
464 	cm_req = cm_get_req_by_cm_id(cm_ctx, req->cm_id);
465 	if (!cm_req)
466 		return QDF_STATUS_E_INVAL;
467 
468 	wlan_vdev_get_bss_peer_mac(cm_ctx->vdev, &bssid);
469 
470 	qdf_copy_macaddr(&req->req.bssid, &bssid);
471 	/*
472 	 * for northbound req, bssid is not provided so update it from vdev
473 	 * in case bssid is not present
474 	 */
475 	if (qdf_is_macaddr_zero(&cm_req->discon_req.req.bssid) ||
476 	    qdf_is_macaddr_broadcast(&cm_req->discon_req.req.bssid))
477 		qdf_copy_macaddr(&cm_req->discon_req.req.bssid,
478 				 &req->req.bssid);
479 	cm_update_scan_mlme_on_disconnect(cm_ctx->vdev,
480 					  &cm_req->discon_req);
481 
482 	mlme_debug(CM_PREFIX_FMT "disconnect " QDF_MAC_ADDR_FMT
483 		   " source %d reason %d",
484 		   CM_PREFIX_REF(req->req.vdev_id, req->cm_id),
485 		   QDF_MAC_ADDR_REF(req->req.bssid.bytes),
486 		   req->req.source, req->req.reason_code);
487 
488 	status = mlme_cm_disconnect_req(cm_ctx->vdev, req);
489 	if (QDF_IS_STATUS_ERROR(status)) {
490 		mlme_err(CM_PREFIX_FMT "disconnect req fail",
491 			 CM_PREFIX_REF(req->req.vdev_id, req->cm_id));
492 		cm_send_disconnect_resp(cm_ctx, req->cm_id);
493 	}
494 
495 	return status;
496 }
497 
498 QDF_STATUS
cm_handle_rso_stop_rsp(struct wlan_objmgr_vdev * vdev,struct wlan_cm_vdev_discon_req * req)499 cm_handle_rso_stop_rsp(struct wlan_objmgr_vdev *vdev,
500 		       struct wlan_cm_vdev_discon_req *req)
501 {
502 	struct cnx_mgr *cm_ctx = cm_get_cm_ctx(vdev);
503 	wlan_cm_id cm_id;
504 
505 	if (!cm_ctx)
506 		return QDF_STATUS_E_INVAL;
507 
508 	cm_id = cm_ctx->active_cm_id;
509 
510 	if ((CM_ID_GET_PREFIX(req->cm_id)) != DISCONNECT_REQ_PREFIX ||
511 	    cm_id != req->cm_id) {
512 		mlme_err(CM_PREFIX_FMT "active req is not disconnect req",
513 			 CM_PREFIX_REF(wlan_vdev_get_id(vdev), req->cm_id));
514 		return QDF_STATUS_E_INVAL;
515 	}
516 
517 	return cm_sm_deliver_event(vdev, WLAN_CM_SM_EV_RSO_STOP_RSP,
518 				   sizeof(*req), req);
519 }
520 
521 #ifdef CONN_MGR_ADV_FEATURE
522 static void
cm_inform_dlm_disconnect_complete(struct wlan_objmgr_vdev * vdev,struct wlan_cm_discon_rsp * resp)523 cm_inform_dlm_disconnect_complete(struct wlan_objmgr_vdev *vdev,
524 				  struct wlan_cm_discon_rsp *resp)
525 {
526 	struct wlan_objmgr_pdev *pdev;
527 
528 	pdev = wlan_vdev_get_pdev(vdev);
529 	if (!pdev) {
530 		mlme_err(CM_PREFIX_FMT "failed to find pdev",
531 			 CM_PREFIX_REF(wlan_vdev_get_id(vdev),
532 				       resp->req.cm_id));
533 		return;
534 	}
535 
536 	wlan_dlm_update_bssid_connect_params(pdev, resp->req.req.bssid,
537 					     DLM_AP_DISCONNECTED);
538 }
539 
540 #else
541 static inline void
cm_inform_dlm_disconnect_complete(struct wlan_objmgr_vdev * vdev,struct wlan_cm_discon_rsp * resp)542 cm_inform_dlm_disconnect_complete(struct wlan_objmgr_vdev *vdev,
543 				  struct wlan_cm_discon_rsp *resp)
544 {}
545 #endif
546 
547 #ifdef WLAN_FEATURE_11BE_MLO
548 #ifdef WLAN_FEATURE_11BE_MLO_ADV_FEATURE
549 static inline void
cm_clear_vdev_mlo_cap(struct wlan_objmgr_vdev * vdev,struct wlan_cm_discon_rsp * rsp)550 cm_clear_vdev_mlo_cap(struct wlan_objmgr_vdev *vdev,
551 		      struct wlan_cm_discon_rsp *rsp)
552 {
553 	wlan_vdev_mlme_clear_mlo_vdev(vdev);
554 }
555 #else /*WLAN_FEATURE_11BE_MLO_ADV_FEATURE*/
556 static inline void
cm_clear_vdev_mlo_cap(struct wlan_objmgr_vdev * vdev,struct wlan_cm_discon_rsp * rsp)557 cm_clear_vdev_mlo_cap(struct wlan_objmgr_vdev *vdev,
558 		      struct wlan_cm_discon_rsp *rsp)
559 {
560 	if (mlo_is_mld_sta(vdev) && ucfg_mlo_is_mld_disconnected(vdev))
561 		ucfg_mlo_mld_clear_mlo_cap(vdev);
562 	if (rsp->req.req.reason_code == REASON_HOST_TRIGGERED_LINK_DELETE) {
563 		wlan_vdev_mlme_clear_mlo_vdev(vdev);
564 		wlan_vdev_mlme_clear_mlo_link_vdev(vdev);
565 	}
566 
567 	wlan_vdev_set_link_id(vdev, WLAN_LINK_ID_INVALID);
568 }
569 #endif /*WLAN_FEATURE_11BE_MLO_ADV_FEATURE*/
570 #else /*WLAN_FEATURE_11BE_MLO*/
571 static inline void
cm_clear_vdev_mlo_cap(struct wlan_objmgr_vdev * vdev,struct wlan_cm_discon_rsp * rsp)572 cm_clear_vdev_mlo_cap(struct wlan_objmgr_vdev *vdev,
573 		      struct wlan_cm_discon_rsp *rsp)
574 { }
575 #endif /*WLAN_FEATURE_11BE_MLO*/
576 
cm_notify_disconnect_complete(struct cnx_mgr * cm_ctx,struct wlan_cm_discon_rsp * resp)577 QDF_STATUS cm_notify_disconnect_complete(struct cnx_mgr *cm_ctx,
578 					 struct wlan_cm_discon_rsp *resp)
579 {
580 	mlme_cm_disconnect_complete_ind(cm_ctx->vdev, resp);
581 	mlo_sta_link_disconn_notify(cm_ctx->vdev, resp);
582 	mlme_cm_osif_disconnect_complete(cm_ctx->vdev, resp);
583 	cm_if_mgr_inform_disconnect_complete(cm_ctx->vdev);
584 	cm_inform_dlm_disconnect_complete(cm_ctx->vdev, resp);
585 
586 	return QDF_STATUS_SUCCESS;
587 }
588 
cm_disconnect_complete(struct cnx_mgr * cm_ctx,struct wlan_cm_discon_rsp * resp)589 QDF_STATUS cm_disconnect_complete(struct cnx_mgr *cm_ctx,
590 				  struct wlan_cm_discon_rsp *resp)
591 {
592 	QDF_STATUS link_switch_status = QDF_STATUS_SUCCESS;
593 	bool is_link_switch_cmd = cm_is_link_switch_disconnect_resp(resp);
594 
595 	/*
596 	 * If the entry is not present in the list, it must have been cleared
597 	 * already.
598 	 */
599 	if (!cm_get_req_by_cm_id(cm_ctx, resp->req.cm_id))
600 		return QDF_STATUS_SUCCESS;
601 
602 	cm_notify_disconnect_complete(cm_ctx, resp);
603 
604 	/* Is any connect or disconnect request in queue, abort link switch
605 	 * by sending failure status for disconnect
606 	 */
607 	if ((cm_ctx->disconnect_count > 1 || cm_ctx->connect_count) &&
608 	    is_link_switch_cmd) {
609 		link_switch_status = QDF_STATUS_E_ABORTED;
610 	}
611 
612 	/*
613 	 * Remove all pending disconnect if this is an active disconnect
614 	 * complete.
615 	 */
616 	if (resp->req.cm_id == cm_ctx->active_cm_id && !is_link_switch_cmd)
617 		cm_flush_pending_request(cm_ctx, DISCONNECT_REQ_PREFIX, false);
618 
619 	cm_remove_cmd(cm_ctx, &resp->req.cm_id);
620 	mlme_debug(CM_PREFIX_FMT "disconnect count %d connect count %d",
621 		   CM_PREFIX_REF(wlan_vdev_get_id(cm_ctx->vdev),
622 				 resp->req.cm_id),
623 		   cm_ctx->disconnect_count, cm_ctx->connect_count);
624 	/* Flush failed connect req as pending disconnect is completed */
625 	if (!cm_ctx->disconnect_count && cm_ctx->connect_count &&
626 	    !is_link_switch_cmd) {
627 		cm_flush_pending_request(cm_ctx, CONNECT_REQ_PREFIX, true);
628 	}
629 
630 	/* Set the disconnect wait event once all disconnect are completed */
631 	if (!cm_ctx->disconnect_count && !is_link_switch_cmd) {
632 		/*
633 		 * Clear MLO cap only when it is the last disconnect req
634 		 * For 1x/owe roaming, link vdev mlo flags are not cleared
635 		 * as connect req is queued on link vdev after this.
636 		 */
637 		if (!wlan_cm_check_mlo_roam_auth_status(cm_ctx->vdev))
638 			cm_clear_vdev_mlo_cap(cm_ctx->vdev, resp);
639 		qdf_event_set(&cm_ctx->disconnect_complete);
640 	}
641 
642 	if (is_link_switch_cmd) {
643 		cm_reset_active_cm_id(cm_ctx->vdev, resp->req.cm_id);
644 		mlo_mgr_link_switch_disconnect_done(cm_ctx->vdev,
645 						    link_switch_status,
646 						    is_link_switch_cmd);
647 	}
648 
649 	return QDF_STATUS_SUCCESS;
650 }
651 
652 QDF_STATUS
cm_handle_discon_req_in_non_connected_state(struct cnx_mgr * cm_ctx,struct cm_disconnect_req * cm_req,enum wlan_cm_sm_state cm_state_substate)653 cm_handle_discon_req_in_non_connected_state(struct cnx_mgr *cm_ctx,
654 					struct cm_disconnect_req *cm_req,
655 					enum wlan_cm_sm_state cm_state_substate)
656 {
657 	enum wlan_cm_sm_state cur_state = cm_get_state(cm_ctx);
658 	uint8_t vdev_id = wlan_vdev_get_id(cm_ctx->vdev);
659 
660 	/*
661 	 * South bound and peer disconnect requests are meant for only in
662 	 * connected state, so if the state is connecting a new connect has
663 	 * been received, hence skip the non-osif disconnect request. Also allow
664 	 * MLO link vdev disconnect in connecting state, as this can be
665 	 * initiated due to disconnect on assoc vdev, which may be in connected
666 	 * state.
667 	 */
668 	if (cur_state == WLAN_CM_S_CONNECTING &&
669 	    (cm_req->req.source != CM_OSIF_DISCONNECT &&
670 	    cm_req->req.source != CM_OSIF_CFG_DISCONNECT &&
671 	    cm_req->req.source != CM_MLO_LINK_VDEV_DISCONNECT)) {
672 		mlme_info(CM_PREFIX_FMT "ignore disconnect req from source %d in state %d",
673 			  CM_PREFIX_REF(vdev_id, cm_req->cm_id),
674 			  cm_req->req.source, cm_state_substate);
675 		return QDF_STATUS_E_INVAL;
676 	}
677 
678 	/* Reject any link switch disconnect request
679 	 * while in disconnecting state
680 	 */
681 	if (cm_is_link_switch_disconnect_req(cm_req)) {
682 		mlme_info(CM_PREFIX_FMT "Ignore disconnect req from source %d state %d",
683 			  CM_PREFIX_REF(vdev_id, cm_req->cm_id),
684 			  cm_req->req.source, cm_state_substate);
685 		return QDF_STATUS_E_INVAL;
686 	}
687 
688 	switch (cm_state_substate) {
689 	case WLAN_CM_S_DISCONNECTING:
690 		/*
691 		 * There would be pending disconnect requests in the list, and
692 		 * if they are flushed as part of new disconnect
693 		 * (cm_flush_pending_request), OS_IF would inform the kernel
694 		 * about the disconnect done even though the disconnect is still
695 		 * pending. So update OS_IF with invalid CM_ID so that the resp
696 		 * of only the new disconnect req is given to kernel.
697 		 */
698 		mlme_cm_osif_update_id_and_src(cm_ctx->vdev,
699 					       CM_SOURCE_INVALID,
700 					       CM_ID_INVALID);
701 
702 		/* Flush for non mlo link vdev only */
703 		if (!wlan_vdev_mlme_is_mlo_vdev(cm_ctx->vdev) ||
704 		    !wlan_vdev_mlme_is_mlo_link_vdev(cm_ctx->vdev))
705 			cm_flush_pending_request(cm_ctx, DISCONNECT_REQ_PREFIX,
706 						 false);
707 		/*
708 		 * Flush failed pending connect req as new req is received
709 		 * and its no longer the latest one.
710 		 */
711 		if (cm_ctx->connect_count)
712 			cm_flush_pending_request(cm_ctx, CONNECT_REQ_PREFIX,
713 						 true);
714 		break;
715 	case WLAN_CM_S_ROAMING:
716 		/* for FW roam/LFR3 remove the req from the list */
717 		if (cm_roam_offload_enabled(wlan_vdev_get_psoc(cm_ctx->vdev)))
718 			cm_flush_pending_request(cm_ctx, ROAM_REQ_PREFIX,
719 						 false);
720 		fallthrough;
721 	case WLAN_CM_SS_JOIN_ACTIVE:
722 		/*
723 		 * In join active/roaming state, there would be no pending
724 		 * command, so no action required. so for new disconnect
725 		 * request, queue disconnect and move the state to
726 		 * disconnecting.
727 		 */
728 		break;
729 	case WLAN_CM_SS_SCAN:
730 		/* In the scan state abort the ongoing scan */
731 		cm_vdev_scan_cancel(wlan_vdev_get_pdev(cm_ctx->vdev),
732 				    cm_ctx->vdev);
733 		fallthrough;
734 	case WLAN_CM_SS_JOIN_PENDING:
735 		/*
736 		 * There would be pending disconnect requests in the list, and
737 		 * if they are flushed as part of new disconnect
738 		 * (cm_flush_pending_request), OS_IF would inform the kernel
739 		 * about the disconnect done even though the disconnect is still
740 		 * pending. So update OS_IF with invalid CM_ID so that the resp
741 		 * of only the new disconnect req is given to kernel.
742 		 */
743 		mlme_cm_osif_update_id_and_src(cm_ctx->vdev,
744 					       CM_SOURCE_INVALID,
745 					       CM_ID_INVALID);
746 		/*
747 		 * In case of scan or join pending there could be a connect and
748 		 * disconnect requests pending, so flush all the requests except
749 		 * the activated request.
750 		 */
751 		cm_flush_pending_request(cm_ctx, CONNECT_REQ_PREFIX, false);
752 		cm_flush_pending_request(cm_ctx, DISCONNECT_REQ_PREFIX, false);
753 		break;
754 	case WLAN_CM_S_INIT:
755 		/*
756 		 * In this case the vdev is already disconnected and thus the
757 		 * indication to upper layer, would have been sent as part of
758 		 * previous disconnect/connect failure.
759 		 *
760 		 * If upper layer is in process of connecting, sending
761 		 * disconnect indication back again may cause it to incorrectly
762 		 * think it as a connect failure. So sending disconnect
763 		 * indication again is not advisable.
764 		 *
765 		 * So no need to do anything here, just return failure and drop
766 		 * disconnect.
767 		 */
768 
769 		mlme_info(CM_PREFIX_FMT "dropping disconnect req from source %d in INIT state",
770 			  CM_PREFIX_REF(vdev_id, cm_req->cm_id),
771 			  cm_req->req.source);
772 
773 		return QDF_STATUS_E_ALREADY;
774 	case WLAN_CM_SS_IDLE_DUE_TO_LINK_SWITCH:
775 		/*
776 		 * Notification to userspace is done on non-LINK VDEV in case of
777 		 * MLO connection, and if assoc VDEV is in INIT state due to
778 		 * link switch disconnect and dropping userspace disconnect
779 		 * might lead to not notifying kernel and any further connect
780 		 * requests from supplicant will be dropped by kernel saying
781 		 * already connected and supplicant will immediately attempt
782 		 * disconnect which will again gets dropped.
783 		 * Notify MLO manager to terminate link switch operation and
784 		 * instead of dropping the disconnect forcefully move VDEV state
785 		 * to disconnecting and add disconnect request to queue so that
786 		 * kernel and driver will be in sync.
787 		 */
788 		if (wlan_vdev_mlme_is_mlo_link_switch_in_progress(cm_ctx->vdev)) {
789 			mlme_info(CM_PREFIX_FMT "Notfiy MLO MGR to abort link switch",
790 				  CM_PREFIX_REF(vdev_id, cm_req->cm_id));
791 			mlo_mgr_link_switch_disconnect_done(cm_ctx->vdev,
792 							    QDF_STATUS_E_ABORTED,
793 							    false);
794 		}
795 
796 		break;
797 	default:
798 		mlme_err(CM_PREFIX_FMT "disconnect req in invalid state %d",
799 			 CM_PREFIX_REF(vdev_id, cm_req->cm_id),
800 			 cm_state_substate);
801 		return QDF_STATUS_E_FAILURE;
802 	};
803 
804 	/* Queue the new disconnect request after state specific actions */
805 	return cm_add_disconnect_req_to_list(cm_ctx, cm_req);
806 }
807 
cm_add_disconnect_req_to_list(struct cnx_mgr * cm_ctx,struct cm_disconnect_req * req)808 QDF_STATUS cm_add_disconnect_req_to_list(struct cnx_mgr *cm_ctx,
809 					 struct cm_disconnect_req *req)
810 {
811 	QDF_STATUS status;
812 	struct cm_req *cm_req;
813 
814 	cm_req = qdf_container_of(req, struct cm_req, discon_req);
815 	req->cm_id = cm_get_cm_id(cm_ctx, req->req.source);
816 	cm_req->cm_id = req->cm_id;
817 	status = cm_add_req_to_list_and_indicate_osif(cm_ctx, cm_req,
818 						      req->req.source);
819 
820 	return status;
821 }
822 
cm_disconnect_start_req(struct wlan_objmgr_vdev * vdev,struct wlan_cm_disconnect_req * req)823 QDF_STATUS cm_disconnect_start_req(struct wlan_objmgr_vdev *vdev,
824 				   struct wlan_cm_disconnect_req *req)
825 {
826 	struct cnx_mgr *cm_ctx;
827 	struct cm_req *cm_req;
828 	struct cm_disconnect_req *disconnect_req;
829 	QDF_STATUS status;
830 
831 	cm_ctx = cm_get_cm_ctx(vdev);
832 	if (!cm_ctx)
833 		return QDF_STATUS_E_INVAL;
834 
835 	/*
836 	 * This would be freed as part of removal from cm req list if adding
837 	 * to list is success after posting WLAN_CM_SM_EV_DISCONNECT_REQ.
838 	 */
839 	cm_req = qdf_mem_malloc(sizeof(*cm_req));
840 
841 	if (!cm_req)
842 		return QDF_STATUS_E_NOMEM;
843 
844 	if (wlan_vdev_mlme_is_mlo_vdev(vdev) &&
845 	    !wlan_vdev_mlme_is_mlo_link_vdev(vdev))
846 		req->is_no_disassoc_disconnect = 1;
847 
848 	disconnect_req = &cm_req->discon_req;
849 	disconnect_req->req = *req;
850 
851 	status = cm_sm_deliver_event(vdev, WLAN_CM_SM_EV_DISCONNECT_REQ,
852 				     sizeof(*disconnect_req), disconnect_req);
853 	/* free the req if disconnect is not handled */
854 	if (QDF_IS_STATUS_ERROR(status))
855 		qdf_mem_free(cm_req);
856 
857 	return status;
858 }
859 
cm_disconnect_start_req_sync(struct wlan_objmgr_vdev * vdev,struct wlan_cm_disconnect_req * req)860 QDF_STATUS cm_disconnect_start_req_sync(struct wlan_objmgr_vdev *vdev,
861 					struct wlan_cm_disconnect_req *req)
862 {
863 	struct cnx_mgr *cm_ctx;
864 	QDF_STATUS status;
865 	uint32_t timeout;
866 	bool is_assoc_vdev = false;
867 	uint8_t vdev_id = wlan_vdev_get_id(vdev);
868 
869 	cm_ctx = cm_get_cm_ctx(vdev);
870 	if (!cm_ctx)
871 		return QDF_STATUS_E_INVAL;
872 
873 	if (wlan_vdev_mlme_is_mlo_vdev(vdev) &&
874 	    !wlan_vdev_mlme_is_mlo_link_vdev(vdev)) {
875 		req->is_no_disassoc_disconnect = 1;
876 		is_assoc_vdev = true;
877 	}
878 	qdf_event_reset(&cm_ctx->disconnect_complete);
879 	status = cm_disconnect_start_req(vdev, req);
880 	if (QDF_IS_STATUS_ERROR(status)) {
881 		mlme_err("vdev %d: Disconnect failed with status %d", vdev_id,
882 			 status);
883 		return status;
884 	}
885 
886 	if (is_assoc_vdev)
887 		timeout = CM_DISCONNECT_CMD_TIMEOUT +
888 			  CM_DISCONNECT_ASSOC_VDEV_EXTRA_TIMEOUT;
889 	else
890 		timeout = CM_DISCONNECT_CMD_TIMEOUT;
891 	status = qdf_wait_single_event(&cm_ctx->disconnect_complete,
892 				       timeout);
893 	if (QDF_IS_STATUS_ERROR(status))
894 		mlme_err("vdev %d: Disconnect timeout with status %d", vdev_id,
895 			 status);
896 
897 	return status;
898 }
899 
cm_disconnect_rsp(struct wlan_objmgr_vdev * vdev,struct wlan_cm_discon_rsp * resp)900 QDF_STATUS cm_disconnect_rsp(struct wlan_objmgr_vdev *vdev,
901 			     struct wlan_cm_discon_rsp *resp)
902 {
903 	struct cnx_mgr *cm_ctx;
904 	QDF_STATUS qdf_status;
905 	wlan_cm_id cm_id;
906 	uint32_t prefix;
907 
908 	cm_ctx = cm_get_cm_ctx(vdev);
909 	if (!cm_ctx)
910 		return QDF_STATUS_E_INVAL;
911 
912 	cm_id = cm_ctx->active_cm_id;
913 	prefix = CM_ID_GET_PREFIX(cm_id);
914 
915 	if (prefix != DISCONNECT_REQ_PREFIX || cm_id != resp->req.cm_id) {
916 		mlme_err(CM_PREFIX_FMT "Active cm_id 0x%x is different",
917 			 CM_PREFIX_REF(wlan_vdev_get_id(vdev), resp->req.cm_id),
918 			 cm_id);
919 		qdf_status = QDF_STATUS_E_FAILURE;
920 		goto disconnect_complete;
921 	}
922 	qdf_status =
923 		cm_sm_deliver_event(vdev,
924 				    WLAN_CM_SM_EV_DISCONNECT_DONE,
925 				    sizeof(*resp), resp);
926 	if (QDF_IS_STATUS_ERROR(qdf_status))
927 		goto disconnect_complete;
928 
929 	return qdf_status;
930 
931 disconnect_complete:
932 	/*
933 	 * If there is a event posting error it means the SM state is not in
934 	 * DISCONNECTING (some new cmd has changed the state of SM), so just
935 	 * complete the disconnect command.
936 	 */
937 	return cm_disconnect_complete(cm_ctx, resp);
938 }
939 
cm_bss_peer_delete_req(struct wlan_objmgr_vdev * vdev,struct qdf_mac_addr * peer_mac)940 QDF_STATUS cm_bss_peer_delete_req(struct wlan_objmgr_vdev *vdev,
941 				  struct qdf_mac_addr *peer_mac)
942 {
943 	mlme_debug("vdev %d: delete peer" QDF_MAC_ADDR_FMT,
944 		   wlan_vdev_get_id(vdev), QDF_MAC_ADDR_REF(peer_mac->bytes));
945 
946 	return mlme_cm_bss_peer_delete_req(vdev);
947 }
948 
cm_vdev_down_req(struct wlan_objmgr_vdev * vdev,uint32_t status)949 QDF_STATUS cm_vdev_down_req(struct wlan_objmgr_vdev *vdev, uint32_t status)
950 {
951 	mlme_debug("vdev %d: down req status %d",
952 		   wlan_vdev_get_id(vdev), status);
953 
954 	return mlme_cm_vdev_down_req(vdev);
955 }
956