Преглед изворни кода

qcacmn: Fix race condition between connect and disconnect

Scenario: In case of STA + STA, NB/SB issues disconnection for
vdev 0 when vdev 1 is in connecting state.

As connection is blocking serialization command and it can try
multiple candidates and can take upto 30+ sec, there is a
chance that disconnect may get timeout and is followed by
vdev delete from osif. This can lead to vdev delete sent
without vdev down/stop and bss peer delete for vdev 0.

So abort connection when trying for candidate if any of the
vdev is waiting for disconnect, to avoid disconnect timeout.

Change-Id: I7ef50b2e870317d2b095dd0385fcbe1c5b86fd20
CRs-Fixed: 3051567
abhinav kumar пре 3 година
родитељ
комит
7427e70f15
1 измењених фајлова са 108 додато и 16 уклоњено
  1. 108 16
      umac/mlme/connection_mgr/core/src/wlan_cm_connect.c

+ 108 - 16
umac/mlme/connection_mgr/core/src/wlan_cm_connect.c

@@ -584,6 +584,22 @@ static void cm_create_bss_peer(struct cnx_mgr *cm_ctx,
 	}
 }
 
+static
+QDF_STATUS cm_if_mgr_validate_candidate(struct cnx_mgr *cm_ctx,
+					struct scan_cache_entry *scan_entry)
+{
+	struct if_mgr_event_data event_data = {0};
+
+	event_data.validate_bss_info.chan_freq = scan_entry->channel.chan_freq;
+	event_data.validate_bss_info.beacon_interval = scan_entry->bcn_int;
+	qdf_copy_macaddr(&event_data.validate_bss_info.peer_addr,
+			 &scan_entry->bssid);
+
+	return if_mgr_deliver_event(cm_ctx->vdev,
+				    WLAN_IF_MGR_EV_VALIDATE_CANDIDATE,
+				    &event_data);
+}
+
 #ifdef CONN_MGR_ADV_FEATURE
 #ifdef WLAN_FEATURE_FILS_SK
 /*
@@ -678,6 +694,65 @@ static inline QDF_STATUS cm_set_fils_key(struct cnx_mgr *cm_ctx,
 }
 #endif /* WLAN_FEATURE_FILS_SK */
 
+static void cm_get_vdev_id_from_bssid(struct wlan_objmgr_pdev *pdev,
+				   void *object, void *arg)
+{
+	uint8_t *vdev_id = arg;
+	struct wlan_objmgr_vdev *vdev = (struct wlan_objmgr_vdev *)object;
+
+	if (!vdev_id)
+		return;
+
+	if (!(wlan_vdev_mlme_get_opmode(vdev) == QDF_STA_MODE ||
+	      wlan_vdev_mlme_get_opmode(vdev) == QDF_P2P_CLIENT_MODE))
+		return;
+
+	if (cm_is_vdev_disconnecting(vdev))
+		*vdev_id = wlan_vdev_get_id(vdev);
+}
+
+/**
+ * cm_is_any_other_vdev_disconnecting() - check whether any other vdev is in
+ * disconnecting state
+ * @cm_ctx: connection manager context
+ * @cm_req: Connect request.
+ *
+ * As Connect is a blocking call this API will make sure the disconnect
+ * doesnt timeout on any vdev and thus make sure that PEER/VDEV SM are cleaned
+ * before vdev delete is sent.
+ *
+ * Return : true if disconnection is on any vdev_id
+ */
+static bool cm_is_any_other_vdev_disconnecting(struct cnx_mgr *cm_ctx,
+					       struct cm_req *cm_req)
+{
+	struct wlan_objmgr_pdev *pdev;
+	uint8_t vdev_id;
+	uint8_t cur_vdev_id = wlan_vdev_get_id(cm_ctx->vdev);
+
+	pdev = wlan_vdev_get_pdev(cm_ctx->vdev);
+	if (!pdev) {
+		mlme_err(CM_PREFIX_FMT "Failed to find pdev",
+			 CM_PREFIX_REF(cur_vdev_id, cm_req->cm_id));
+		return false;
+	}
+
+	vdev_id = WLAN_INVALID_VDEV_ID;
+	wlan_objmgr_pdev_iterate_obj_list(pdev, WLAN_VDEV_OP,
+					  cm_get_vdev_id_from_bssid,
+					  &vdev_id, 0,
+					  WLAN_MLME_CM_ID);
+
+	if (vdev_id != WLAN_INVALID_VDEV_ID && vdev_id != cur_vdev_id) {
+		mlme_info(CM_PREFIX_FMT "Abort connection as vdev %d is waiting for disconnect",
+			  CM_PREFIX_REF(cur_vdev_id, cm_req->cm_id),
+			  vdev_id);
+		return true;
+	}
+
+	return false;
+}
+
 QDF_STATUS
 cm_inform_blm_connect_complete(struct wlan_objmgr_vdev *vdev,
 			       struct wlan_cm_connect_resp *resp)
@@ -717,6 +792,7 @@ static bool cm_is_retry_with_same_candidate(struct cnx_mgr *cm_ctx,
 	uint32_t key_mgmt;
 	struct wlan_objmgr_psoc *psoc;
 	bool sae_connection;
+	QDF_STATUS status;
 
 	psoc = wlan_pdev_get_psoc(wlan_vdev_get_pdev(cm_ctx->vdev));
 	key_mgmt = req->cur_candidate->entry->neg_sec_info.key_mgmt;
@@ -754,6 +830,11 @@ use_same_candidate:
 	if (req->cur_candidate_retries >= max_retry_count)
 		return false;
 
+	status = cm_if_mgr_validate_candidate(cm_ctx,
+					      req->cur_candidate->entry);
+	if (QDF_IS_STATUS_ERROR(status))
+		return false;
+
 	mlme_info(CM_PREFIX_FMT "Retry again with " QDF_MAC_ADDR_FMT ", status code %d reason %d key_mgmt 0x%x retry count %d max retry %d",
 		  CM_PREFIX_REF(resp->vdev_id, resp->cm_id),
 		  QDF_MAC_ADDR_REF(resp->bssid.bytes), resp->status_code,
@@ -876,6 +957,13 @@ static void cm_set_fils_wep_key(struct cnx_mgr *cm_ctx,
 }
 #else
 
+static inline
+bool cm_is_any_other_vdev_disconnecting(struct cnx_mgr *cm_ctx,
+					struct cm_req *cm_req)
+{
+	return false;
+}
+
 static inline
 bool cm_is_retry_with_same_candidate(struct cnx_mgr *cm_ctx,
 				     struct cm_connect_req *req,
@@ -1148,22 +1236,6 @@ static QDF_STATUS cm_connect_get_candidates(struct wlan_objmgr_pdev *pdev,
 	return QDF_STATUS_SUCCESS;
 }
 
-static
-QDF_STATUS cm_if_mgr_validate_candidate(struct cnx_mgr *cm_ctx,
-					struct scan_cache_entry *scan_entry)
-{
-	struct if_mgr_event_data event_data = {0};
-
-	event_data.validate_bss_info.chan_freq = scan_entry->channel.chan_freq;
-	event_data.validate_bss_info.beacon_interval = scan_entry->bcn_int;
-	qdf_copy_macaddr(&event_data.validate_bss_info.peer_addr,
-			 &scan_entry->bssid);
-
-	return if_mgr_deliver_event(cm_ctx->vdev,
-				    WLAN_IF_MGR_EV_VALIDATE_CANDIDATE,
-				    &event_data);
-}
-
 QDF_STATUS cm_if_mgr_inform_connect_complete(struct wlan_objmgr_vdev *vdev,
 					     QDF_STATUS connect_status)
 {
@@ -1375,6 +1447,26 @@ static QDF_STATUS cm_get_valid_candidate(struct cnx_mgr *cm_ctx,
 	}
 
 	prev_candidate = cm_req->connect_req.cur_candidate;
+	/*
+	 * In case of STA/CLI + STA/CLI, if a STA/CLI is in connecting state and
+	 * a disconnect is received on any other STA/CLI, the disconnect can
+	 * timeout waiting for the connection on first STA/CLI to get completed.
+	 * This is because the connect is a blocking serialization command and
+	 * it can try multiple candidates and thus can take upto 30+ sec to
+	 * complete.
+	 *
+	 * Now osif will proceed with vdev delete after disconnect timeout.
+	 * This can lead to vdev delete sent without vdev down/stop/peer delete
+	 * for the vdev.
+	 *
+	 * So abort the connection if any of the vdev is waiting for disconnect,
+	 * to avoid disconnect timeout.
+	 */
+	if (cm_is_any_other_vdev_disconnecting(cm_ctx, cm_req)) {
+		status = QDF_STATUS_E_FAILURE;
+		goto flush_single_pmk;
+	}
+
 	if (cm_req->connect_req.connect_attempts >=
 	    cm_ctx->max_connect_attempts) {
 		mlme_info(CM_PREFIX_FMT "%d attempts tried, max %d",