diff mbox series

[8/8] wifi: ath12k: Add MLO peer assoc command support

Message ID 20241023133004.2253830-9-kvalo@kernel.org (mailing list archive)
State Changes Requested
Delegated to: Jeff Johnson
Headers show
Series wifi: ath12k: MLO support part 2 | expand

Commit Message

Kalle Valo Oct. 23, 2024, 1:30 p.m. UTC
From: Sriram R <quic_srirrama@quicinc.com>

Add changes to send MLO peer assoc command with partner link details and
primary umac details

Tested-on: QCN9274 hw2.0 PCI WLAN.WBE.1.0.1-00029-QCAHKSWPL_SILICONZ-1
Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.0.c5-00481-QCAHMTSWPL_V1.0_V2.0_SILICONZ-3

Signed-off-by: Sriram R <quic_srirrama@quicinc.com>
Signed-off-by: Harshitha Prem <quic_hprem@quicinc.com>
Signed-off-by: Kalle Valo <quic_kvalo@quicinc.com>
---
 drivers/net/wireless/ath/ath12k/core.h |  7 +++
 drivers/net/wireless/ath/ath12k/mac.c  | 62 ++++++++++++++++++++
 drivers/net/wireless/ath/ath12k/wmi.c  | 79 ++++++++++++++++++++++++--
 drivers/net/wireless/ath/ath12k/wmi.h  | 46 +++++++++++++++
 4 files changed, 188 insertions(+), 6 deletions(-)

Comments

Jeff Johnson Oct. 23, 2024, 4:10 p.m. UTC | #1
On 10/23/2024 6:30 AM, Kalle Valo wrote:
> From: Sriram R <quic_srirrama@quicinc.com>
> 
> Add changes to send MLO peer assoc command with partner link details and
> primary umac details
> 
> Tested-on: QCN9274 hw2.0 PCI WLAN.WBE.1.0.1-00029-QCAHKSWPL_SILICONZ-1
> Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.0.c5-00481-QCAHMTSWPL_V1.0_V2.0_SILICONZ-3
> 
> Signed-off-by: Sriram R <quic_srirrama@quicinc.com>
> Signed-off-by: Harshitha Prem <quic_hprem@quicinc.com>
> Signed-off-by: Kalle Valo <quic_kvalo@quicinc.com>
> ---
>  drivers/net/wireless/ath/ath12k/core.h |  7 +++
>  drivers/net/wireless/ath/ath12k/mac.c  | 62 ++++++++++++++++++++
>  drivers/net/wireless/ath/ath12k/wmi.c  | 79 ++++++++++++++++++++++++--
>  drivers/net/wireless/ath/ath12k/wmi.h  | 46 +++++++++++++++
>  4 files changed, 188 insertions(+), 6 deletions(-)
> 
> diff --git a/drivers/net/wireless/ath/ath12k/core.h b/drivers/net/wireless/ath/ath12k/core.h
> index 0a0c1a1594f2..e7a2d43e7b8a 100644
> --- a/drivers/net/wireless/ath/ath12k/core.h
> +++ b/drivers/net/wireless/ath/ath12k/core.h
> @@ -487,9 +487,16 @@ struct ath12k_link_sta {
>  	struct ath12k_rx_peer_stats *rx_stats;
>  	struct ath12k_wbm_tx_stats *wbm_tx_stats;
>  	u32 bw_prev;
> +
> +	/* For now the assoc link will be considered primary */
> +	bool is_assoc_link;
> +
> +	 /* for firmware use only */
> +	u8 link_idx;
>  };
>  
>  struct ath12k_sta {
> +	struct ath12k_vif *ahvif;
>  	enum hal_pn_type pn_type;
>  	struct ath12k_link_sta deflink;
>  	struct ath12k_link_sta __rcu *link[IEEE80211_MLD_MAX_NUM_LINKS];
> diff --git a/drivers/net/wireless/ath/ath12k/mac.c b/drivers/net/wireless/ath/ath12k/mac.c
> index b628bc2fd0f5..2e79849974f0 100644
> --- a/drivers/net/wireless/ath/ath12k/mac.c
> +++ b/drivers/net/wireless/ath/ath12k/mac.c
> @@ -2873,6 +2873,67 @@ static void ath12k_peer_assoc_h_eht(struct ath12k *ar,
>  	arg->punct_bitmap = ~arvif->punct_bitmap;
>  }
>  
> +static void ath12k_peer_assoc_h_mlo(struct ath12k_link_sta *arsta,
> +				    struct ath12k_wmi_peer_assoc_arg *arg)
> +{
> +	struct ieee80211_sta *sta = ath12k_ahsta_to_sta(arsta->ahsta);
> +	struct peer_assoc_mlo_params *ml = &arg->ml;
> +	struct ath12k_sta *ahsta = arsta->ahsta;
> +	struct ath12k_link_sta *arsta_p;
> +	struct ath12k_link_vif *arvif;
> +	unsigned long links;
> +	u8 link_id;
> +	int i;
> +
> +	if (!sta->mlo || ahsta->ml_peer_id == ATH12K_MLO_PEER_ID_INVALID)
> +		return;
> +
> +	ml->enabled = true;
> +	ml->assoc_link = arsta->is_assoc_link;
> +
> +	/* For now considering the primary umac based on assoc link */
> +	ml->primary_umac = arsta->is_assoc_link;
> +	ml->peer_id_valid = true;
> +	ml->logical_link_idx_valid = true;
> +
> +	ether_addr_copy(ml->mld_addr, sta->addr);
> +	ml->logical_link_idx = arsta->link_idx;
> +	ml->ml_peer_id = ahsta->ml_peer_id;
> +	ml->ieee_link_id = arsta->link_id;
> +	ml->num_partner_links = 0;
> +	links = ahsta->links_map;
> +
> +	rcu_read_lock();
> +
> +	i = 0;

nit: setting i=0 doesn't need to be RCU protected

> +
> +	for_each_set_bit(link_id, &links, IEEE80211_MLD_MAX_NUM_LINKS) {
> +		if (i >= ATH12K_WMI_MLO_MAX_LINKS)
> +			break;
> +
> +		arsta_p = rcu_dereference(ahsta->link[link_id]);
> +		arvif = rcu_dereference(ahsta->ahvif->link[link_id]);
> +
> +		if (arsta_p == arsta)
> +			continue;
> +
> +		if (!arvif->is_started)
> +			continue;
> +
> +		ml->partner_info[i].vdev_id = arvif->vdev_id;
> +		ml->partner_info[i].hw_link_id = arvif->ar->pdev->hw_link_id;
> +		ml->partner_info[i].assoc_link = arsta_p->is_assoc_link;
> +		ml->partner_info[i].primary_umac = arsta_p->is_assoc_link;
> +		ml->partner_info[i].logical_link_idx_valid = true;
> +		ml->partner_info[i].logical_link_idx = arsta_p->link_idx;
> +		ml->num_partner_links++;
> +
> +		i++;
> +	}
> +
> +	rcu_read_unlock();
> +}
> +
>  static void ath12k_peer_assoc_prepare(struct ath12k *ar,
>  				      struct ath12k_link_vif *arvif,
>  				      struct ath12k_link_sta *arsta,
> @@ -2897,6 +2958,7 @@ static void ath12k_peer_assoc_prepare(struct ath12k *ar,
>  	ath12k_peer_assoc_h_qos(ar, arvif, arsta, arg);
>  	ath12k_peer_assoc_h_phymode(ar, arvif, arsta, arg);
>  	ath12k_peer_assoc_h_smps(arsta, arg);
> +	ath12k_peer_assoc_h_mlo(arsta, arg);
>  
>  	/* TODO: amsdu_disable req? */
>  }
> diff --git a/drivers/net/wireless/ath/ath12k/wmi.c b/drivers/net/wireless/ath/ath12k/wmi.c
> index 0583d832fac7..73c1c6bcf48b 100644
> --- a/drivers/net/wireless/ath/ath12k/wmi.c
> +++ b/drivers/net/wireless/ath/ath12k/wmi.c
> @@ -2101,12 +2101,15 @@ int ath12k_wmi_send_peer_assoc_cmd(struct ath12k *ar,
>  	struct ath12k_wmi_vht_rate_set_params *mcs;
>  	struct ath12k_wmi_he_rate_set_params *he_mcs;
>  	struct ath12k_wmi_eht_rate_set_params *eht_mcs;
> +	struct wmi_peer_assoc_mlo_params *ml_params;
> +	struct wmi_peer_assoc_mlo_partner_info *partner_info;
>  	struct sk_buff *skb;
>  	struct wmi_tlv *tlv;
>  	void *ptr;
>  	u32 peer_legacy_rates_align;
>  	u32 peer_ht_rates_align;
>  	int i, ret, len;
> +	__le32 v;
>  
>  	peer_legacy_rates_align = roundup(arg->peer_legacy_rates.num_rates,
>  					  sizeof(u32));
> @@ -2118,8 +2121,13 @@ int ath12k_wmi_send_peer_assoc_cmd(struct ath12k *ar,
>  	      TLV_HDR_SIZE + (peer_ht_rates_align * sizeof(u8)) +
>  	      sizeof(*mcs) + TLV_HDR_SIZE +
>  	      (sizeof(*he_mcs) * arg->peer_he_mcs_count) +
> -	      TLV_HDR_SIZE + (sizeof(*eht_mcs) * arg->peer_eht_mcs_count) +
> -	      TLV_HDR_SIZE + TLV_HDR_SIZE;
> +	      TLV_HDR_SIZE + (sizeof(*eht_mcs) * arg->peer_eht_mcs_count);
> +
> +	if (arg->ml.enabled)
> +		len += TLV_HDR_SIZE + sizeof(*ml_params) +
> +		       TLV_HDR_SIZE + (arg->ml.num_partner_links * sizeof(*partner_info));
> +	else
> +		len += (2 * TLV_HDR_SIZE);
>  
>  	skb = ath12k_wmi_alloc_skb(wmi->wmi_ab, len);
>  	if (!skb)
> @@ -2243,12 +2251,38 @@ int ath12k_wmi_send_peer_assoc_cmd(struct ath12k *ar,
>  		ptr += sizeof(*he_mcs);
>  	}
>  
> -	/* MLO header tag with 0 length */
> -	len = 0;
>  	tlv = ptr;
> +	len = arg->ml.enabled ? sizeof(*ml_params) : 0;
>  	tlv->header = ath12k_wmi_tlv_hdr(WMI_TAG_ARRAY_STRUCT, len);
>  	ptr += TLV_HDR_SIZE;
> +	if (!len)
> +		goto skip_ml_params;
>  
> +	ml_params = ptr;
> +	ml_params->tlv_header = ath12k_wmi_tlv_cmd_hdr(WMI_TAG_MLO_PEER_ASSOC_PARAMS,
> +						       len);

this is another instance where we are using the same length for two
consecutive TLVs -- that doesn't seem right

> +	ml_params->flags = cpu_to_le32(ATH12K_WMI_FLAG_MLO_ENABLED);
> +
> +	if (arg->ml.assoc_link)
> +		ml_params->flags |= cpu_to_le32(ATH12K_WMI_FLAG_MLO_ASSOC_LINK);
> +
> +	if (arg->ml.primary_umac)
> +		ml_params->flags |= cpu_to_le32(ATH12K_WMI_FLAG_MLO_PRIMARY_UMAC);
> +
> +	if (arg->ml.logical_link_idx_valid)
> +		ml_params->flags |=
> +			cpu_to_le32(ATH12K_WMI_FLAG_MLO_LOGICAL_LINK_IDX_VALID);
> +
> +	if (arg->ml.peer_id_valid)
> +		ml_params->flags |= cpu_to_le32(ATH12K_WMI_FLAG_MLO_PEER_ID_VALID);
> +
> +	ether_addr_copy(ml_params->mld_addr.addr, arg->ml.mld_addr);
> +	ml_params->logical_link_idx = cpu_to_le32(arg->ml.logical_link_idx);
> +	ml_params->ml_peer_id = cpu_to_le32(arg->ml.ml_peer_id);
> +	ml_params->ieee_link_id = cpu_to_le32(arg->ml.ieee_link_id);
> +	ptr += sizeof(*ml_params);
> +
> +skip_ml_params:
>  	/* Loop through the EHT rate set */
>  	len = arg->peer_eht_mcs_count * sizeof(*eht_mcs);
>  	tlv = ptr;
> @@ -2265,12 +2299,45 @@ int ath12k_wmi_send_peer_assoc_cmd(struct ath12k *ar,
>  		ptr += sizeof(*eht_mcs);
>  	}
>  
> -	/* ML partner links tag with 0 length */
> -	len = 0;
>  	tlv = ptr;
> +	len = arg->ml.enabled ? arg->ml.num_partner_links * sizeof(*partner_info) : 0;
> +	/* fill ML Partner links */
>  	tlv->header = ath12k_wmi_tlv_hdr(WMI_TAG_ARRAY_STRUCT, len);
>  	ptr += TLV_HDR_SIZE;
>  
> +	if (len == 0)
> +		goto send;
> +
> +	for (i = 0; i < arg->ml.num_partner_links; i++) {
> +		u32 cmd = WMI_TAG_MLO_PARTNER_LINK_PARAMS_PEER_ASSOC;
> +
> +		partner_info = ptr;
> +		partner_info->tlv_header = ath12k_wmi_tlv_cmd_hdr(cmd,
> +								  sizeof(*partner_info));
> +		partner_info->vdev_id = cpu_to_le32(arg->ml.partner_info[i].vdev_id);
> +		partner_info->hw_link_id =
> +			cpu_to_le32(arg->ml.partner_info[i].hw_link_id);
> +		partner_info->flags = cpu_to_le32(ATH12K_WMI_FLAG_MLO_ENABLED);
> +
> +		if (arg->ml.partner_info[i].assoc_link)
> +			partner_info->flags |=
> +				cpu_to_le32(ATH12K_WMI_FLAG_MLO_ASSOC_LINK);
> +
> +		if (arg->ml.partner_info[i].primary_umac)
> +			partner_info->flags |=
> +				cpu_to_le32(ATH12K_WMI_FLAG_MLO_PRIMARY_UMAC);
> +
> +		if (arg->ml.partner_info[i].logical_link_idx_valid) {
> +			v = cpu_to_le32(ATH12K_WMI_FLAG_MLO_LINK_ID_VALID);
> +			partner_info->flags |= v;
> +		}
> +
> +		partner_info->logical_link_idx =
> +			cpu_to_le32(arg->ml.partner_info[i].logical_link_idx);
> +		ptr += sizeof(*partner_info);
> +	}
> +
> +send:
>  	ath12k_dbg(ar->ab, ATH12K_DBG_WMI,
>  		   "wmi peer assoc vdev id %d assoc id %d peer mac %pM peer_flags %x rate_caps %x peer_caps %x listen_intval %d ht_caps %x max_mpdu %d nss %d phymode %d peer_mpdu_density %d vht_caps %x he cap_info %x he ops %x he cap_info_ext %x he phy %x %x %x peer_bw_rxnss_override %x peer_flags_ext %x eht mac_cap %x %x eht phy_cap %x %x %x\n",
>  		   cmd->vdev_id, cmd->peer_associd, arg->peer_mac,
> diff --git a/drivers/net/wireless/ath/ath12k/wmi.h b/drivers/net/wireless/ath/ath12k/wmi.h
> index 07bd275608bf..e93f74e97771 100644
> --- a/drivers/net/wireless/ath/ath12k/wmi.h
> +++ b/drivers/net/wireless/ath/ath12k/wmi.h
> @@ -3698,6 +3698,24 @@ struct wmi_vdev_install_key_arg {
>  #define WMI_HECAP_TXRX_MCS_NSS_IDX_160		1
>  #define WMI_HECAP_TXRX_MCS_NSS_IDX_80_80	2
>  
> +#define ATH12K_WMI_MLO_MAX_PARTNER_LINKS \
> +	(ATH12K_WMI_MLO_MAX_LINKS + ATH12K_MAX_NUM_BRIDGE_LINKS - 1)
> +
> +struct peer_assoc_mlo_params {
> +	bool enabled;
> +	bool assoc_link;
> +	bool primary_umac;
> +	bool peer_id_valid;
> +	bool logical_link_idx_valid;
> +	bool bridge_peer;
> +	u8 mld_addr[ETH_ALEN];
> +	u32 logical_link_idx;
> +	u32 ml_peer_id;
> +	u32 ieee_link_id;
> +	u8 num_partner_links;
> +	struct wmi_ml_partner_info partner_info[ATH12K_WMI_MLO_MAX_LINKS];
> +};
> +
>  struct wmi_rate_set_arg {
>  	u32 num_rates;
>  	u8 rates[WMI_MAX_SUPPORTED_RATES];
> @@ -3772,8 +3790,36 @@ struct ath12k_wmi_peer_assoc_arg {
>  	u32 peer_eht_tx_mcs_set[WMI_MAX_EHTCAP_RATE_SET];
>  	struct ath12k_wmi_ppe_threshold_arg peer_eht_ppet;
>  	u32 punct_bitmap;
> +	bool is_assoc;
> +	struct peer_assoc_mlo_params ml;
>  };
>  
> +#define ATH12K_WMI_FLAG_MLO_ENABLED			BIT(0)
> +#define ATH12K_WMI_FLAG_MLO_ASSOC_LINK			BIT(1)
> +#define ATH12K_WMI_FLAG_MLO_PRIMARY_UMAC		BIT(2)
> +#define ATH12K_WMI_FLAG_MLO_LINK_ID_VALID		BIT(3)
> +#define ATH12K_WMI_FLAG_MLO_PEER_ID_VALID		BIT(4)
> +
> +struct wmi_peer_assoc_mlo_partner_info {

this doesn't follow the WMI naming convention, should be _params

> +	__le32 tlv_header;
> +	__le32 vdev_id;
> +	__le32 hw_link_id;
> +	__le32 flags;
> +	__le32 logical_link_idx;
> +} __packed;
> +
> +struct wmi_peer_assoc_mlo_params {
> +	__le32 tlv_header;
> +	__le32 flags;
> +	struct wmi_mac_addr mld_addr;
> +	__le32 logical_link_idx;
> +	__le32 ml_peer_id;
> +	__le32 ieee_link_id;
> +	__le32 emlsr_trans_timeout_us;
> +	__le32 emlsr_trans_delay_us;
> +	__le32 emlsr_padding_delay_us;
> +} __packed;
> +
>  struct wmi_peer_assoc_complete_cmd {
>  	__le32 tlv_header;
>  	struct ath12k_wmi_mac_addr_params peer_macaddr;
Kalle Valo Oct. 29, 2024, 4:05 p.m. UTC | #2
Jeff Johnson <quic_jjohnson@quicinc.com> writes:

> On 10/23/2024 6:30 AM, Kalle Valo wrote:
>
>> +	rcu_read_lock();
>> +
>> +	i = 0;
>
> nit: setting i=0 doesn't need to be RCU protected

Yeah, but that doesn't cause any issues and this way it's closer to the
for loop where it's used:

>> +	for_each_set_bit(link_id, &links, IEEE80211_MLD_MAX_NUM_LINKS) {
>> +		if (i >= ATH12K_WMI_MLO_MAX_LINKS)
>> +			break;

[...]

>> @@ -2243,12 +2251,38 @@ int ath12k_wmi_send_peer_assoc_cmd(struct ath12k *ar,
>>  		ptr += sizeof(*he_mcs);
>>  	}
>>  
>> -	/* MLO header tag with 0 length */
>> -	len = 0;
>>  	tlv = ptr;
>> +	len = arg->ml.enabled ? sizeof(*ml_params) : 0;
>>  	tlv->header = ath12k_wmi_tlv_hdr(WMI_TAG_ARRAY_STRUCT, len);
>>  	ptr += TLV_HDR_SIZE;
>> +	if (!len)
>> +		goto skip_ml_params;
>>  
>> +	ml_params = ptr;
>> +	ml_params->tlv_header = ath12k_wmi_tlv_cmd_hdr(WMI_TAG_MLO_PEER_ASSOC_PARAMS,
>> +						       len);
>
> this is another instance where we are using the same length for two
> consecutive TLVs -- that doesn't seem right

This is also a similar case of _hdr() vs _cmd_hdr(), does that look ok?
Jeff Johnson Oct. 29, 2024, 4:10 p.m. UTC | #3
On 10/29/2024 9:05 AM, Kalle Valo wrote:
> Jeff Johnson <quic_jjohnson@quicinc.com> writes:
> 
>> On 10/23/2024 6:30 AM, Kalle Valo wrote:
>>
>>> +	rcu_read_lock();
>>> +
>>> +	i = 0;
>>
>> nit: setting i=0 doesn't need to be RCU protected
> 
> Yeah, but that doesn't cause any issues and this way it's closer to the
> for loop where it's used:
> 
>>> +	for_each_set_bit(link_id, &links, IEEE80211_MLD_MAX_NUM_LINKS) {
>>> +		if (i >= ATH12K_WMI_MLO_MAX_LINKS)
>>> +			break;
> 
> [...]
> 
>>> @@ -2243,12 +2251,38 @@ int ath12k_wmi_send_peer_assoc_cmd(struct ath12k *ar,
>>>  		ptr += sizeof(*he_mcs);
>>>  	}
>>>  
>>> -	/* MLO header tag with 0 length */
>>> -	len = 0;
>>>  	tlv = ptr;
>>> +	len = arg->ml.enabled ? sizeof(*ml_params) : 0;
>>>  	tlv->header = ath12k_wmi_tlv_hdr(WMI_TAG_ARRAY_STRUCT, len);
>>>  	ptr += TLV_HDR_SIZE;
>>> +	if (!len)
>>> +		goto skip_ml_params;
>>>  
>>> +	ml_params = ptr;
>>> +	ml_params->tlv_header = ath12k_wmi_tlv_cmd_hdr(WMI_TAG_MLO_PEER_ASSOC_PARAMS,
>>> +						       len);
>>
>> this is another instance where we are using the same length for two
>> consecutive TLVs -- that doesn't seem right
> 
> This is also a similar case of _hdr() vs _cmd_hdr(), does that look ok?
> 
here again this is evil. please change to _hdr() and explicitly subtract out
the tlv header size
diff mbox series

Patch

diff --git a/drivers/net/wireless/ath/ath12k/core.h b/drivers/net/wireless/ath/ath12k/core.h
index 0a0c1a1594f2..e7a2d43e7b8a 100644
--- a/drivers/net/wireless/ath/ath12k/core.h
+++ b/drivers/net/wireless/ath/ath12k/core.h
@@ -487,9 +487,16 @@  struct ath12k_link_sta {
 	struct ath12k_rx_peer_stats *rx_stats;
 	struct ath12k_wbm_tx_stats *wbm_tx_stats;
 	u32 bw_prev;
+
+	/* For now the assoc link will be considered primary */
+	bool is_assoc_link;
+
+	 /* for firmware use only */
+	u8 link_idx;
 };
 
 struct ath12k_sta {
+	struct ath12k_vif *ahvif;
 	enum hal_pn_type pn_type;
 	struct ath12k_link_sta deflink;
 	struct ath12k_link_sta __rcu *link[IEEE80211_MLD_MAX_NUM_LINKS];
diff --git a/drivers/net/wireless/ath/ath12k/mac.c b/drivers/net/wireless/ath/ath12k/mac.c
index b628bc2fd0f5..2e79849974f0 100644
--- a/drivers/net/wireless/ath/ath12k/mac.c
+++ b/drivers/net/wireless/ath/ath12k/mac.c
@@ -2873,6 +2873,67 @@  static void ath12k_peer_assoc_h_eht(struct ath12k *ar,
 	arg->punct_bitmap = ~arvif->punct_bitmap;
 }
 
+static void ath12k_peer_assoc_h_mlo(struct ath12k_link_sta *arsta,
+				    struct ath12k_wmi_peer_assoc_arg *arg)
+{
+	struct ieee80211_sta *sta = ath12k_ahsta_to_sta(arsta->ahsta);
+	struct peer_assoc_mlo_params *ml = &arg->ml;
+	struct ath12k_sta *ahsta = arsta->ahsta;
+	struct ath12k_link_sta *arsta_p;
+	struct ath12k_link_vif *arvif;
+	unsigned long links;
+	u8 link_id;
+	int i;
+
+	if (!sta->mlo || ahsta->ml_peer_id == ATH12K_MLO_PEER_ID_INVALID)
+		return;
+
+	ml->enabled = true;
+	ml->assoc_link = arsta->is_assoc_link;
+
+	/* For now considering the primary umac based on assoc link */
+	ml->primary_umac = arsta->is_assoc_link;
+	ml->peer_id_valid = true;
+	ml->logical_link_idx_valid = true;
+
+	ether_addr_copy(ml->mld_addr, sta->addr);
+	ml->logical_link_idx = arsta->link_idx;
+	ml->ml_peer_id = ahsta->ml_peer_id;
+	ml->ieee_link_id = arsta->link_id;
+	ml->num_partner_links = 0;
+	links = ahsta->links_map;
+
+	rcu_read_lock();
+
+	i = 0;
+
+	for_each_set_bit(link_id, &links, IEEE80211_MLD_MAX_NUM_LINKS) {
+		if (i >= ATH12K_WMI_MLO_MAX_LINKS)
+			break;
+
+		arsta_p = rcu_dereference(ahsta->link[link_id]);
+		arvif = rcu_dereference(ahsta->ahvif->link[link_id]);
+
+		if (arsta_p == arsta)
+			continue;
+
+		if (!arvif->is_started)
+			continue;
+
+		ml->partner_info[i].vdev_id = arvif->vdev_id;
+		ml->partner_info[i].hw_link_id = arvif->ar->pdev->hw_link_id;
+		ml->partner_info[i].assoc_link = arsta_p->is_assoc_link;
+		ml->partner_info[i].primary_umac = arsta_p->is_assoc_link;
+		ml->partner_info[i].logical_link_idx_valid = true;
+		ml->partner_info[i].logical_link_idx = arsta_p->link_idx;
+		ml->num_partner_links++;
+
+		i++;
+	}
+
+	rcu_read_unlock();
+}
+
 static void ath12k_peer_assoc_prepare(struct ath12k *ar,
 				      struct ath12k_link_vif *arvif,
 				      struct ath12k_link_sta *arsta,
@@ -2897,6 +2958,7 @@  static void ath12k_peer_assoc_prepare(struct ath12k *ar,
 	ath12k_peer_assoc_h_qos(ar, arvif, arsta, arg);
 	ath12k_peer_assoc_h_phymode(ar, arvif, arsta, arg);
 	ath12k_peer_assoc_h_smps(arsta, arg);
+	ath12k_peer_assoc_h_mlo(arsta, arg);
 
 	/* TODO: amsdu_disable req? */
 }
diff --git a/drivers/net/wireless/ath/ath12k/wmi.c b/drivers/net/wireless/ath/ath12k/wmi.c
index 0583d832fac7..73c1c6bcf48b 100644
--- a/drivers/net/wireless/ath/ath12k/wmi.c
+++ b/drivers/net/wireless/ath/ath12k/wmi.c
@@ -2101,12 +2101,15 @@  int ath12k_wmi_send_peer_assoc_cmd(struct ath12k *ar,
 	struct ath12k_wmi_vht_rate_set_params *mcs;
 	struct ath12k_wmi_he_rate_set_params *he_mcs;
 	struct ath12k_wmi_eht_rate_set_params *eht_mcs;
+	struct wmi_peer_assoc_mlo_params *ml_params;
+	struct wmi_peer_assoc_mlo_partner_info *partner_info;
 	struct sk_buff *skb;
 	struct wmi_tlv *tlv;
 	void *ptr;
 	u32 peer_legacy_rates_align;
 	u32 peer_ht_rates_align;
 	int i, ret, len;
+	__le32 v;
 
 	peer_legacy_rates_align = roundup(arg->peer_legacy_rates.num_rates,
 					  sizeof(u32));
@@ -2118,8 +2121,13 @@  int ath12k_wmi_send_peer_assoc_cmd(struct ath12k *ar,
 	      TLV_HDR_SIZE + (peer_ht_rates_align * sizeof(u8)) +
 	      sizeof(*mcs) + TLV_HDR_SIZE +
 	      (sizeof(*he_mcs) * arg->peer_he_mcs_count) +
-	      TLV_HDR_SIZE + (sizeof(*eht_mcs) * arg->peer_eht_mcs_count) +
-	      TLV_HDR_SIZE + TLV_HDR_SIZE;
+	      TLV_HDR_SIZE + (sizeof(*eht_mcs) * arg->peer_eht_mcs_count);
+
+	if (arg->ml.enabled)
+		len += TLV_HDR_SIZE + sizeof(*ml_params) +
+		       TLV_HDR_SIZE + (arg->ml.num_partner_links * sizeof(*partner_info));
+	else
+		len += (2 * TLV_HDR_SIZE);
 
 	skb = ath12k_wmi_alloc_skb(wmi->wmi_ab, len);
 	if (!skb)
@@ -2243,12 +2251,38 @@  int ath12k_wmi_send_peer_assoc_cmd(struct ath12k *ar,
 		ptr += sizeof(*he_mcs);
 	}
 
-	/* MLO header tag with 0 length */
-	len = 0;
 	tlv = ptr;
+	len = arg->ml.enabled ? sizeof(*ml_params) : 0;
 	tlv->header = ath12k_wmi_tlv_hdr(WMI_TAG_ARRAY_STRUCT, len);
 	ptr += TLV_HDR_SIZE;
+	if (!len)
+		goto skip_ml_params;
 
+	ml_params = ptr;
+	ml_params->tlv_header = ath12k_wmi_tlv_cmd_hdr(WMI_TAG_MLO_PEER_ASSOC_PARAMS,
+						       len);
+	ml_params->flags = cpu_to_le32(ATH12K_WMI_FLAG_MLO_ENABLED);
+
+	if (arg->ml.assoc_link)
+		ml_params->flags |= cpu_to_le32(ATH12K_WMI_FLAG_MLO_ASSOC_LINK);
+
+	if (arg->ml.primary_umac)
+		ml_params->flags |= cpu_to_le32(ATH12K_WMI_FLAG_MLO_PRIMARY_UMAC);
+
+	if (arg->ml.logical_link_idx_valid)
+		ml_params->flags |=
+			cpu_to_le32(ATH12K_WMI_FLAG_MLO_LOGICAL_LINK_IDX_VALID);
+
+	if (arg->ml.peer_id_valid)
+		ml_params->flags |= cpu_to_le32(ATH12K_WMI_FLAG_MLO_PEER_ID_VALID);
+
+	ether_addr_copy(ml_params->mld_addr.addr, arg->ml.mld_addr);
+	ml_params->logical_link_idx = cpu_to_le32(arg->ml.logical_link_idx);
+	ml_params->ml_peer_id = cpu_to_le32(arg->ml.ml_peer_id);
+	ml_params->ieee_link_id = cpu_to_le32(arg->ml.ieee_link_id);
+	ptr += sizeof(*ml_params);
+
+skip_ml_params:
 	/* Loop through the EHT rate set */
 	len = arg->peer_eht_mcs_count * sizeof(*eht_mcs);
 	tlv = ptr;
@@ -2265,12 +2299,45 @@  int ath12k_wmi_send_peer_assoc_cmd(struct ath12k *ar,
 		ptr += sizeof(*eht_mcs);
 	}
 
-	/* ML partner links tag with 0 length */
-	len = 0;
 	tlv = ptr;
+	len = arg->ml.enabled ? arg->ml.num_partner_links * sizeof(*partner_info) : 0;
+	/* fill ML Partner links */
 	tlv->header = ath12k_wmi_tlv_hdr(WMI_TAG_ARRAY_STRUCT, len);
 	ptr += TLV_HDR_SIZE;
 
+	if (len == 0)
+		goto send;
+
+	for (i = 0; i < arg->ml.num_partner_links; i++) {
+		u32 cmd = WMI_TAG_MLO_PARTNER_LINK_PARAMS_PEER_ASSOC;
+
+		partner_info = ptr;
+		partner_info->tlv_header = ath12k_wmi_tlv_cmd_hdr(cmd,
+								  sizeof(*partner_info));
+		partner_info->vdev_id = cpu_to_le32(arg->ml.partner_info[i].vdev_id);
+		partner_info->hw_link_id =
+			cpu_to_le32(arg->ml.partner_info[i].hw_link_id);
+		partner_info->flags = cpu_to_le32(ATH12K_WMI_FLAG_MLO_ENABLED);
+
+		if (arg->ml.partner_info[i].assoc_link)
+			partner_info->flags |=
+				cpu_to_le32(ATH12K_WMI_FLAG_MLO_ASSOC_LINK);
+
+		if (arg->ml.partner_info[i].primary_umac)
+			partner_info->flags |=
+				cpu_to_le32(ATH12K_WMI_FLAG_MLO_PRIMARY_UMAC);
+
+		if (arg->ml.partner_info[i].logical_link_idx_valid) {
+			v = cpu_to_le32(ATH12K_WMI_FLAG_MLO_LINK_ID_VALID);
+			partner_info->flags |= v;
+		}
+
+		partner_info->logical_link_idx =
+			cpu_to_le32(arg->ml.partner_info[i].logical_link_idx);
+		ptr += sizeof(*partner_info);
+	}
+
+send:
 	ath12k_dbg(ar->ab, ATH12K_DBG_WMI,
 		   "wmi peer assoc vdev id %d assoc id %d peer mac %pM peer_flags %x rate_caps %x peer_caps %x listen_intval %d ht_caps %x max_mpdu %d nss %d phymode %d peer_mpdu_density %d vht_caps %x he cap_info %x he ops %x he cap_info_ext %x he phy %x %x %x peer_bw_rxnss_override %x peer_flags_ext %x eht mac_cap %x %x eht phy_cap %x %x %x\n",
 		   cmd->vdev_id, cmd->peer_associd, arg->peer_mac,
diff --git a/drivers/net/wireless/ath/ath12k/wmi.h b/drivers/net/wireless/ath/ath12k/wmi.h
index 07bd275608bf..e93f74e97771 100644
--- a/drivers/net/wireless/ath/ath12k/wmi.h
+++ b/drivers/net/wireless/ath/ath12k/wmi.h
@@ -3698,6 +3698,24 @@  struct wmi_vdev_install_key_arg {
 #define WMI_HECAP_TXRX_MCS_NSS_IDX_160		1
 #define WMI_HECAP_TXRX_MCS_NSS_IDX_80_80	2
 
+#define ATH12K_WMI_MLO_MAX_PARTNER_LINKS \
+	(ATH12K_WMI_MLO_MAX_LINKS + ATH12K_MAX_NUM_BRIDGE_LINKS - 1)
+
+struct peer_assoc_mlo_params {
+	bool enabled;
+	bool assoc_link;
+	bool primary_umac;
+	bool peer_id_valid;
+	bool logical_link_idx_valid;
+	bool bridge_peer;
+	u8 mld_addr[ETH_ALEN];
+	u32 logical_link_idx;
+	u32 ml_peer_id;
+	u32 ieee_link_id;
+	u8 num_partner_links;
+	struct wmi_ml_partner_info partner_info[ATH12K_WMI_MLO_MAX_LINKS];
+};
+
 struct wmi_rate_set_arg {
 	u32 num_rates;
 	u8 rates[WMI_MAX_SUPPORTED_RATES];
@@ -3772,8 +3790,36 @@  struct ath12k_wmi_peer_assoc_arg {
 	u32 peer_eht_tx_mcs_set[WMI_MAX_EHTCAP_RATE_SET];
 	struct ath12k_wmi_ppe_threshold_arg peer_eht_ppet;
 	u32 punct_bitmap;
+	bool is_assoc;
+	struct peer_assoc_mlo_params ml;
 };
 
+#define ATH12K_WMI_FLAG_MLO_ENABLED			BIT(0)
+#define ATH12K_WMI_FLAG_MLO_ASSOC_LINK			BIT(1)
+#define ATH12K_WMI_FLAG_MLO_PRIMARY_UMAC		BIT(2)
+#define ATH12K_WMI_FLAG_MLO_LINK_ID_VALID		BIT(3)
+#define ATH12K_WMI_FLAG_MLO_PEER_ID_VALID		BIT(4)
+
+struct wmi_peer_assoc_mlo_partner_info {
+	__le32 tlv_header;
+	__le32 vdev_id;
+	__le32 hw_link_id;
+	__le32 flags;
+	__le32 logical_link_idx;
+} __packed;
+
+struct wmi_peer_assoc_mlo_params {
+	__le32 tlv_header;
+	__le32 flags;
+	struct wmi_mac_addr mld_addr;
+	__le32 logical_link_idx;
+	__le32 ml_peer_id;
+	__le32 ieee_link_id;
+	__le32 emlsr_trans_timeout_us;
+	__le32 emlsr_trans_delay_us;
+	__le32 emlsr_padding_delay_us;
+} __packed;
+
 struct wmi_peer_assoc_complete_cmd {
 	__le32 tlv_header;
 	struct ath12k_wmi_mac_addr_params peer_macaddr;