Эх сурвалжийг харах

qcacmn: Add API to get partner link info from Basic Variant ML element

Add API to get partner link information in the per-STA profiles
present in a Basic variant Multi-Link element. The partner link
information is returned only for those per-STA profiles which have a
MAC address in the STA Info field. Also note that the current
implementation does not have Multi-Link element fragmentation support
- this will be added in an upcoming change after shared helper
utilities for the same are available. Similarly, the current
implementation does not have support for per-STA profile fragmentation
- this may be added once it is introduced in the IEEE802.11be
standard.

Change-Id: Ied7092ba1366d39fa15adffc3737d4ad5c0adf59
CRs-Fixed: 3040491
Krishna Rao 3 жил өмнө
parent
commit
39c0eb2153

+ 34 - 0
umac/mlo_mgr/inc/utils_mlo.h

@@ -143,6 +143,33 @@ util_get_bvmlie_mldmacaddr(uint8_t *mlieseq, qdf_size_t mlieseqlen,
 QDF_STATUS
 util_get_bvmlie_primary_linkid(uint8_t *mlieseq, qdf_size_t mlieseqlen,
 			       bool *linkidfound, uint8_t *linkid);
+
+/**
+ * util_get_bvmlie_persta_partner_info() - Get per-STA partner link information
+ * @mlie: Starting address of the Multi-Link element
+ * @mlielen: Total length of the Multi-Link element
+ * @partner_info: Pointer to the location where the partner link information
+ * should be updated. This should be ignored by the caller if the function
+ * returns error. Note that success will be returned and the number of links in
+ * this structure will be reported as 0, if no Link Info is found, or no per-STA
+ * profile is found, or if none of the per-STA profiles includes a MAC address
+ * in the STA Info field (assuming no errors are encountered).
+ *
+ * Get partner link information in the per-STA profiles present in a Basic
+ * variant Multi-Link element. The partner link information is returned only for
+ * those per-STA profiles which have a MAC address in the STA Info field. Also
+ * note that the current implementation does not have Multi-Link element
+ * fragmentation support - this will be added in an upcoming change after shared
+ * helper utilities for the same are available. Similarly, the current
+ * implementation does not have support for per-STA profile fragmentation - this
+ * may be added once it is introduced in the IEEE802.11be standard.
+ *
+ * Return: QDF_STATUS_SUCCESS in the case of success, QDF_STATUS value giving
+ * the reason for error in the case of failure
+ */
+QDF_STATUS
+util_get_bvmlie_persta_partner_info(uint8_t *mlie, qdf_size_t mlielen,
+				    struct mlo_partner_info *partner_info);
 #else
 static inline QDF_STATUS
 util_gen_link_assoc_rsp(uint8_t *frame, qdf_size_t len,
@@ -179,5 +206,12 @@ util_get_bvmlie_primary_linkid(uint8_t *mlieseq, qdf_size_t mlieseqlen,
 {
 	return QDF_STATUS_E_NOSUPPORT;
 }
+
+static inline QDF_STATUS
+util_get_bvmlie_persta_partner_info(uint8_t *mlie, qdf_size_t mlielen,
+				    struct mlo_partner_info *partner_info)
+{
+	return QDF_STATUS_E_NOSUPPORT;
+}
 #endif /* WLAN_FEATURE_11BE_MLO */
 #endif /* _WLAN_UTILS_MLO_H_ */

+ 408 - 102
umac/mlo_mgr/src/utils_mlo.c

@@ -66,121 +66,231 @@ uint8_t *util_find_extn_eid(uint8_t eid, uint8_t extn_eid,
 }
 
 static
-uint8_t *util_parse_multi_link_ctrl_ie(uint8_t *subelement,
-				       qdf_size_t len,
-				       qdf_size_t *link_info_ie_len)
+uint8_t *util_parse_multi_link_ctrl(uint8_t *element,
+				    qdf_size_t len,
+				    qdf_size_t *link_info_len)
 {
-	qdf_size_t sub_ie_len = 0;
+	qdf_size_t parsed_ie_len = 0;
+	struct wlan_ie_multilink *mlie_fixed;
+	uint16_t mlcontrol;
+	uint16_t presencebm;
+
+	if (!element) {
+		mlo_err("Pointer to element is NULL");
+		return NULL;
+	}
 
-	if (!subelement || !len)
+	if (!len) {
+		mlo_err("Length is zero");
 		return NULL;
+	}
+
+	if (len < sizeof(struct wlan_ie_multilink)) {
+		mlo_err_rl("Length %zu octets is smaller than required for the fixed portion of Multi-Link element (%zu octets)",
+			   len, sizeof(struct wlan_ie_multilink));
+		return NULL;
+	}
+
+	mlie_fixed = (struct wlan_ie_multilink *)element;
+	mlcontrol = le16toh(mlie_fixed->mlcontrol);
+	presencebm = QDF_GET_BITS(mlcontrol, WLAN_ML_CTRL_PBM_IDX,
+				  WLAN_ML_CTRL_PBM_BITS);
+
+	parsed_ie_len += sizeof(*mlie_fixed);
+
+	/* Check if MLD MAC address is present */
+	if (presencebm & WLAN_ML_BV_CTRL_PBM_MLDMACADDR_P)
+		parsed_ie_len += QDF_MAC_ADDR_SIZE;
+
+	/* Check if Link ID info is present */
+	if (presencebm & WLAN_ML_BV_CTRL_PBM_LINKIDINFO_P)
+		parsed_ie_len += WLAN_ML_BV_CINFO_LINKIDINFO_SIZE;
+
+	/* Check if BSS parameter change count is present */
+	if (presencebm & WLAN_ML_BV_CTRL_PBM_BSSPARAMCHANGECNT_P)
+		parsed_ie_len += WLAN_ML_BV_CINFO_BSSPARAMCHNGCNT_SIZE;
+
+	/* Check if Medium Sync Delay Info is present */
+	if (presencebm & WLAN_ML_BV_CTRL_PBM_MEDIUMSYNCDELAYINFO_P)
+		parsed_ie_len += WLAN_ML_BV_CINFO_MEDMSYNCDELAYINFO_SIZE;
+
+	/* Check if EML cap is present */
+	if (presencebm & WLAN_ML_BV_CTRL_PBM_EMLCAP_P)
+		parsed_ie_len += WLAN_ML_BV_CINFO_EMLCAP_SIZE;
 
-	/* sta prof len = ML IE len + EID extn(1) + Multi Lnk ctrl(2) */
-	sub_ie_len += TAG_LEN_POS + MIN_IE_LEN + 2;
-
-	/* check if MLD MAC address present */
-	if (qdf_test_bit(WLAN_ML_CTRL_PBM_IDX |
-			 WLAN_ML_BV_CTRL_PBM_MLDMACADDR_P,
-			 (unsigned long *)&subelement[MULTI_LINK_CTRL_1]))
-		sub_ie_len = sub_ie_len + QDF_MAC_ADDR_SIZE;
-
-	/* check if Link ID info */
-	if (qdf_test_bit(WLAN_ML_CTRL_PBM_IDX |
-			 WLAN_ML_BV_CTRL_PBM_LINKIDINFO_P,
-			 (unsigned long *)&subelement[MULTI_LINK_CTRL_1]))
-		sub_ie_len = sub_ie_len + TAG_LEN_POS;
-
-	/* check if BSS parameter change count */
-	if (qdf_test_bit(WLAN_ML_CTRL_PBM_IDX |
-			 WLAN_ML_BV_CTRL_PBM_BSSPARAMCHANGECNT_P,
-			 (unsigned long *)&subelement[MULTI_LINK_CTRL_1]))
-		sub_ie_len = sub_ie_len + TAG_LEN_POS;
-
-	/* check if Medium Sync Delay Info present */
-	if (qdf_test_bit(WLAN_ML_CTRL_PBM_IDX |
-			 WLAN_ML_BV_CTRL_PBM_MEDIUMSYNCDELAYINFO_P,
-			 (unsigned long *)&subelement[MULTI_LINK_CTRL_1]))
-		sub_ie_len = sub_ie_len + PAYLOAD_START_POS;
-
-	/* check if EML cap present */
-	if (qdf_test_bit(WLAN_ML_CTRL_PBM_IDX |
-			 WLAN_ML_BV_CTRL_PBM_EMLCAP_P,
-			 (unsigned long *)&subelement[MULTI_LINK_CTRL_2]))
-		sub_ie_len = sub_ie_len + SUBELEMENT_START_POS;
-
-	/* check if MLD cap present */
-	if (qdf_test_bit(WLAN_ML_CTRL_PBM_IDX |
-			 WLAN_ML_BV_CTRL_PBM_MLDCAP_P,
-			 (unsigned long *)&subelement[MULTI_LINK_CTRL_2]))
-		sub_ie_len = sub_ie_len + PAYLOAD_START_POS;
-
-	if (link_info_ie_len) {
-		*link_info_ie_len = len - sub_ie_len;
-		mlo_debug("link_info_ie_len:%zu, sub_ie_len:%zu",
-			  *link_info_ie_len, sub_ie_len);
-	}
-
-	return &subelement[sub_ie_len];
+	/* Check if MLD cap is present */
+	if (presencebm & WLAN_ML_BV_CTRL_PBM_MLDCAP_P)
+		parsed_ie_len += WLAN_ML_BV_CINFO_MLDCAP_SIZE;
+
+	if (link_info_len) {
+		*link_info_len = len - parsed_ie_len;
+		mlo_debug("link_info_len:%zu, parsed_ie_len:%zu",
+			  *link_info_len, parsed_ie_len);
+	}
+
+	return &element[parsed_ie_len];
 }
 
 static
-uint8_t *util_parse_sta_profile_ie(uint8_t *subelement,
-				   qdf_size_t len,
-				   qdf_size_t *per_sta_prof_ie_len,
-				   struct qdf_mac_addr *bssid)
+uint8_t *util_parse_perstaprofile(uint8_t *subelement,
+				  qdf_size_t len,
+				  bool is_staprof_reqd,
+				  qdf_size_t *staprof_len,
+				  uint8_t *linkid,
+				  bool *is_macaddr_valid,
+				  struct qdf_mac_addr *macaddr)
 {
-	qdf_size_t tmp_len = 0;
-	uint8_t *tmp = NULL;
+	qdf_size_t subelement_len = 0;
+	struct wlan_ml_bv_linfo_perstaprof *perstaprof_fixed;
+	uint16_t stacontrol;
+	uint8_t completeprofile;
+	uint8_t nstrlppresent;
+	enum wlan_ml_bv_linfo_perstaprof_stactrl_nstrbmsz nstrbmsz;
+
+	if (!subelement) {
+		mlo_err("Pointer to subelement is NULL");
+		return NULL;
+	}
 
-	if (!subelement || !len)
+	if (!len) {
+		mlo_err("Length is zero");
 		return NULL;
+	}
 
-	if (subelement[0] == 0)
-		tmp = subelement;
-	if (!tmp)
+	if (subelement[0] != WLAN_ML_BV_LINFO_SUBELEMID_PERSTAPROFILE) {
+		mlo_err_rl("Pointer to subelement does not point to per-STA profile");
 		return NULL;
+	}
 
-	/* tmp_len = 2 (sta ctrl) + 1 (sub EID) + 1 (len) */
-	tmp_len = PAYLOAD_START_POS + 2;
+	if (len < sizeof(struct wlan_ml_bv_linfo_perstaprof)) {
+		mlo_err_rl("len %zu octets is smaller than that required for the fixed portion of per-STA profile (%zu octets)",
+			   len, sizeof(struct wlan_ml_bv_linfo_perstaprof));
+		return NULL;
+	}
+
+	perstaprof_fixed = (struct wlan_ml_bv_linfo_perstaprof *)subelement;
 
-	/* check DTIM info present bit */
-	if (qdf_test_bit(WLAN_ML_BV_LINFO_PERSTAPROF_STACTRL_DTIMINFOP_IDX,
-			 (unsigned long *)&subelement[STA_CTRL_1]))
-		tmp_len = tmp_len + MIN_IE_LEN;
+	subelement_len = sizeof(*perstaprof_fixed);
 
-	/* check Beacon interval present bit */
-	if (qdf_test_bit(WLAN_ML_BV_LINFO_PERSTAPROF_STACTRL_BCNINTP_IDX,
-			 (unsigned long *)&subelement[STA_CTRL_1]))
-		tmp_len = tmp_len + TAG_LEN_POS;
+	stacontrol = le16toh(perstaprof_fixed->stacontrol);
 
-	/* check STA link mac addr info present bit */
-	if (qdf_test_bit(WLAN_ML_BV_LINFO_PERSTAPROF_STACTRL_MACADDRP_IDX,
-			 (unsigned long *)&subelement[STA_CTRL_1])) {
-		tmp_len = tmp_len + QDF_MAC_ADDR_SIZE;
-		mlo_debug("copied mac addr: " QDF_MAC_ADDR_FMT, &tmp[4]);
-		qdf_mem_copy(&bssid->bytes, &tmp[4], QDF_MAC_ADDR_SIZE);
+	if (linkid) {
+		*linkid = QDF_GET_BITS(stacontrol,
+				       WLAN_ML_BV_LINFO_PERSTAPROF_STACTRL_LINKID_IDX,
+				       WLAN_ML_BV_LINFO_PERSTAPROF_STACTRL_LINKID_BITS);
 	}
 
-	/* Add Link ID offset,as it will always be present in assoc rsp mlo ie */
-	tmp_len = tmp_len + TAG_LEN_POS;
+	/* Check if this a complete profile */
+	completeprofile = QDF_GET_BITS(stacontrol,
+				       WLAN_ML_BV_LINFO_PERSTAPROF_STACTRL_CMPLTPROF_IDX,
+				       WLAN_ML_BV_LINFO_PERSTAPROF_STACTRL_CMPLTPROF_BITS);
+
+	/*  We increment the measured length of the per-STA profile by checking
+	 *  for the presence of individual fields. We validate this length
+	 *  against the total length of the sublement only at the end, except in
+	 *  cases where we are actually about to access a given field.
+	 */
+
+	if (is_macaddr_valid)
+		*is_macaddr_valid = false;
+
+	/* Check STA MAC address present bit */
+	if (QDF_GET_BITS(stacontrol,
+			 WLAN_ML_BV_LINFO_PERSTAPROF_STACTRL_MACADDRP_IDX,
+			 WLAN_ML_BV_LINFO_PERSTAPROF_STACTRL_MACADDRP_BITS)) {
+		if (macaddr) {
+			/* Explicityly check if the length is sufficient to hold
+			 * the STA MAC address, since we are about to attempt to
+			 * access the STA MAC address.
+			 */
+			if (len < (subelement_len + QDF_MAC_ADDR_SIZE)) {
+				mlo_err_rl("len %zu octets is smaller than min size of per-STA profile required to accommodate STA MAC address (%zu octets)",
+					   len,
+					   (subelement_len + QDF_MAC_ADDR_SIZE));
+				return NULL;
+			}
 
-	/* check NTSR Link pair present bit */
-	if (qdf_test_bit(WLAN_ML_BV_LINFO_PERSTAPROF_STACTRL_NSTRLINKPRP_IDX % 8,
-			 (unsigned long *)&subelement[STA_CTRL_2]))
-		tmp_len = tmp_len + MIN_IE_LEN;
+			qdf_mem_copy(macaddr->bytes,
+				     subelement + subelement_len,
+				     QDF_MAC_ADDR_SIZE);
 
-	/* check NTSR bitmap size present bit */
-	if (qdf_test_bit(WLAN_ML_BV_LINFO_PERSTAPROF_STACTRL_NSTRBMSZ_IDX % 8,
-			 (unsigned long *)&subelement[STA_CTRL_2]))
-		tmp_len = tmp_len + TAG_LEN_POS;
+			mlo_nofl_debug("Copied MAC address: " QDF_MAC_ADDR_FMT,
+				       subelement + subelement_len);
 
-	if (len <= tmp_len) {
-		mlo_err("len %zu <= tmp_len %zu, return", len, tmp_len);
+			if (is_macaddr_valid)
+				*is_macaddr_valid = true;
+		}
+
+		subelement_len += QDF_MAC_ADDR_SIZE;
+	}
+
+	/* Check Beacon Interval present bit */
+	if (QDF_GET_BITS(stacontrol,
+			 WLAN_ML_BV_LINFO_PERSTAPROF_STACTRL_BCNINTP_IDX,
+			 WLAN_ML_BV_LINFO_PERSTAPROF_STACTRL_BCNINTP_BITS))
+		subelement_len += WLAN_BEACONINTERVAL_LEN;
+
+	/* Check DTIM Info present bit */
+	if (QDF_GET_BITS(stacontrol,
+			 WLAN_ML_BV_LINFO_PERSTAPROF_STACTRL_DTIMINFOP_IDX,
+			 WLAN_ML_BV_LINFO_PERSTAPROF_STACTRL_DTIMINFOP_BITS))
+		subelement_len +=
+			sizeof(struct wlan_ml_bv_linfo_perstaprof_stainfo_dtiminfo);
+
+	/* Check NTSR Link pair present bit */
+	nstrlppresent =
+		QDF_GET_BITS(stacontrol,
+			     WLAN_ML_BV_LINFO_PERSTAPROF_STACTRL_NSTRLINKPRP_IDX,
+			     WLAN_ML_BV_LINFO_PERSTAPROF_STACTRL_NSTRLINKPRP_BITS);
+
+	if (completeprofile && nstrlppresent) {
+		/* Check NTSR Bitmap Size bit */
+		nstrbmsz =
+			QDF_GET_BITS(stacontrol,
+				     WLAN_ML_BV_LINFO_PERSTAPROF_STACTRL_NSTRBMSZ_IDX,
+				     WLAN_ML_BV_LINFO_PERSTAPROF_STACTRL_NSTRBMSZ_BITS);
+
+		if (nstrbmsz == WLAN_ML_BV_LINFO_PERSTAPROF_STACTRL_NSTRBMSZ_1_OCTET) {
+			subelement_len += 1;
+		} else if (nstrbmsz == WLAN_ML_BV_LINFO_PERSTAPROF_STACTRL_NSTRBMSZ_2_OCTETS) {
+			subelement_len += 2;
+		} else {
+			/* Though an invalid value cannot occur if only 1 bit is
+			 * used, we check for it in a generic manner in case the
+			 * number of bits is increased in the future.
+			 */
+			mlo_err_rl("Invalid NSTR Bitmap size %u", nstrbmsz);
+			return NULL;
+		}
+	}
+
+	/* Note: Some implementation versions of hostapd/wpa_supplicant may
+	 * provide a per-STA profile without STA profile. Let the caller
+	 * indicate whether a STA profile is required to be found. This may be
+	 * revisited as upstreaming progresses.
+	 */
+	if (!is_staprof_reqd) {
+		if (len < subelement_len) {
+			mlo_err_rl("len %zu < subelement_len %zu",
+				   len,
+				   subelement_len);
+			return NULL;
+		}
+
+		return &subelement[subelement_len - 1];
+	}
+
+	if (len <= subelement_len) {
+		mlo_err_rl("len %zu <= subelement_len %zu",
+			   len,
+			   subelement_len);
 		return NULL;
 	}
-	*per_sta_prof_ie_len = len - tmp_len;
 
-	return &tmp[tmp_len];
+	if (staprof_len)
+		*staprof_len = len - subelement_len;
+
+	return &subelement[subelement_len];
 }
 
 static
@@ -210,6 +320,101 @@ uint8_t *util_get_successorfrag(uint8_t *currie, uint8_t *frame, qdf_size_t len)
 	return nextie;
 }
 
+static
+QDF_STATUS util_parse_partner_info_from_linkinfo(uint8_t *linkinfo,
+						 qdf_size_t linkinfo_len,
+						 struct mlo_partner_info *partner_info)
+{
+	uint8_t linkid;
+	struct qdf_mac_addr macaddr;
+	bool is_macaddr_valid;
+	uint8_t *currpos;
+	qdf_size_t currlen;
+	uint8_t *endofstainfo;
+
+	if (!linkinfo) {
+		mlo_err("linkinfo is NULL");
+		return QDF_STATUS_E_NULL_VALUE;
+	}
+
+	if (!linkinfo_len) {
+		mlo_err("linkinfo_len is zero");
+		return QDF_STATUS_E_NULL_VALUE;
+	}
+
+	if (!partner_info) {
+		mlo_err("ML partner info is NULL");
+		return QDF_STATUS_E_NULL_VALUE;
+	}
+
+	partner_info->num_partner_links = 0;
+	currpos = linkinfo;
+	currlen = linkinfo_len;
+
+	while (currlen) {
+		if (currlen <  MIN_IE_LEN) {
+			mlo_err_rl("currlen %zu is smaller than minimum IE length %u",
+				   currlen, MIN_IE_LEN);
+			return QDF_STATUS_E_PROTO;
+		}
+
+		if (currlen <  (MIN_IE_LEN + currpos[TAG_LEN_POS])) {
+			mlo_err_rl("currlen %zu is smaller than length of current IE %u",
+				   currlen, MIN_IE_LEN + currpos[TAG_LEN_POS]);
+			return QDF_STATUS_E_PROTO;
+		}
+
+		if (currpos[ID_POS] ==
+				WLAN_ML_BV_LINFO_SUBELEMID_PERSTAPROFILE) {
+			is_macaddr_valid = false;
+
+			/* Per-STA profile fragmentation support may be added
+			 * once support for this is introduced in the standard.
+			 */
+			endofstainfo = util_parse_perstaprofile(currpos,
+								currlen,
+								false,
+								NULL,
+								&linkid,
+								&is_macaddr_valid,
+								&macaddr);
+
+			if (!endofstainfo) {
+				mlo_err_rl("Error in parsing per-STA profile");
+				return QDF_STATUS_E_EMPTY;
+			}
+
+			if (is_macaddr_valid) {
+				if (partner_info->num_partner_links >=
+					QDF_ARRAY_SIZE(partner_info->partner_link_info)) {
+					mlo_err_rl("Insufficient size %zu of array for partner link info",
+						   QDF_ARRAY_SIZE(partner_info->partner_link_info));
+					return QDF_STATUS_E_NOMEM;
+				}
+
+				partner_info->partner_link_info[partner_info->num_partner_links].link_id =
+					linkid;
+				qdf_mem_copy(&partner_info->partner_link_info[partner_info->num_partner_links].link_addr,
+					     &macaddr,
+					     sizeof(partner_info->partner_link_info[partner_info->num_partner_links].link_addr));
+
+				partner_info->num_partner_links++;
+			} else {
+				mlo_warn_rl("MAC address not found in STA Info field of per-STA profile with link ID %u",
+					    linkid);
+			}
+		}
+
+		currlen -= (MIN_IE_LEN + currpos[TAG_LEN_POS]);
+		currpos += (MIN_IE_LEN + currpos[TAG_LEN_POS]);
+	}
+
+	mlo_debug("Number of ML partner links found=%u",
+		  partner_info->num_partner_links);
+
+	return QDF_STATUS_SUCCESS;
+}
+
 QDF_STATUS util_gen_link_assoc_rsp(uint8_t *frame, qdf_size_t len,
 				   struct qdf_mac_addr link_addr,
 				   uint8_t *assoc_link_frame)
@@ -217,10 +422,11 @@ QDF_STATUS util_gen_link_assoc_rsp(uint8_t *frame, qdf_size_t len,
 	uint8_t *tmp = NULL;
 	const uint8_t *tmp_old, *rsn_ie;
 	qdf_size_t sub_len, tmp_rem_len;
-	qdf_size_t link_info_len, per_sta_prof_len = 0;
+	qdf_size_t link_info_len, sta_prof_len = 0;
 	uint8_t *subelement;
 	uint8_t *pos;
 	uint8_t *sub_copy, *orig_copy;
+	bool is_bssid_valid;
 	struct qdf_mac_addr bssid;
 	struct wlan_frame_hdr *hdr;
 
@@ -257,9 +463,9 @@ QDF_STATUS util_gen_link_assoc_rsp(uint8_t *frame, qdf_size_t len,
 	qdf_mem_copy(sub_copy, subelement, sub_len);
 
 	/* parse ml ie */
-	sub_copy = util_parse_multi_link_ctrl_ie(sub_copy,
-						 sub_len,
-						 &link_info_len);
+	sub_copy = util_parse_multi_link_ctrl(sub_copy,
+					      sub_len,
+					      &link_info_len);
 
 	if (!sub_copy)
 		return QDF_STATUS_E_NULL_VALUE;
@@ -268,11 +474,19 @@ QDF_STATUS util_gen_link_assoc_rsp(uint8_t *frame, qdf_size_t len,
 	QDF_TRACE_HEX_DUMP(QDF_MODULE_ID_MLO, QDF_TRACE_LEVEL_DEBUG,
 			   sub_copy, link_info_len);
 
-	/* parse sta profile ie */
-	sub_copy = util_parse_sta_profile_ie(sub_copy,
-					     link_info_len,
-					     &per_sta_prof_len,
-					     &bssid);
+	is_bssid_valid = false;
+
+	/* Parse per-STA profile */
+	sub_copy = util_parse_perstaprofile(sub_copy,
+					    link_info_len,
+					    true,
+					    &sta_prof_len,
+					    NULL,
+					    &is_bssid_valid,
+					    &bssid);
+
+	if (!is_bssid_valid)
+		return QDF_STATUS_E_NULL_VALUE;
 
 	if (!sub_copy) {
 		qdf_mem_copy(pos, frame, len);
@@ -289,7 +503,7 @@ QDF_STATUS util_gen_link_assoc_rsp(uint8_t *frame, qdf_size_t len,
 	while (((tmp_old + tmp_old[TAG_LEN_POS] + MIN_IE_LEN) - frame) <= len) {
 		tmp = (uint8_t *)util_find_eid(tmp_old[0],
 					       sub_copy,
-					       per_sta_prof_len);
+					       sta_prof_len);
 		if (!tmp) {
 			/* ie in old ie but not in subelement */
 			if (tmp_old[2] != WLAN_EXTN_ELEMID_MULTI_LINK) {
@@ -308,7 +522,7 @@ QDF_STATUS util_gen_link_assoc_rsp(uint8_t *frame, qdf_size_t len,
 			 * vendor ie, compare OUI + type + subType to
 			 * determine if they are the same ie.
 			 */
-			tmp_rem_len = per_sta_prof_len - (tmp - sub_copy);
+			tmp_rem_len = sta_prof_len - (tmp - sub_copy);
 			if (tmp_old[0] == WLAN_ELEMID_VENDOR &&
 			    tmp_rem_len >= MIN_VENDOR_TAG_LEN) {
 				if (!qdf_mem_cmp(tmp_old + PAYLOAD_START_POS,
@@ -624,4 +838,96 @@ util_get_bvmlie_primary_linkid(uint8_t *mlieseq, qdf_size_t mlieseqlen,
 
 	return QDF_STATUS_SUCCESS;
 }
+
+QDF_STATUS
+util_get_bvmlie_persta_partner_info(uint8_t *mlie, qdf_size_t mlielen,
+				    struct mlo_partner_info *partner_info)
+{
+	struct wlan_ie_multilink *mlie_fixed;
+	uint16_t mlcontrol;
+	enum wlan_ml_variant variant;
+	uint8_t *linkinfo;
+	qdf_size_t linkinfo_len;
+	struct mlo_partner_info pinfo = {0};
+	QDF_STATUS ret;
+
+	if (!mlie) {
+		mlo_err("mlie is NULL");
+		return QDF_STATUS_E_NULL_VALUE;
+	}
+
+	if (!mlielen) {
+		mlo_err("mlielen is zero");
+		return QDF_STATUS_E_NULL_VALUE;
+	}
+
+	if (!partner_info) {
+		mlo_err("partner_info is NULL");
+		return QDF_STATUS_E_NULL_VALUE;
+	}
+
+	partner_info->num_partner_links = 0;
+
+	if (mlielen < sizeof(struct wlan_ie_multilink)) {
+		mlo_err_rl("mlielen %zu octets is smaller than required for the fixed portion of Multi-Link element (%zu octets)",
+			   mlielen, sizeof(struct wlan_ie_multilink));
+		return QDF_STATUS_E_INVAL;
+	}
+
+	/* Note: Multi-Link element fragmentation support will be added in a
+	 * later change once shared helper utilities for the same are available.
+	 * As of now, we temporarily return error if the mlielen is greater than
+	 * the max length for an IE.
+	 */
+	if (mlielen > (sizeof(struct ie_header) + WLAN_MAX_IE_LEN)) {
+		mlo_err_rl("Element fragmentation is not yet supported for this API");
+		return QDF_STATUS_E_NOSUPPORT;
+	}
+
+	mlie_fixed = (struct wlan_ie_multilink *)mlie;
+
+	if ((mlie_fixed->elem_id != WLAN_ELEMID_EXTN_ELEM) ||
+	    (mlie_fixed->elem_id_ext != WLAN_EXTN_ELEMID_MULTI_LINK)) {
+		mlo_err("The element is not a Multi-Link element");
+		return QDF_STATUS_E_INVAL;
+	}
+
+	mlcontrol = le16toh(mlie_fixed->mlcontrol);
+
+	variant = QDF_GET_BITS(mlcontrol, WLAN_ML_CTRL_TYPE_IDX,
+			       WLAN_ML_CTRL_TYPE_BITS);
+
+	if (variant != WLAN_ML_VARIANT_BASIC) {
+		mlo_err("The variant value %u does not correspond to Basic Variant value %u",
+			variant, WLAN_ML_VARIANT_BASIC);
+		return QDF_STATUS_E_INVAL;
+	}
+
+	linkinfo_len = 0;
+	linkinfo = util_parse_multi_link_ctrl(mlie, mlielen,
+					      &linkinfo_len);
+
+	if (!linkinfo) {
+		mlo_err_rl("Error in parsing Multi-Link element control");
+		return QDF_STATUS_E_INVAL;
+	}
+
+	/* In case Link Info is absent as indicated by the Link Info length
+	 * being 0, return success. The number of partner links will remain 0.
+	 */
+	if (!linkinfo_len) {
+		mlo_warn_rl("Link Info is absent");
+		return QDF_STATUS_SUCCESS;
+	}
+
+	ret = util_parse_partner_info_from_linkinfo(linkinfo,
+						    linkinfo_len,
+						    &pinfo);
+
+	if (QDF_IS_STATUS_ERROR(ret))
+		return ret;
+
+	qdf_mem_copy(partner_info, &pinfo, sizeof(*partner_info));
+	return QDF_STATUS_SUCCESS;
+}
 #endif