Przeglądaj źródła

qcacmn: New substate for VDEV in INIT due to link switch

If connection fails, CM state will be moved to INIT state and
kernel will not be made aware of this connection failure in case
of link switch and treats the interface as if it is connected.
In such case kernel and host will be out of sync and because of
this if any disconnect request is received host will drop them
immediately as VDEV is in INIT state.

Introduce new substate of VDEV for INIT state. If VDEV moves to
INIT state either due to link switch disconnect or link switch
connect failure, instead of being in INIT-IDLE state move VDEV
to INIT-IDLE_DUE_TO_LINK_SWITCH. If any disconnect request is
received in INIT-IDLE_DUE_TO_LINK_SWITCH state then instead of
dropping the request forcefully queue the disconnect and proceed
for disconnect to notify kernel.

Drop link switch connect will be dropped if VDEV is not in
INIT-IDLE_DUE_TO_LINK_SWITCH state.

Change-Id: Ifb6b72f0720b95cf862e9a4ff756b6577a3e45fd
CRs-Fixed: 3617919
Vinod Kumar Pirla 1 rok temu
rodzic
commit
b1f923bc46

+ 3 - 2
os_if/linux/mlme/src/osif_cm_connect_rsp.c

@@ -1054,7 +1054,7 @@ static inline
 bool osif_cm_is_unlink_bss_required(struct wlan_cm_connect_resp *rsp)
 bool osif_cm_is_unlink_bss_required(struct wlan_cm_connect_resp *rsp)
 {
 {
 	if (QDF_IS_STATUS_SUCCESS(rsp->connect_status) ||
 	if (QDF_IS_STATUS_SUCCESS(rsp->connect_status) ||
-	    rsp->cm_id & CM_ID_LSWITCH_BIT)
+	    ucfg_cm_is_link_switch_connect_resp(rsp))
 		return false;
 		return false;
 
 
 	if (rsp->reason == CM_NO_CANDIDATE_FOUND ||
 	if (rsp->reason == CM_NO_CANDIDATE_FOUND ||
@@ -1114,7 +1114,8 @@ QDF_STATUS osif_connect_handler(struct wlan_objmgr_vdev *vdev,
 	osif_check_and_unlink_bss(vdev, rsp);
 	osif_check_and_unlink_bss(vdev, rsp);
 
 
 	status = osif_validate_connect_and_reset_src_id(osif_priv, rsp);
 	status = osif_validate_connect_and_reset_src_id(osif_priv, rsp);
-	if (QDF_IS_STATUS_ERROR(status) || rsp->cm_id & CM_ID_LSWITCH_BIT) {
+	if (QDF_IS_STATUS_ERROR(status) ||
+	    ucfg_cm_is_link_switch_connect_resp(rsp)) {
 		osif_cm_connect_comp_ind(vdev, rsp, OSIF_NOT_HANDLED);
 		osif_cm_connect_comp_ind(vdev, rsp, OSIF_NOT_HANDLED);
 		return status;
 		return status;
 	}
 	}

+ 1 - 1
os_if/linux/mlme/src/osif_cm_disconnect_rsp.c

@@ -56,7 +56,7 @@ osif_validate_disconnect_and_reset_src_id(struct vdev_osif_priv *osif_priv,
 	qdf_spinlock_acquire(&osif_priv->cm_info.cmd_id_lock);
 	qdf_spinlock_acquire(&osif_priv->cm_info.cmd_id_lock);
 	if (rsp->req.req.source == CM_INTERNAL_DISCONNECT ||
 	if (rsp->req.req.source == CM_INTERNAL_DISCONNECT ||
 	    rsp->req.req.source == CM_MLO_ROAM_INTERNAL_DISCONNECT ||
 	    rsp->req.req.source == CM_MLO_ROAM_INTERNAL_DISCONNECT ||
-	    rsp->req.req.source == CM_MLO_LINK_SWITCH_DISCONNECT) {
+	    ucfg_cm_is_link_switch_disconnect_resp(rsp)) {
 		osif_debug("ignore internal disconnect");
 		osif_debug("ignore internal disconnect");
 		status = QDF_STATUS_E_INVAL;
 		status = QDF_STATUS_E_INVAL;
 		goto rel_lock;
 		goto rel_lock;

+ 4 - 4
umac/mlme/connection_mgr/core/src/wlan_cm_connect.c

@@ -1750,7 +1750,7 @@ cm_handle_connect_req_in_non_init_state(struct cnx_mgr *cm_ctx,
 	}
 	}
 
 
 	/* Reject any link switch connect request while in non-init state */
 	/* Reject any link switch connect request while in non-init state */
-	if (cm_req->req.source == CM_MLO_LINK_SWITCH_CONNECT) {
+	if (cm_is_link_switch_connect_req(cm_req)) {
 		mlme_info(CM_PREFIX_FMT "Ignore connect req from source %d state %d",
 		mlme_info(CM_PREFIX_FMT "Ignore connect req from source %d state %d",
 			  CM_PREFIX_REF(vdev_id, cm_req->cm_id),
 			  CM_PREFIX_REF(vdev_id, cm_req->cm_id),
 			  cm_req->req.source, cm_state_substate);
 			  cm_req->req.source, cm_state_substate);
@@ -1902,7 +1902,7 @@ QDF_STATUS cm_connect_start(struct cnx_mgr *cm_ctx,
 		return QDF_STATUS_SUCCESS;
 		return QDF_STATUS_SUCCESS;
 	}
 	}
 
 
-	if (cm_req->req.source == CM_MLO_LINK_SWITCH_CONNECT) {
+	if (cm_is_link_switch_connect_req(cm_req)) {
 		/* The error handling has to be different here.not corresponds
 		/* The error handling has to be different here.not corresponds
 		 * to connect req serialization now.
 		 * to connect req serialization now.
 		 */
 		 */
@@ -2988,7 +2988,7 @@ QDF_STATUS cm_notify_connect_complete(struct cnx_mgr *cm_ctx,
 					  resp->connect_status);
 					  resp->connect_status);
 	cm_inform_dlm_connect_complete(cm_ctx->vdev, resp);
 	cm_inform_dlm_connect_complete(cm_ctx->vdev, resp);
 	if (QDF_IS_STATUS_ERROR(resp->connect_status) &&
 	if (QDF_IS_STATUS_ERROR(resp->connect_status) &&
-	    sm_state == WLAN_CM_S_INIT && !(resp->cm_id & CM_ID_LSWITCH_BIT))
+	    sm_state == WLAN_CM_S_INIT && !cm_is_link_switch_connect_resp(resp))
 		cm_clear_vdev_mlo_cap(cm_ctx->vdev);
 		cm_clear_vdev_mlo_cap(cm_ctx->vdev);
 
 
 	return QDF_STATUS_SUCCESS;
 	return QDF_STATUS_SUCCESS;
@@ -3050,7 +3050,7 @@ QDF_STATUS cm_connect_complete(struct cnx_mgr *cm_ctx,
 				 resp->cm_id));
 				 resp->cm_id));
 	cm_remove_cmd(cm_ctx, &resp->cm_id);
 	cm_remove_cmd(cm_ctx, &resp->cm_id);
 
 
-	if (resp->cm_id & CM_ID_LSWITCH_BIT) {
+	if (cm_is_link_switch_connect_resp(resp)) {
 		cm_reset_active_cm_id(cm_ctx->vdev, resp->cm_id);
 		cm_reset_active_cm_id(cm_ctx->vdev, resp->cm_id);
 		mlo_mgr_link_switch_connect_done(cm_ctx->vdev,
 		mlo_mgr_link_switch_connect_done(cm_ctx->vdev,
 						 resp->connect_status);
 						 resp->connect_status);

+ 18 - 17
umac/mlme/connection_mgr/core/src/wlan_cm_disconnect.c

@@ -319,6 +319,7 @@ QDF_STATUS cm_disconnect_start(struct cnx_mgr *cm_ctx,
 {
 {
 	struct wlan_objmgr_pdev *pdev;
 	struct wlan_objmgr_pdev *pdev;
 	QDF_STATUS status = QDF_STATUS_SUCCESS;
 	QDF_STATUS status = QDF_STATUS_SUCCESS;
+	bool is_link_switch_discon = cm_is_link_switch_disconnect_req(req);
 
 
 	pdev = wlan_vdev_get_pdev(cm_ctx->vdev);
 	pdev = wlan_vdev_get_pdev(cm_ctx->vdev);
 	if (!pdev) {
 	if (!pdev) {
@@ -326,8 +327,7 @@ QDF_STATUS cm_disconnect_start(struct cnx_mgr *cm_ctx,
 		return QDF_STATUS_E_INVAL;
 		return QDF_STATUS_E_INVAL;
 	}
 	}
 
 
-	if (wlan_vdev_mlme_is_mlo_vdev(cm_ctx->vdev) &&
-	    req->req.source != CM_MLO_LINK_SWITCH_DISCONNECT)
+	if (wlan_vdev_mlme_is_mlo_vdev(cm_ctx->vdev) && !is_link_switch_discon)
 		mlo_internal_disconnect_links(cm_ctx->vdev);
 		mlo_internal_disconnect_links(cm_ctx->vdev);
 
 
 	cm_vdev_scan_cancel(pdev, cm_ctx->vdev);
 	cm_vdev_scan_cancel(pdev, cm_ctx->vdev);
@@ -336,7 +336,7 @@ QDF_STATUS cm_disconnect_start(struct cnx_mgr *cm_ctx,
 	mlme_cm_osif_disconnect_start_ind(cm_ctx->vdev, req->req.source);
 	mlme_cm_osif_disconnect_start_ind(cm_ctx->vdev, req->req.source);
 
 
 	/* For link switch disconnect, don't serialize the command */
 	/* For link switch disconnect, don't serialize the command */
-	if (req->req.source != CM_MLO_LINK_SWITCH_DISCONNECT) {
+	if (!is_link_switch_discon) {
 		/* Serialize disconnect req, Handle failure status */
 		/* Serialize disconnect req, Handle failure status */
 		status = cm_ser_disconnect_req(pdev, cm_ctx, req);
 		status = cm_ser_disconnect_req(pdev, cm_ctx, req);
 	} else {
 	} else {
@@ -364,7 +364,7 @@ cm_update_scan_mlme_on_disconnect(struct wlan_objmgr_vdev *vdev,
 	/* Avoid setting the scan entry as not connected when it is
 	/* Avoid setting the scan entry as not connected when it is
 	 * due to link switch disconnect
 	 * due to link switch disconnect
 	 */
 	 */
-	if (req->req.source == CM_MLO_LINK_SWITCH_DISCONNECT)
+	if (cm_is_link_switch_disconnect_req(req))
 		return;
 		return;
 
 
 	pdev = wlan_vdev_get_pdev(vdev);
 	pdev = wlan_vdev_get_pdev(vdev);
@@ -598,7 +598,7 @@ QDF_STATUS cm_disconnect_complete(struct cnx_mgr *cm_ctx,
 				  struct wlan_cm_discon_rsp *resp)
 				  struct wlan_cm_discon_rsp *resp)
 {
 {
 	QDF_STATUS link_switch_status = QDF_STATUS_SUCCESS;
 	QDF_STATUS link_switch_status = QDF_STATUS_SUCCESS;
-	bool is_link_switch_cmd = resp->req.cm_id & CM_ID_LSWITCH_BIT;
+	bool is_link_switch_cmd = cm_is_link_switch_disconnect_resp(resp);
 
 
 	/*
 	/*
 	 * If the entry is not present in the list, it must have been cleared
 	 * If the entry is not present in the list, it must have been cleared
@@ -686,7 +686,7 @@ cm_handle_discon_req_in_non_connected_state(struct cnx_mgr *cm_ctx,
 	/* Reject any link switch disconnect request
 	/* Reject any link switch disconnect request
 	 * while in disconnecting state
 	 * while in disconnecting state
 	 */
 	 */
-	if (cm_req->req.source == CM_MLO_LINK_SWITCH_DISCONNECT) {
+	if (cm_is_link_switch_disconnect_req(cm_req)) {
 		mlme_info(CM_PREFIX_FMT "Ignore disconnect req from source %d state %d",
 		mlme_info(CM_PREFIX_FMT "Ignore disconnect req from source %d state %d",
 			  CM_PREFIX_REF(vdev_id, cm_req->cm_id),
 			  CM_PREFIX_REF(vdev_id, cm_req->cm_id),
 			  cm_req->req.source, cm_state_substate);
 			  cm_req->req.source, cm_state_substate);
@@ -772,10 +772,18 @@ cm_handle_discon_req_in_non_connected_state(struct cnx_mgr *cm_ctx,
 		 *
 		 *
 		 * So no need to do anything here, just return failure and drop
 		 * So no need to do anything here, just return failure and drop
 		 * disconnect.
 		 * disconnect.
-		 *
+		 */
+
+		mlme_info(CM_PREFIX_FMT "dropping disconnect req from source %d in INIT state",
+			  CM_PREFIX_REF(vdev_id, cm_req->cm_id),
+			  cm_req->req.source);
+
+		return QDF_STATUS_E_ALREADY;
+	case WLAN_CM_SS_IDLE_DUE_TO_LINK_SWITCH:
+		/*
 		 * Notification to userspace is done on non-LINK VDEV in case of
 		 * Notification to userspace is done on non-LINK VDEV in case of
 		 * MLO connection, and if assoc VDEV is in INIT state due to
 		 * MLO connection, and if assoc VDEV is in INIT state due to
-		 * link switch disconnect and dropping userspace disconnect here
+		 * link switch disconnect and dropping userspace disconnect
 		 * might lead to not notifying kernel and any further connect
 		 * might lead to not notifying kernel and any further connect
 		 * requests from supplicant will be dropped by kernel saying
 		 * requests from supplicant will be dropped by kernel saying
 		 * already connected and supplicant will immediately attempt
 		 * already connected and supplicant will immediately attempt
@@ -785,22 +793,15 @@ cm_handle_discon_req_in_non_connected_state(struct cnx_mgr *cm_ctx,
 		 * to disconnecting and add disconnect request to queue so that
 		 * to disconnecting and add disconnect request to queue so that
 		 * kernel and driver will be in sync.
 		 * kernel and driver will be in sync.
 		 */
 		 */
-		if (cm_req->req.source != CM_MLO_LINK_SWITCH_DISCONNECT &&
-		    wlan_vdev_mlme_is_mlo_link_switch_in_progress(cm_ctx->vdev)) {
+		if (wlan_vdev_mlme_is_mlo_link_switch_in_progress(cm_ctx->vdev)) {
 			mlme_info(CM_PREFIX_FMT "Notfiy MLO MGR to abort link switch",
 			mlme_info(CM_PREFIX_FMT "Notfiy MLO MGR to abort link switch",
 				  CM_PREFIX_REF(vdev_id, cm_req->cm_id));
 				  CM_PREFIX_REF(vdev_id, cm_req->cm_id));
 			mlo_mgr_link_switch_disconnect_done(cm_ctx->vdev,
 			mlo_mgr_link_switch_disconnect_done(cm_ctx->vdev,
 							    QDF_STATUS_E_ABORTED,
 							    QDF_STATUS_E_ABORTED,
 							    false);
 							    false);
-			break;
-
-		} else {
-			mlme_info(CM_PREFIX_FMT "dropping disconnect req from source %d in INIT state",
-				  CM_PREFIX_REF(vdev_id, cm_req->cm_id),
-				  cm_req->req.source);
 		}
 		}
 
 
-		return QDF_STATUS_E_ALREADY;
+		break;
 	default:
 	default:
 		mlme_err(CM_PREFIX_FMT "disconnect req in invalid state %d",
 		mlme_err(CM_PREFIX_FMT "disconnect req in invalid state %d",
 			 CM_PREFIX_REF(vdev_id, cm_req->cm_id),
 			 CM_PREFIX_REF(vdev_id, cm_req->cm_id),

+ 4 - 1
umac/mlme/connection_mgr/core/src/wlan_cm_main.h

@@ -63,6 +63,8 @@
  * @WLAN_CM_SS_REASSOC:                 Roam substate for reassoc state
  * @WLAN_CM_SS_REASSOC:                 Roam substate for reassoc state
  * @WLAN_CM_SS_ROAM_STARTED:            Roaming in progress (LFR 3.0)
  * @WLAN_CM_SS_ROAM_STARTED:            Roaming in progress (LFR 3.0)
  * @WLAN_CM_SS_ROAM_SYNC:               Roam sync indication from FW
  * @WLAN_CM_SS_ROAM_SYNC:               Roam sync indication from FW
+ * @WLAN_CM_SS_IDLE_DUE_TO_LINK_SWITCH: Substate when VDEV moves to INIT state
+ *                                      due to link switch.
  * @WLAN_CM_SS_MAX:                     Max Substate
  * @WLAN_CM_SS_MAX:                     Max Substate
  */
  */
 enum wlan_cm_sm_state {
 enum wlan_cm_sm_state {
@@ -80,7 +82,8 @@ enum wlan_cm_sm_state {
 	WLAN_CM_SS_REASSOC = 11,
 	WLAN_CM_SS_REASSOC = 11,
 	WLAN_CM_SS_ROAM_STARTED = 12,
 	WLAN_CM_SS_ROAM_STARTED = 12,
 	WLAN_CM_SS_ROAM_SYNC = 13,
 	WLAN_CM_SS_ROAM_SYNC = 13,
-	WLAN_CM_SS_MAX = 14,
+	WLAN_CM_SS_IDLE_DUE_TO_LINK_SWITCH = 14,
+	WLAN_CM_SS_MAX = 15,
 };
 };
 
 
 /**
 /**

+ 105 - 0
umac/mlme/connection_mgr/core/src/wlan_cm_main_api.h

@@ -637,6 +637,83 @@ QDF_STATUS cm_bss_select_ind_rsp(struct wlan_objmgr_vdev *vdev,
 {
 {
 	return QDF_STATUS_SUCCESS;
 	return QDF_STATUS_SUCCESS;
 }
 }
+
+/**
+ * cm_is_link_switch_connect_req() - API to check if connect request
+ * is for link switch.
+ * @req: Connect request
+ *
+ * Return true if the request for connection is due to link switch or else
+ * return false.
+ *
+ * Return: bool
+ */
+static inline bool cm_is_link_switch_connect_req(struct cm_connect_req *req)
+{
+	return req->req.source == CM_MLO_LINK_SWITCH_CONNECT;
+}
+
+/**
+ * cm_is_link_switch_disconnect_req() - API to check if disconnect request is
+ * for link switch.
+ * @req: Disconnect request.
+ *
+ * Return true if the request for disconnection is due to link switch or else
+ * return false.
+ *
+ * Return: bool
+ */
+static inline bool
+cm_is_link_switch_disconnect_req(struct cm_disconnect_req *req)
+{
+	return req->req.source == CM_MLO_LINK_SWITCH_DISCONNECT;
+}
+
+/**
+ * cm_is_link_switch_cmd() - Check if the CM ID is for link switch
+ * @cm_id: Connection manager request ID
+ *
+ * Return true if the bit corresponding to link switch is set for @cm_id or
+ * else return false.
+ *
+ * Return: bool
+ */
+static inline bool cm_is_link_switch_cmd(wlan_cm_id cm_id)
+{
+	return cm_id & CM_ID_LSWITCH_BIT;
+}
+
+/**
+ * cm_is_link_switch_disconnect_resp() - API to check if the disconnect
+ * response is for link switch.
+ * @resp: Disconnect response.
+ *
+ * Return true if the disconnect response is for link switch or else return
+ * false.
+ *
+ * Return: bool
+ */
+static inline bool
+cm_is_link_switch_disconnect_resp(struct wlan_cm_discon_rsp *resp)
+{
+	return cm_is_link_switch_cmd(resp->req.cm_id);
+}
+
+/**
+ * cm_is_link_switch_connect_resp() - API to check if the connect response
+ * is for link switch.
+ * @resp: Connect response.
+ *
+ * Return true if the connect response is for link switch or else return
+ * false.
+ *
+ * Return: bool
+ */
+static inline bool
+cm_is_link_switch_connect_resp(struct wlan_cm_connect_resp *resp)
+{
+	return cm_is_link_switch_cmd(resp->cm_id);
+}
 #else
 #else
 static inline void cm_store_wep_key(struct cnx_mgr *cm_ctx,
 static inline void cm_store_wep_key(struct cnx_mgr *cm_ctx,
 				    struct wlan_cm_connect_crypto_info *crypto,
 				    struct wlan_cm_connect_crypto_info *crypto,
@@ -672,6 +749,34 @@ cm_peer_create_on_bss_select_ind_resp(struct cnx_mgr *cm_ctx,
  */
  */
 QDF_STATUS cm_bss_select_ind_rsp(struct wlan_objmgr_vdev *vdev,
 QDF_STATUS cm_bss_select_ind_rsp(struct wlan_objmgr_vdev *vdev,
 				 QDF_STATUS status);
 				 QDF_STATUS status);
+
+static inline bool cm_is_link_switch_connect_req(struct cm_connect_req *req)
+{
+	return false;
+}
+
+static inline bool
+cm_is_link_switch_disconnect_req(struct cm_disconnect_req *req)
+{
+	return false;
+}
+
+static inline bool cm_is_link_switch_cmd(wlan_cm_id cm_id)
+{
+	return false;
+}
+
+static inline bool
+cm_is_link_switch_disconnect_resp(struct wlan_cm_discon_rsp *resp)
+{
+	return false;
+}
+
+static inline bool
+cm_is_link_switch_connect_resp(struct wlan_cm_connect_resp *resp)
+{
+	return false;
+}
 #endif
 #endif
 
 
 #ifdef WLAN_FEATURE_FILS_SK
 #ifdef WLAN_FEATURE_FILS_SK

+ 184 - 9
umac/mlme/connection_mgr/core/src/wlan_cm_sm.c

@@ -111,9 +111,39 @@ static bool cm_state_init_event(void *ctx, uint16_t event,
 		break;
 		break;
 	case WLAN_CM_SM_EV_CONNECT_FAILURE:
 	case WLAN_CM_SM_EV_CONNECT_FAILURE:
 		cm_connect_complete(cm_ctx, data);
 		cm_connect_complete(cm_ctx, data);
+
+		if (cm_is_link_switch_connect_resp(data)) {
+			/*
+			 * If non-link switch connect fails, kernel will be
+			 * notified so the driver and kernel are in sync,
+			 * but link switch is internal to driver and any failure
+			 * is not notified to kernel.
+			 * This can lead to kernel and driver going out of sync
+			 * and any new disconnect requests might get dropped as
+			 * CM is in INIT state and kernel will assume that
+			 * interface is still in connected state.
+			 * To handle this situation, change the substate of CM
+			 * to signify VDEV is in INIT state due to link switch,
+			 * so that any later disconnect requests will not be
+			 * dropped.
+			 */
+			cm_sm_transition_to(cm_ctx,
+					    WLAN_CM_SS_IDLE_DUE_TO_LINK_SWITCH);
+		}
 		break;
 		break;
 	case WLAN_CM_SM_EV_DISCONNECT_DONE:
 	case WLAN_CM_SM_EV_DISCONNECT_DONE:
 		cm_disconnect_complete(cm_ctx, data);
 		cm_disconnect_complete(cm_ctx, data);
+
+		if (cm_is_link_switch_disconnect_resp(data)) {
+			/*
+			 * Change the substate of CM incase the disconnect
+			 * is due to link switch so that any disconnect requests
+			 * from NB/SB will not get dropped when handling those
+			 * in INIT state.
+			 */
+			cm_sm_transition_to(cm_ctx,
+					    WLAN_CM_SS_IDLE_DUE_TO_LINK_SWITCH);
+		}
 		break;
 		break;
 	case WLAN_CM_SM_EV_DISCONNECT_REQ:
 	case WLAN_CM_SM_EV_DISCONNECT_REQ:
 		status = cm_handle_discon_req_in_non_connected_state(cm_ctx, data,
 		status = cm_handle_discon_req_in_non_connected_state(cm_ctx, data,
@@ -124,16 +154,7 @@ static bool cm_state_init_event(void *ctx, uint16_t event,
 			 * dropped and return failure to the requester
 			 * dropped and return failure to the requester
 			 */
 			 */
 			event_handled = false;
 			event_handled = false;
-			break;
 		}
 		}
-
-		/*
-		 * If status is success, then forcefully queue
-		 * disconnect req and move VDEV state to disconnecting.
-		 */
-		cm_sm_transition_to(cm_ctx, WLAN_CM_S_DISCONNECTING);
-		cm_sm_deliver_event_sync(cm_ctx, WLAN_CM_SM_EV_DISCONNECT_START,
-					 data_len, data);
 		break;
 		break;
 	case WLAN_CM_SM_EV_ROAM_SYNC:
 	case WLAN_CM_SM_EV_ROAM_SYNC:
 		/**
 		/**
@@ -912,6 +933,150 @@ static bool cm_subst_join_active_event(void *ctx, uint16_t event,
 	return event_handled;
 	return event_handled;
 }
 }
 
 
+#ifdef CONN_MGR_ADV_FEATURE
+/**
+ * cm_subst_idle_due_to_link_switch_entry() - Entry API for idle due to
+ * link switch substate for connection manager.
+ * @ctx: Connection manager context
+ *
+ * API to perform entry operations to this substate.
+ */
+static void cm_subst_idle_due_to_link_switch_entry(void *ctx)
+{
+	struct cnx_mgr *cm_ctx = ctx;
+
+	if (cm_get_state(cm_ctx) != WLAN_CM_S_INIT)
+		QDF_BUG(0);
+
+	cm_set_substate(cm_ctx, WLAN_CM_SS_IDLE_DUE_TO_LINK_SWITCH);
+}
+
+/**
+ * cm_subst_idle_due_to_link_switch_exit() - Exit API from idle due to
+ * link switch substate for connection manager.
+ * @ctx: Connection manager context
+ *
+ * API to perform exit operations before leaving from this substate.
+ */
+static inline void cm_subst_idle_due_to_link_switch_exit(void *ctx)
+{
+}
+
+/**
+ * cm_subst_idle_due_to_link_switch_event() - Event handler API for idle
+ * due to link switch substate for connection manager.
+ * @ctx: connection manager ctx
+ * @event: event
+ * @data_len: length of @data
+ * @data: event data
+ *
+ * API to handle events in IDLE_DUE_TO_LINK_SWITCH substate.
+ * Return true if the event is handled or else return false.
+ *
+ * Return: bool
+ */
+static bool cm_subst_idle_due_to_link_switch_event(void *ctx, uint16_t event,
+						   uint16_t data_len,
+						   void *data)
+{
+	struct cnx_mgr *cm_ctx = ctx;
+	bool event_handled = true;
+	QDF_STATUS status;
+	enum wlan_cm_sm_state cm_state = WLAN_CM_SS_IDLE_DUE_TO_LINK_SWITCH;
+
+	switch (event) {
+	case WLAN_CM_SM_EV_CONNECT_REQ:
+		/*
+		 * If the connect request is due to link switch then
+		 * move the state to INIT to handle usual connect request.
+		 * Connect request due to link switch are only allowed in
+		 * INIT-IDLE and INIT-IDLE_DUE_TO_LINK_SWITCH states in all
+		 * other states the connect request will be rejected.
+		 *
+		 * As VDEV is in INIT state due to link switch and if connect
+		 * request is received from other than link switch, then
+		 * forcefully move VDEV to CONNECTED state, so the event follows
+		 * pre-link switch handling path.
+		 */
+		if (cm_is_link_switch_connect_req(data))
+			cm_sm_transition_to(cm_ctx, WLAN_CM_S_INIT);
+		else
+			cm_sm_transition_to(cm_ctx, WLAN_CM_S_CONNECTED);
+
+		status = cm_sm_deliver_event_sync(cm_ctx, event, data_len,
+						  data);
+		if (QDF_IS_STATUS_ERROR(status))
+			event_handled = false;
+		break;
+	case WLAN_CM_SM_EV_DISCONNECT_REQ:
+		status = cm_handle_discon_req_in_non_connected_state(cm_ctx,
+								     data,
+								     cm_state);
+		/*
+		 * To handle the case where disconnect request is due to
+		 * link switch, return error so that link switch will abort.
+		 */
+		if (QDF_IS_STATUS_ERROR(status)) {
+			event_handled = false;
+			break;
+		}
+		/*
+		 * If disconnect request for non-connected state is success,
+		 * then forcefully move VDEV to disconnecting state and start
+		 * the disconnect sequence.
+		 */
+		cm_sm_transition_to(cm_ctx, WLAN_CM_S_DISCONNECTING);
+		status = cm_sm_deliver_event_sync(cm_ctx,
+						  WLAN_CM_SM_EV_DISCONNECT_START,
+						  data_len, data);
+		if (QDF_IS_STATUS_ERROR(status))
+			event_handled = false;
+
+		break;
+	case WLAN_CM_SM_EV_ROAM_SYNC:
+		/*
+		 * If link switch fails on assoc VDEV and FW roams to new AP
+		 * then the ROAM_SYNC event will be dropped as ROAM_SYNC in
+		 * INIT state is only allowed for link VDEV. Hence move the VDEV
+		 * state to CONNECTED state to handle this event.
+		 */
+		cm_sm_transition_to(cm_ctx, WLAN_CM_S_CONNECTED);
+		status = cm_sm_deliver_event_sync(cm_ctx, event, data_len,
+						  data);
+		if (QDF_IS_STATUS_ERROR(status)) {
+			cm_sm_transition_to(cm_ctx, WLAN_CM_S_INIT);
+			event_handled = false;
+		}
+		break;
+	default:
+		/* Handle all other events in INIT state. */
+		cm_sm_transition_to(cm_ctx, WLAN_CM_S_INIT);
+		status = cm_sm_deliver_event_sync(cm_ctx, event, data_len,
+						  data);
+		if (QDF_IS_STATUS_ERROR(status))
+			event_handled = false;
+		break;
+	}
+
+	return event_handled;
+}
+#else
+static inline void cm_subst_idle_due_to_link_switch_entry(void *ctx)
+{
+}
+
+static inline void cm_subst_idle_due_to_link_switch_exit(void *ctx)
+{
+}
+
+static inline bool
+cm_subst_idle_due_to_link_switch_event(void *ctx, uint16_t event,
+				       uint16_t data_len, void *data)
+{
+	return false;
+}
+#endif
+
 struct wlan_sm_state_info cm_sm_info[] = {
 struct wlan_sm_state_info cm_sm_info[] = {
 	{
 	{
 		(uint8_t)WLAN_CM_S_INIT,
 		(uint8_t)WLAN_CM_S_INIT,
@@ -1053,6 +1218,16 @@ struct wlan_sm_state_info cm_sm_info[] = {
 		cm_subst_roam_sync_exit,
 		cm_subst_roam_sync_exit,
 		cm_subst_roam_sync_event
 		cm_subst_roam_sync_event
 	},
 	},
+	{
+		(uint8_t)WLAN_CM_SS_IDLE_DUE_TO_LINK_SWITCH,
+		(uint8_t)WLAN_SM_ENGINE_STATE_NONE,
+		(uint8_t)WLAN_SM_ENGINE_STATE_NONE,
+		false,
+		"IDLE_DUE_TO_LINK_SWITCH",
+		cm_subst_idle_due_to_link_switch_entry,
+		cm_subst_idle_due_to_link_switch_exit,
+		cm_subst_idle_due_to_link_switch_event
+	},
 	{
 	{
 		(uint8_t)WLAN_CM_SS_MAX,
 		(uint8_t)WLAN_CM_SS_MAX,
 		(uint8_t)WLAN_SM_ENGINE_STATE_NONE,
 		(uint8_t)WLAN_SM_ENGINE_STATE_NONE,

+ 3 - 3
umac/mlme/connection_mgr/core/src/wlan_cm_util.c

@@ -602,7 +602,7 @@ cm_handle_connect_flush(struct cnx_mgr *cm_ctx, struct cm_req *cm_req)
 	cm_notify_connect_complete(cm_ctx, resp, 0);
 	cm_notify_connect_complete(cm_ctx, resp, 0);
 
 
 	/* For link switch connect request, notify MLO mgr */
 	/* For link switch connect request, notify MLO mgr */
-	if (resp->cm_id & CM_ID_LSWITCH_BIT) {
+	if (cm_is_link_switch_connect_resp(resp)) {
 		cm_reset_active_cm_id(cm_ctx->vdev, resp->cm_id);
 		cm_reset_active_cm_id(cm_ctx->vdev, resp->cm_id);
 		mlo_mgr_link_switch_connect_done(cm_ctx->vdev,
 		mlo_mgr_link_switch_connect_done(cm_ctx->vdev,
 						 resp->connect_status);
 						 resp->connect_status);
@@ -638,7 +638,7 @@ cm_handle_disconnect_flush(struct cnx_mgr *cm_ctx, struct cm_req *cm_req)
 	 */
 	 */
 	mlme_cm_osif_disconnect_complete(cm_ctx->vdev, &resp);
 	mlme_cm_osif_disconnect_complete(cm_ctx->vdev, &resp);
 
 
-	if (resp.req.cm_id & CM_ID_LSWITCH_BIT) {
+	if (cm_is_link_switch_disconnect_resp(&resp)) {
 		cm_reset_active_cm_id(cm_ctx->vdev, resp.req.cm_id);
 		cm_reset_active_cm_id(cm_ctx->vdev, resp.req.cm_id);
 		mlo_mgr_link_switch_disconnect_done(cm_ctx->vdev,
 		mlo_mgr_link_switch_disconnect_done(cm_ctx->vdev,
 						    QDF_STATUS_E_ABORTED,
 						    QDF_STATUS_E_ABORTED,
@@ -1152,7 +1152,7 @@ void cm_remove_cmd(struct cnx_mgr *cm_ctx, wlan_cm_id *cm_id_to_remove)
 	if (QDF_IS_STATUS_ERROR(status))
 	if (QDF_IS_STATUS_ERROR(status))
 		return;
 		return;
 
 
-	if (cm_id & CM_ID_LSWITCH_BIT) {
+	if (cm_is_link_switch_cmd(cm_id)) {
 		mlme_debug("Skip cmd remove for link switch connect/disconnect");
 		mlme_debug("Skip cmd remove for link switch connect/disconnect");
 		return;
 		return;
 	}
 	}

+ 18 - 0
umac/mlme/connection_mgr/dispatcher/inc/wlan_cm_api.h

@@ -229,6 +229,24 @@ void wlan_cm_free_connect_resp(struct wlan_cm_connect_resp *connect_rsp);
  */
  */
 void wlan_cm_free_connect_req_param(struct wlan_cm_connect_req *req);
 void wlan_cm_free_connect_req_param(struct wlan_cm_connect_req *req);
 
 
+/**
+ * wlan_cm_is_link_switch_disconnect_resp() - Check if the disconnect response
+ * is for link switch request.
+ * @resp: Connection manager disconnect response.
+ *
+ * Return: bool
+ */
+bool wlan_cm_is_link_switch_disconnect_resp(struct wlan_cm_discon_rsp *resp);
+
+/**
+ * wlan_cm_is_link_switch_connect_resp() - Check if the connect response if for
+ * link switch request.
+ * @resp: Connection manager connect response.
+ *
+ * Return: bool
+ */
+bool wlan_cm_is_link_switch_connect_resp(struct wlan_cm_connect_resp *resp);
+
 #ifdef WLAN_FEATURE_ROAM_OFFLOAD
 #ifdef WLAN_FEATURE_ROAM_OFFLOAD
 /**
 /**
  * wlan_cm_is_vdev_roam_started() - check if vdev is in roaming state and
  * wlan_cm_is_vdev_roam_started() - check if vdev is in roaming state and

+ 18 - 0
umac/mlme/connection_mgr/dispatcher/inc/wlan_cm_ucfg_api.h

@@ -213,4 +213,22 @@ const char *ucfg_cm_reason_code_to_str(enum wlan_reason_code reason)
  * Return: enum band_info
  * Return: enum band_info
  */
  */
 enum band_info ucfg_cm_get_connected_band(struct wlan_objmgr_vdev *vdev);
 enum band_info ucfg_cm_get_connected_band(struct wlan_objmgr_vdev *vdev);
+
+/**
+ * ucfg_cm_is_link_switch_disconnect_resp() - Check if the disconnect response
+ * is for link switch request.
+ * @resp: Connection manager disconnect response.
+ *
+ * Return: bool
+ */
+bool ucfg_cm_is_link_switch_disconnect_resp(struct wlan_cm_discon_rsp *resp);
+
+/**
+ * ucfg_cm_is_link_switch_connect_resp() - Check if the connect response if for
+ * link switch request.
+ * @resp: Connection manager connect response.
+ *
+ * Return: bool
+ */
+bool ucfg_cm_is_link_switch_connect_resp(struct wlan_cm_connect_resp *resp);
 #endif /* __WLAN_CM_UCFG_API_H */
 #endif /* __WLAN_CM_UCFG_API_H */

+ 10 - 0
umac/mlme/connection_mgr/dispatcher/src/wlan_cm_api.c

@@ -250,6 +250,16 @@ bool wlan_cm_is_first_candidate_connect_attempt(struct wlan_objmgr_vdev *vdev)
 	return cm_is_first_candidate_connect_attempt(vdev);
 	return cm_is_first_candidate_connect_attempt(vdev);
 }
 }
 
 
+bool wlan_cm_is_link_switch_disconnect_resp(struct wlan_cm_discon_rsp *resp)
+{
+	return cm_is_link_switch_disconnect_resp(resp);
+}
+
+bool wlan_cm_is_link_switch_connect_resp(struct wlan_cm_connect_resp *resp)
+{
+	return cm_is_link_switch_connect_resp(resp);
+}
+
 #ifdef WLAN_FEATURE_HOST_ROAM
 #ifdef WLAN_FEATURE_HOST_ROAM
 bool wlan_cm_get_active_reassoc_req(struct wlan_objmgr_vdev *vdev,
 bool wlan_cm_get_active_reassoc_req(struct wlan_objmgr_vdev *vdev,
 				    struct wlan_cm_vdev_reassoc_req *req)
 				    struct wlan_cm_vdev_reassoc_req *req)

+ 11 - 1
umac/mlme/connection_mgr/dispatcher/src/wlan_cm_ucfg_api.c

@@ -113,7 +113,7 @@ enum band_info ucfg_cm_get_connected_band(struct wlan_objmgr_vdev *vdev)
 	struct wlan_channel *bss_chan;
 	struct wlan_channel *bss_chan;
 	uint32_t sta_freq = 0;
 	uint32_t sta_freq = 0;
 
 
-	if (wlan_cm_is_vdev_connected(vdev)) {
+	if (cm_is_vdev_connected(vdev)) {
 		bss_chan = wlan_vdev_mlme_get_bss_chan(vdev);
 		bss_chan = wlan_vdev_mlme_get_bss_chan(vdev);
 		sta_freq = bss_chan->ch_freq;
 		sta_freq = bss_chan->ch_freq;
 	}
 	}
@@ -127,3 +127,13 @@ enum band_info ucfg_cm_get_connected_band(struct wlan_objmgr_vdev *vdev)
 	else	/* If station is not connected return as BAND_ALL */
 	else	/* If station is not connected return as BAND_ALL */
 		return BAND_ALL;
 		return BAND_ALL;
 }
 }
+
+bool ucfg_cm_is_link_switch_disconnect_resp(struct wlan_cm_discon_rsp *resp)
+{
+	return cm_is_link_switch_disconnect_resp(resp);
+}
+
+bool ucfg_cm_is_link_switch_connect_resp(struct wlan_cm_connect_resp *resp)
+{
+	return cm_is_link_switch_connect_resp(resp);
+}

+ 2 - 2
umac/mlo_mgr/src/wlan_mlo_mgr_sta.c

@@ -1229,7 +1229,7 @@ void mlo_sta_link_connect_notify(struct wlan_objmgr_vdev *vdev,
 		return;
 		return;
 	}
 	}
 
 
-	if (rsp->cm_id & CM_ID_LSWITCH_BIT) {
+	if (wlan_cm_is_link_switch_connect_resp(rsp)) {
 		mlo_info("Skip for link switch connect request");
 		mlo_info("Skip for link switch connect request");
 		return;
 		return;
 	}
 	}
@@ -1649,7 +1649,7 @@ void mlo_sta_link_disconn_notify(struct wlan_objmgr_vdev *vdev,
 		}
 		}
 	}
 	}
 
 
-	if (resp->req.req.source != CM_MLO_LINK_SWITCH_DISCONNECT)
+	if (!wlan_cm_is_link_switch_disconnect_resp(resp))
 		mlo_handle_disconnect_resp(vdev, resp);
 		mlo_handle_disconnect_resp(vdev, resp);
 }
 }