Pārlūkot izejas kodu

qcacmn: Allow disconnect req in INIT state for link switch

VDEV is moved to INIT state as part of link switch disconnect,
before set MAC address response is received, any disconnect
request on this VDEV will not be handled as VDEV is in INIT
state, if link switch is in progress then it will abort link
switch and starts link switch dequeue process.

If the new disconnect request is from userspace it will
increment the OSIF ops, but if link switch is on assoc VDEV
OSIF is notified to restore the adapter deflink as part of
link switch complete where it wait for all OSIF ops to
complete. This is a deadlock case where driver is waiting
for ops completion on same thread where ops is initiated.

To fix this issue, do not handle link switch dequeue on the
same thread, instead move the link switch state to abort
and when actual link switch thread comes it will flush from
serialization.

If userspace disconnect is not queued as VDEV is in INIT
state due to link switch, kernel won't be notified about
the disconnect as this notification is only done on assoc
VDEV and any further connect requests from supplicant gets
dropped in kernel saying already connected and supplicant
will immediately try disconnect which driver will again
drop as VDEV is in INIT state. To avoid this kernel-driver
out of sync, forcefully move VDEV to disconnecting state
and queue the disconnect request.

Change-Id: I116859601ebba21d44797e74e160b56532ef833c
CRs-Fixed: 3588936
Vinod Kumar Pirla 1 gadu atpakaļ
vecāks
revīzija
9e3a7ecb78

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

@@ -55,7 +55,8 @@ osif_validate_disconnect_and_reset_src_id(struct vdev_osif_priv *osif_priv,
 	/* Always drop internal disconnect */
 	qdf_spinlock_acquire(&osif_priv->cm_info.cmd_id_lock);
 	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) {
 		osif_debug("ignore internal disconnect");
 		status = QDF_STATUS_E_INVAL;
 		goto rel_lock;
@@ -323,8 +324,7 @@ QDF_STATUS osif_disconnect_handler(struct wlan_objmgr_vdev *vdev,
 		osif_cm_unlink_bss(vdev, &rsp->req.req.bssid);
 
 	status = osif_validate_disconnect_and_reset_src_id(osif_priv, rsp);
-	if (QDF_IS_STATUS_ERROR(status) ||
-	    rsp->req.req.source == CM_MLO_LINK_SWITCH_DISCONNECT) {
+	if (QDF_IS_STATUS_ERROR(status)) {
 		osif_cm_disconnect_comp_ind(vdev, rsp, OSIF_NOT_HANDLED);
 		return status;
 	}

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

@@ -1682,7 +1682,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 */
 	if (cm_req->req.source == CM_MLO_LINK_SWITCH_CONNECT) {
-		mlme_info(CM_PREFIX_FMT "Ignore disconnect 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_req->req.source, cm_state_substate);
 		return QDF_STATUS_E_INVAL;

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

@@ -639,7 +639,8 @@ QDF_STATUS cm_disconnect_complete(struct cnx_mgr *cm_ctx,
 	if (is_link_switch_cmd) {
 		cm_reset_active_cm_id(cm_ctx->vdev, resp->req.cm_id);
 		mlo_mgr_link_switch_disconnect_done(cm_ctx->vdev,
-						    link_switch_status);
+						    link_switch_status,
+						    is_link_switch_cmd);
 	}
 
 	return QDF_STATUS_SUCCESS;
@@ -760,13 +761,27 @@ 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
 		 * disconnect.
+		 *
+		 * Notification to userspace is done on non-LINK VDEV in case of
+		 * MLO connection, and if assoc VDEV is in INIT state due to
+		 * link switch disconnect and dropping userspace disconnect here
+		 * might lead to not notifying kernel and any further connect
+		 * requests from supplicant will be dropped by kernel saying
+		 * already connected and supplicant will immediately attempt
+		 * disconnect which will again gets dropped.
+		 * Notify MLO manager to terminate link switch operation and
+		 * instead of dropping the disconnect forcefully move VDEV state
+		 * to disconnecting and add disconnect request to queue so that
+		 * 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)) {
 			mlme_info(CM_PREFIX_FMT "Notfiy MLO MGR to abort link switch",
 				  CM_PREFIX_REF(vdev_id, cm_req->cm_id));
 			mlo_mgr_link_switch_disconnect_done(cm_ctx->vdev,
-							    QDF_STATUS_E_ABORTED);
+							    QDF_STATUS_E_ABORTED,
+							    false);
+			break;
 
 		} else {
 			mlme_info(CM_PREFIX_FMT "dropping disconnect req from source %d in INIT state",

+ 16 - 5
umac/mlme/connection_mgr/core/src/wlan_cm_sm.c

@@ -116,13 +116,24 @@ static bool cm_state_init_event(void *ctx, uint16_t event,
 		cm_disconnect_complete(cm_ctx, data);
 		break;
 	case WLAN_CM_SM_EV_DISCONNECT_REQ:
-		cm_handle_discon_req_in_non_connected_state(cm_ctx, data,
-							    WLAN_CM_S_INIT);
+		status = cm_handle_discon_req_in_non_connected_state(cm_ctx, data,
+								     WLAN_CM_S_INIT);
+		if (QDF_IS_STATUS_ERROR(status)) {
+			/*
+			 * Return not handled as this req need to be
+			 * dropped and return failure to the requester
+			 */
+			event_handled = false;
+			break;
+		}
+
 		/*
-		 * Return not handled as this req need to be dropped and return
-		 * failure to the requester
+		 * If status is success, then forcefully queue
+		 * disconnect req and move VDEV state to disconnecting.
 		 */
-		event_handled = false;
+		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;
 	case WLAN_CM_SM_EV_ROAM_SYNC:
 		/**

+ 2 - 1
umac/mlme/connection_mgr/core/src/wlan_cm_util.c

@@ -640,7 +640,8 @@ cm_handle_disconnect_flush(struct cnx_mgr *cm_ctx, struct cm_req *cm_req)
 	if (resp.req.cm_id & CM_ID_LSWITCH_BIT) {
 		cm_reset_active_cm_id(cm_ctx->vdev, resp.req.cm_id);
 		mlo_mgr_link_switch_disconnect_done(cm_ctx->vdev,
-						    QDF_STATUS_E_ABORTED);
+						    QDF_STATUS_E_ABORTED,
+						    true);
 	}
 }
 

+ 36 - 4
umac/mlo_mgr/inc/wlan_mlo_mgr_link_switch.h

@@ -101,6 +101,9 @@ enum wlan_mlo_link_switch_reason {
  * @MLO_LINK_SWITCH_STATE_SET_MAC_ADDR: MAC address update in progress
  * @MLO_LINK_SWITCH_STATE_CONNECT_NEW_LINK: New link connect in progress.
  * @MLO_LINK_SWITCH_STATE_COMPLETE_SUCCESS: Link switch completed successfully
+ * @MLO_LINK_SWITCH_STATE_ABORT_TRANS: Do not allow any further state
+ *                                     transition, only allowed to move to
+ *                                     MLO_LINK_SWITCH_STATE_IDLE state.
  */
 enum mlo_link_switch_req_state {
 	MLO_LINK_SWITCH_STATE_IDLE,
@@ -108,6 +111,7 @@ enum mlo_link_switch_req_state {
 	MLO_LINK_SWITCH_STATE_SET_MAC_ADDR,
 	MLO_LINK_SWITCH_STATE_CONNECT_NEW_LINK,
 	MLO_LINK_SWITCH_STATE_COMPLETE_SUCCESS,
+	MLO_LINK_SWITCH_STATE_ABORT_TRANS,
 };
 
 /**
@@ -286,15 +290,23 @@ void mlo_mgr_osif_update_connect_info(struct wlan_objmgr_vdev *vdev,
  * disconnect complete.
  * @vdev: VDEV object manager
  * @status: Status of disconnect
+ * @is_link_switch_resp: Set to true is disconnect response is for link switch
+ * disconnect request else false.
  *
  * The API to decide on next sequence of tasks based on status on disconnect
  * request send as part of link switch. If the status is error, then abort
  * link switch or else continue.
  *
+ * If API is called with @is_link_switch_resp argument as false, then some
+ * other thread initiated disconnect, in this scenario change the state of
+ * link switch to abort further state transition and return, in actual link
+ * switch flow check this state to abort link switch.
+ *
  * Return: QDF_STATUS
  */
 QDF_STATUS mlo_mgr_link_switch_disconnect_done(struct wlan_objmgr_vdev *vdev,
-					       QDF_STATUS status);
+					       QDF_STATUS status,
+					       bool is_link_switch_resp);
 
 /**
  * mlo_mgr_link_switch_set_mac_addr_resp() - Handle response of set MAC addr
@@ -360,9 +372,22 @@ void mlo_mgr_link_switch_init_state(struct wlan_mlo_dev_context *mlo_dev_ctx);
  *
  * Return: void
  */
-void
+QDF_STATUS
 mlo_mgr_link_switch_trans_next_state(struct wlan_mlo_dev_context *mlo_dev_ctx);
 
+/**
+ * mlo_mgr_link_switch_trans_abort_state() - Transition to abort trans state.
+ * @mlo_dev_ctx: ML dev context pointer of VDEV
+ *
+ * Transition the current link switch state to MLO_LINK_SWITCH_STATE_ABORT_TRANS
+ * state, no further state transitions are allowed in the ongoing link switch
+ * request.
+ *
+ * Return: void
+ */
+void
+mlo_mgr_link_switch_trans_abort_state(struct wlan_mlo_dev_context *mlo_dev_ctx);
+
 /**
  * mlo_mgr_link_switch_get_curr_state() - Get the current state of link switch.
  * @mlo_dev_ctx: MLO dev context.
@@ -533,7 +558,8 @@ mlo_mgr_osif_update_connect_info(struct wlan_objmgr_vdev *vdev, int32_t link_id)
 
 static inline QDF_STATUS
 mlo_mgr_link_switch_disconnect_done(struct wlan_objmgr_vdev *vdev,
-				    QDF_STATUS status)
+				    QDF_STATUS status,
+				    bool is_link_switch_resp)
 {
 	return QDF_STATUS_SUCCESS;
 }
@@ -574,8 +600,14 @@ mlo_mgr_link_switch_init_state(struct wlan_mlo_dev_context *mlo_dev_ctx)
 {
 }
 
-static inline void
+static inline QDF_STATUS
 mlo_mgr_link_switch_trans_next_state(struct wlan_mlo_dev_context *mlo_dev_ctx)
+{
+	return QDF_STATUS_E_INVAL;
+}
+
+static inline void
+mlo_mgr_link_switch_trans_abort_state(struct wlan_mlo_dev_context *mlo_dev_ctx)
 {
 }
 

+ 47 - 11
umac/mlo_mgr/src/wlan_mlo_mgr_link_switch.c

@@ -320,9 +320,10 @@ void mlo_mgr_link_switch_init_state(struct wlan_mlo_dev_context *mlo_dev_ctx)
 	mlo_dev_lock_release(mlo_dev_ctx);
 }
 
-void
+QDF_STATUS
 mlo_mgr_link_switch_trans_next_state(struct wlan_mlo_dev_context *mlo_dev_ctx)
 {
+	QDF_STATUS status = QDF_STATUS_SUCCESS;
 	enum mlo_link_switch_req_state cur_state, next_state;
 
 	mlo_dev_lock_acquire(mlo_dev_ctx);
@@ -343,12 +344,30 @@ mlo_mgr_link_switch_trans_next_state(struct wlan_mlo_dev_context *mlo_dev_ctx)
 	case MLO_LINK_SWITCH_STATE_COMPLETE_SUCCESS:
 		next_state = MLO_LINK_SWITCH_STATE_IDLE;
 		break;
+	case MLO_LINK_SWITCH_STATE_ABORT_TRANS:
+		next_state = MLO_LINK_SWITCH_STATE_ABORT_TRANS;
+		status = QDF_STATUS_E_PERM;
+		mlo_debug("State transition not allowed");
+		break;
 	default:
 		QDF_ASSERT(0);
 		break;
 	}
 	mlo_dev_ctx->link_ctx->last_req.state = next_state;
 	mlo_dev_lock_release(mlo_dev_ctx);
+
+	return status;
+}
+
+void
+mlo_mgr_link_switch_trans_abort_state(struct wlan_mlo_dev_context *mlo_dev_ctx)
+{
+	enum mlo_link_switch_req_state next_state =
+					MLO_LINK_SWITCH_STATE_ABORT_TRANS;
+
+	mlo_dev_lock_acquire(mlo_dev_ctx);
+	mlo_dev_ctx->link_ctx->last_req.state = next_state;
+	mlo_dev_lock_release(mlo_dev_ctx);
 }
 
 enum mlo_link_switch_req_state
@@ -500,22 +519,28 @@ mlo_mgr_osif_update_connect_info(struct wlan_objmgr_vdev *vdev, int32_t link_id)
 }
 
 QDF_STATUS mlo_mgr_link_switch_disconnect_done(struct wlan_objmgr_vdev *vdev,
-					       QDF_STATUS status)
+					       QDF_STATUS discon_status,
+					       bool is_link_switch_resp)
 {
-	QDF_STATUS qdf_status;
+	QDF_STATUS status;
 	enum mlo_link_switch_req_state cur_state;
 	struct mlo_link_info *new_link_info;
 	struct qdf_mac_addr mac_addr, mld_addr;
 	struct wlan_mlo_dev_context *mlo_dev_ctx = vdev->mlo_dev_ctx;
 	struct wlan_mlo_link_switch_req *req = &mlo_dev_ctx->link_ctx->last_req;
 
+	if (!is_link_switch_resp) {
+		mlo_mgr_link_switch_trans_abort_state(mlo_dev_ctx);
+		return QDF_STATUS_SUCCESS;
+	}
+
 	cur_state = mlo_mgr_link_switch_get_curr_state(mlo_dev_ctx);
-	if (QDF_IS_STATUS_ERROR(status) ||
+	if (QDF_IS_STATUS_ERROR(discon_status) ||
 	    cur_state != MLO_LINK_SWITCH_STATE_DISCONNECT_CURR_LINK) {
 		mlo_err("VDEV %d link switch disconnect req failed",
 			req->vdev_id);
 		mlo_mgr_remove_link_switch_cmd(vdev);
-		return status;
+		return QDF_STATUS_SUCCESS;
 	}
 
 	mlo_debug("VDEV %d link switch disconnect complete",
@@ -532,13 +557,17 @@ QDF_STATUS mlo_mgr_link_switch_disconnect_done(struct wlan_objmgr_vdev *vdev,
 	qdf_copy_macaddr(&mld_addr, &mlo_dev_ctx->mld_addr);
 	qdf_copy_macaddr(&mac_addr, &new_link_info->link_addr);
 
-	mlo_mgr_link_switch_trans_next_state(mlo_dev_ctx);
+	status = mlo_mgr_link_switch_trans_next_state(mlo_dev_ctx);
+	if (QDF_IS_STATUS_ERROR(status)) {
+		mlo_mgr_remove_link_switch_cmd(vdev);
+		return status;
+	}
 
-	qdf_status = wlan_vdev_mlme_send_set_mac_addr(mac_addr, mld_addr, vdev);
-	if (QDF_IS_STATUS_ERROR(qdf_status))
+	status = wlan_vdev_mlme_send_set_mac_addr(mac_addr, mld_addr, vdev);
+	if (QDF_IS_STATUS_ERROR(status))
 		mlo_mgr_remove_link_switch_cmd(vdev);
 
-	return qdf_status;
+	return status;
 }
 
 QDF_STATUS mlo_mgr_link_switch_set_mac_addr_resp(struct wlan_objmgr_vdev *vdev,
@@ -586,7 +615,12 @@ QDF_STATUS mlo_mgr_link_switch_set_mac_addr_resp(struct wlan_objmgr_vdev *vdev,
 							req->new_ieee_link_id,
 							req->vdev_id);
 
-	mlo_mgr_link_switch_trans_next_state(vdev->mlo_dev_ctx);
+	status = mlo_mgr_link_switch_trans_next_state(vdev->mlo_dev_ctx);
+	if (QDF_IS_STATUS_ERROR(status)) {
+		mlo_mgr_remove_link_switch_cmd(vdev);
+		return status;
+	}
+
 	status = mlo_mgr_link_switch_start_connect(vdev);
 
 	return status;
@@ -722,7 +756,9 @@ mlo_mgr_start_link_switch(struct wlan_objmgr_vdev *vdev,
 	}
 
 	wlan_vdev_mlme_set_mlo_link_switch_in_progress(vdev);
-	mlo_mgr_link_switch_trans_next_state(vdev->mlo_dev_ctx);
+	status = mlo_mgr_link_switch_trans_next_state(vdev->mlo_dev_ctx);
+	if (QDF_IS_STATUS_ERROR(status))
+		return status;
 
 	status = wlan_cm_disconnect(vdev, CM_MLO_LINK_SWITCH_DISCONNECT,
 				    REASON_FW_TRIGGERED_LINK_SWITCH, &bssid);

+ 4 - 3
umac/mlo_mgr/src/wlan_mlo_mgr_sta.c

@@ -1293,8 +1293,10 @@ mlo_send_link_disconnect_sync(struct wlan_mlo_dev_context *mlo_dev_ctx,
 		mlo_get_assoc_link_vdev(mlo_dev_ctx);
 
 	for (i = 0; i < WLAN_UMAC_MLO_MAX_VDEVS; i++) {
-		if (!mlo_dev_ctx->wlan_vdev_list[i])
+		if (!mlo_dev_ctx->wlan_vdev_list[i] ||
+		    mlo_dev_ctx->wlan_vdev_list[i] == sync_vdev) {
 			continue;
+		}
 
 		/**
 		 * If the assoc vdev isn't present, use the first link dev as
@@ -2141,8 +2143,7 @@ void mlo_internal_disconnect_links(struct wlan_objmgr_vdev *vdev)
 	for (i =  0; i < vdev_count; i++) {
 		if (wlan_vdev_list[i] != assoc_vdev &&
 		    (wlan_cm_is_vdev_connected(wlan_vdev_list[i]) ||
-		     wlan_cm_is_vdev_connecting(wlan_vdev_list[i]) ||
-		     wlan_vdev_mlme_is_mlo_link_switch_in_progress(wlan_vdev_list[i])))
+		     wlan_cm_is_vdev_connecting(wlan_vdev_list[i])))
 			wlan_cm_disconnect(wlan_vdev_list[i],
 					   CM_MLO_LINK_VDEV_DISCONNECT,
 					   REASON_UNSPEC_FAILURE,