qcacld-3.0: Validate nss configuration from user

Validate the nss set by the user and then
send the user config to the firmware

Change-Id: Ia1292e428e0c5b30ff0b96f2387ea29ceec9c97c
CRs-Fixed: 2347502
diff --git a/core/hdd/src/wlan_hdd_cfg.c b/core/hdd/src/wlan_hdd_cfg.c
index 009f0a9..66c25df 100644
--- a/core/hdd/src/wlan_hdd_cfg.c
+++ b/core/hdd/src/wlan_hdd_cfg.c
@@ -4342,6 +4342,16 @@
 				       CFG_PMKID_MODES_PMKSA_CACHING) ? 1 : 0;
 }
 
+static void
+hdd_populate_vdev_nss(struct wlan_mlme_nss_chains *user_cfg,
+		      uint8_t tx_nss,
+		      uint8_t rx_nss,
+		      enum nss_chains_band_info  band)
+{
+	user_cfg->rx_nss[band] = rx_nss;
+	user_cfg->tx_nss[band] = tx_nss;
+}
+
 /**
  * hdd_update_nss() - Update the number of spatial streams supported.
  * Ensure that nss is either 1 or 2 before calling this.
@@ -4368,12 +4378,50 @@
 	uint8_t enable2x2;
 	mac_handle_t mac_handle;
 	bool bval = 0;
+	enum nss_chains_band_info band;
+	struct wlan_mlme_nss_chains user_cfg;
+	uint8_t tx_nss, rx_nss;
 
 	if ((nss == 2) && (hdd_ctx->num_rf_chains != 2)) {
 		hdd_err("No support for 2 spatial streams");
 		return QDF_STATUS_E_INVAL;
 	}
 
+	if (nss > MAX_VDEV_NSS) {
+		hdd_debug("Cannot support %d nss streams", nss);
+		return QDF_STATUS_E_INVAL;
+	}
+
+	mac_handle = hdd_ctx->mac_handle;
+	if (!mac_handle) {
+		hdd_err("NULL MAC handle");
+		return QDF_STATUS_E_INVAL;
+	}
+
+	/* Till now we dont have support for different rx, tx nss values */
+	tx_nss = nss;
+	rx_nss = nss;
+
+	qdf_mem_zero(&user_cfg, sizeof(user_cfg));
+
+	if (hdd_ctx->dynamic_nss_chains_support) {
+		for (band = BAND_2GHZ; band < BAND_MAX; band++)
+			hdd_populate_vdev_nss(&user_cfg, tx_nss,
+					      rx_nss, band);
+		if (QDF_IS_STATUS_ERROR(
+			sme_nss_chains_update(mac_handle,
+					      &user_cfg,
+					      adapter->session_id)))
+			return QDF_STATUS_E_FAILURE;
+
+		return QDF_STATUS_SUCCESS;
+	}
+
+	/*
+	 * The code below is executed only when fw doesn't support dynamic
+	 * update of nss and chains per vdev feature, for the upcoming
+	 * connection
+	 */
 	enable2x2 = (nss == 1) ? 0 : 1;
 
 	qdf_status = ucfg_mlme_get_vht_enable2x2(hdd_ctx->psoc, &bval);
@@ -4387,12 +4435,6 @@
 		return QDF_STATUS_SUCCESS;
 	}
 
-	mac_handle = hdd_ctx->mac_handle;
-	if (!mac_handle) {
-		hdd_err("NULL MAC handle");
-		return QDF_STATUS_E_INVAL;
-	}
-
 	if (sme_is_any_session_in_connected_state(mac_handle)) {
 		hdd_err("Connected sessions present, Do not change NSS");
 		return QDF_STATUS_E_INVAL;
diff --git a/core/sme/inc/sme_api.h b/core/sme/inc/sme_api.h
index d76d87f1..393ab49 100644
--- a/core/sme/inc/sme_api.h
+++ b/core/sme/inc/sme_api.h
@@ -342,6 +342,23 @@
 			     uint8_t rf_chains_supported);
 
 /**
+ * sme_nss_chains_update() - validate and send the user params to fw
+ * @mac_handle: The handle returned by mac_open.
+ * @user_cfg: pointer to the structure to be validated and sent to fw
+ * @vdev_id: vdev id
+ *
+ *
+ * This API will validate the config, and if found correct will update the
+ * config in dynamic config, and send to the fw.
+ *
+ * Return: QDF_STATUS
+ */
+QDF_STATUS
+sme_nss_chains_update(mac_handle_t mac_handle,
+		      struct wlan_mlme_nss_chains *user_cfg,
+		      uint8_t vdev_id);
+
+/**
  * sme_open_session() - Open a session for given persona
  *
  * This is a synchronous API. For any protocol stack related activity
diff --git a/core/sme/src/common/sme_api.c b/core/sme/src/common/sme_api.c
index 30560f0..d033b78 100644
--- a/core/sme/src/common/sme_api.c
+++ b/core/sme/src/common/sme_api.c
@@ -4880,6 +4880,198 @@
 					  rf_chains_supported);
 }
 
+static void
+sme_populate_user_config(struct wlan_mlme_nss_chains *dynamic_cfg,
+			 struct wlan_mlme_nss_chains *user_cfg,
+			 enum nss_chains_band_info band)
+{
+	if (!user_cfg->num_rx_chains[band])
+		user_cfg->num_rx_chains[band] =
+			dynamic_cfg->num_rx_chains[band];
+
+	if (!user_cfg->num_tx_chains[band])
+		user_cfg->num_tx_chains[band] =
+			dynamic_cfg->num_tx_chains[band];
+
+	if (!user_cfg->rx_nss[band])
+		user_cfg->rx_nss[band] =
+			dynamic_cfg->rx_nss[band];
+
+	if (!user_cfg->tx_nss[band])
+		user_cfg->tx_nss[band] =
+			dynamic_cfg->tx_nss[band];
+
+	if (!user_cfg->num_tx_chains_11a)
+		user_cfg->num_tx_chains_11a =
+			dynamic_cfg->num_tx_chains_11a;
+
+	if (!user_cfg->num_tx_chains_11b)
+		user_cfg->num_tx_chains_11b =
+			dynamic_cfg->num_tx_chains_11b;
+
+	if (!user_cfg->num_tx_chains_11g)
+		user_cfg->num_tx_chains_11g =
+			dynamic_cfg->num_tx_chains_11g;
+
+	if (!user_cfg->disable_rx_mrc[band])
+		user_cfg->disable_rx_mrc[band] =
+			dynamic_cfg->disable_rx_mrc[band];
+
+	if (!user_cfg->disable_tx_mrc[band])
+		user_cfg->disable_tx_mrc[band] =
+			dynamic_cfg->disable_tx_mrc[band];
+}
+
+static QDF_STATUS
+sme_validate_from_ini_config(struct wlan_mlme_nss_chains *user_cfg,
+			     struct wlan_mlme_nss_chains *ini_cfg,
+			     enum nss_chains_band_info band)
+{
+	if (user_cfg->num_rx_chains[band] >
+	    ini_cfg->num_rx_chains[band])
+		return QDF_STATUS_E_FAILURE;
+
+	if (user_cfg->num_tx_chains[band] >
+	    ini_cfg->num_tx_chains[band])
+		return QDF_STATUS_E_FAILURE;
+
+	if (user_cfg->rx_nss[band] >
+	    ini_cfg->rx_nss[band])
+		return QDF_STATUS_E_FAILURE;
+
+	if (user_cfg->tx_nss[band] >
+	    ini_cfg->tx_nss[band])
+		return QDF_STATUS_E_FAILURE;
+
+	if (user_cfg->num_tx_chains_11a >
+	    ini_cfg->num_tx_chains_11a)
+		return QDF_STATUS_E_FAILURE;
+
+	if (user_cfg->num_tx_chains_11b >
+	    ini_cfg->num_tx_chains_11b)
+		return QDF_STATUS_E_FAILURE;
+
+	if (user_cfg->num_tx_chains_11g >
+	    ini_cfg->num_tx_chains_11g)
+		return QDF_STATUS_E_FAILURE;
+
+	return QDF_STATUS_SUCCESS;
+}
+
+static QDF_STATUS
+sme_validate_user_nss_chain_params(
+				    struct wlan_mlme_nss_chains *user_cfg,
+				    enum nss_chains_band_info band)
+{
+	/* Reject as 2x1 modes are not supported in chains yet */
+
+	if (user_cfg->num_tx_chains[band] >
+	    user_cfg->num_rx_chains[band])
+		return QDF_STATUS_E_FAILURE;
+
+	/* Also if mode is 2x2, we cant have chains as 1x1, or 1x2, or 2x1 */
+
+	if (user_cfg->tx_nss[band] >
+	    user_cfg->num_tx_chains[band])
+		user_cfg->num_tx_chains[band] =
+			user_cfg->tx_nss[band];
+
+	if (user_cfg->rx_nss[band] >
+	    user_cfg->num_rx_chains[band])
+		user_cfg->num_rx_chains[band] =
+			user_cfg->rx_nss[band];
+
+	/*
+	 * It may happen that already chains are in 1x1 mode and nss too
+	 * is in 1x1 mode, but the tx 11a/b/g chains in user config comes
+	 * as 2x1, or 1x2 which cannot support respective mode, as tx chains
+	 * for respective band have max of 1x1 only, so these cannot exceed
+	 * respective band num tx chains.
+	 */
+
+	if (user_cfg->num_tx_chains_11a >
+	    user_cfg->num_tx_chains[BAND_5GHZ])
+		user_cfg->num_tx_chains_11a =
+			user_cfg->num_tx_chains[BAND_5GHZ];
+
+	if (user_cfg->num_tx_chains_11b >
+	    user_cfg->num_tx_chains[BAND_2GHZ])
+		user_cfg->num_tx_chains_11b =
+			user_cfg->num_tx_chains[BAND_2GHZ];
+
+	if (user_cfg->num_tx_chains_11g >
+	    user_cfg->num_tx_chains[BAND_2GHZ])
+		user_cfg->num_tx_chains_11g =
+			user_cfg->num_tx_chains[BAND_2GHZ];
+
+	return QDF_STATUS_SUCCESS;
+}
+
+static QDF_STATUS
+sme_validate_nss_chains_config(struct wlan_objmgr_vdev *vdev,
+			       struct wlan_mlme_nss_chains *user_cfg)
+{
+	enum nss_chains_band_info band;
+	struct wlan_mlme_nss_chains *ini_cfg;
+	struct wlan_mlme_nss_chains *dynamic_cfg;
+	QDF_STATUS status;
+
+	ini_cfg = mlme_get_ini_vdev_config(vdev);
+	if (!ini_cfg) {
+		sme_err("nss chain ini config NULL");
+		return QDF_STATUS_E_FAILURE;
+	}
+	dynamic_cfg = mlme_get_dynamic_vdev_config(vdev);
+	if (!dynamic_cfg) {
+		sme_err("nss chain dynamic config NULL");
+		return QDF_STATUS_E_FAILURE;
+	}
+
+	for (band = BAND_2GHZ; band < BAND_MAX; band++) {
+		sme_populate_user_config(dynamic_cfg,
+					 user_cfg, band);
+		status = sme_validate_from_ini_config(user_cfg,
+						      ini_cfg,
+						      band);
+		if (QDF_IS_STATUS_ERROR(status)) {
+			sme_err("Validation from ini config failed");
+			return QDF_STATUS_E_FAILURE;
+		}
+		status = sme_validate_user_nss_chain_params(user_cfg,
+							    band);
+		if (QDF_IS_STATUS_ERROR(status)) {
+			sme_err("User cfg validation failed");
+			return QDF_STATUS_E_FAILURE;
+		}
+	}
+
+	return QDF_STATUS_SUCCESS;
+}
+
+QDF_STATUS
+sme_nss_chains_update(mac_handle_t mac_handle,
+		      struct wlan_mlme_nss_chains *user_cfg,
+		      uint8_t vdev_id)
+{
+	struct sAniSirGlobal *mac_ctx = MAC_CONTEXT(mac_handle);
+	QDF_STATUS status;
+	struct wlan_objmgr_vdev *vdev =
+		       wlan_objmgr_get_vdev_by_id_from_psoc(mac_ctx->psoc,
+							    vdev_id,
+							    WLAN_LEGACY_SME_ID);
+	if (!vdev) {
+		sme_err("Got NULL vdev obj, returning");
+		return QDF_STATUS_E_FAILURE;
+	}
+	status = sme_validate_nss_chains_config(vdev, user_cfg);
+	if (QDF_IS_STATUS_ERROR(status))
+		goto end;
+
+end:
+	wlan_objmgr_vdev_release_ref(vdev, WLAN_LEGACY_SME_ID);
+	return status;
+}
+
 QDF_STATUS sme_open_session(mac_handle_t hal, struct sme_session_params *params)
 {
 	QDF_STATUS status = QDF_STATUS_E_INVAL;