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