public inbox for linux-wireless@vger.kernel.org
 help / color / mirror / Atom feed
* [PATCH wireless-next v2 0/2] wifi: mac80211: Introduce eMLSR/eMLMR parsing support in AP mode.
@ 2026-01-25 10:51 Lorenzo Bianconi
  2026-01-25 10:51 ` [PATCH wireless-next v2 1/2] wifi: mac80211: Add eMLSR/eMLMR action frame parsing support Lorenzo Bianconi
  2026-01-25 10:51 ` [PATCH wireless-next v2 2/2] wifi: mt76: mt7996: Add eMLSR support Lorenzo Bianconi
  0 siblings, 2 replies; 9+ messages in thread
From: Lorenzo Bianconi @ 2026-01-25 10:51 UTC (permalink / raw)
  To: Johannes Berg, Ryder Lee, Sean Wang, Matthias Brugger,
	AngeloGioacchino Del Regno
  Cc: linux-wireless, Felix Fietkau, Shayne Chen, Lorenzo Bianconi,
	Christian Marangi, linux-mediatek, linux-arm-kernel, MeiChia Chiu

Introduce support in AP mode for parsing of the Operating Mode Notification
frame sent by the client to enable/disable MLO eMLSR or eMLMR if supported
by both the AP and the client.
Add drv_set_eml_op_mode mac80211 callback in order to configure underlay
driver with eMLSR info (control and bitmap).
Implement drv_set_eml_op_mode callback for MT7996 driver.

---
Changes in v2:
- Improve sanity check against device EML capabilities
- Squash patch 1/2 and 2/2
- Validate link_bitmap with vif->active_links
- Introduce ieee80211_eml_params struct as containe for EML info to pass
  to the underlay driver.
- Pass padding_delay and transition_delay to the underlay driver.
- Implement drv_set_eml_op_mode callback for MT7996 driver.
- Link to v1: https://lore.kernel.org/r/20260122-mac80211-emlsr-v1-0-f0e43bb6d95a@kernel.org

---
Lorenzo Bianconi (1):
      wifi: mac80211: Add eMLSR/eMLMR action frame parsing support

MeiChia Chiu (1):
      wifi: mt76: mt7996: Add eMLSR support

 .../net/wireless/mediatek/mt76/mt76_connac_mcu.h   |   9 ++
 drivers/net/wireless/mediatek/mt76/mt7996/main.c   |  16 +++
 drivers/net/wireless/mediatek/mt76/mt7996/mcu.c    |  54 +++++++++
 drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h |   5 +
 include/linux/ieee80211-eht.h                      |  28 +++++
 include/linux/ieee80211.h                          |   6 +
 include/net/mac80211.h                             |  23 ++++
 net/mac80211/driver-ops.h                          |  22 ++++
 net/mac80211/eht.c                                 | 135 +++++++++++++++++++++
 net/mac80211/ieee80211_i.h                         |   2 +
 net/mac80211/iface.c                               |  10 +-
 net/mac80211/rx.c                                  |   8 ++
 net/mac80211/trace.h                               |  32 +++++
 13 files changed, 349 insertions(+), 1 deletion(-)
---
base-commit: 1e1dd9eeaab3908746d1dce5db6b0c29e0d28d6d
change-id: 20260121-mac80211-emlsr-5774082ff8cc

Best regards,
-- 
Lorenzo Bianconi <lorenzo@kernel.org>


^ permalink raw reply	[flat|nested] 9+ messages in thread

* [PATCH wireless-next v2 1/2] wifi: mac80211: Add eMLSR/eMLMR action frame parsing support
  2026-01-25 10:51 [PATCH wireless-next v2 0/2] wifi: mac80211: Introduce eMLSR/eMLMR parsing support in AP mode Lorenzo Bianconi
@ 2026-01-25 10:51 ` Lorenzo Bianconi
  2026-01-26 10:59   ` Johannes Berg
  2026-01-25 10:51 ` [PATCH wireless-next v2 2/2] wifi: mt76: mt7996: Add eMLSR support Lorenzo Bianconi
  1 sibling, 1 reply; 9+ messages in thread
From: Lorenzo Bianconi @ 2026-01-25 10:51 UTC (permalink / raw)
  To: Johannes Berg, Ryder Lee, Sean Wang, Matthias Brugger,
	AngeloGioacchino Del Regno
  Cc: linux-wireless, Felix Fietkau, Shayne Chen, Lorenzo Bianconi,
	Christian Marangi, linux-mediatek, linux-arm-kernel

Introduce support in AP mode for parsing of the Operating Mode Notification
frame sent by the client to enable/disable MLO eMLSR or eMLMR if supported
by both the AP and the client.
Add drv_set_eml_op_mode mac80211 callback in order to configure underlay
driver with eMLSR/eMLMR info.

Tested-by: Christian Marangi <ansuelsmth@gmail.com>
Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
---
 include/linux/ieee80211-eht.h |  28 +++++++++
 include/linux/ieee80211.h     |   6 ++
 include/net/mac80211.h        |  23 +++++++
 net/mac80211/driver-ops.h     |  22 +++++++
 net/mac80211/eht.c            | 135 ++++++++++++++++++++++++++++++++++++++++++
 net/mac80211/ieee80211_i.h    |   2 +
 net/mac80211/iface.c          |  10 +++-
 net/mac80211/rx.c             |   8 +++
 net/mac80211/trace.h          |  32 ++++++++++
 9 files changed, 265 insertions(+), 1 deletion(-)

diff --git a/include/linux/ieee80211-eht.h b/include/linux/ieee80211-eht.h
index f9782e46c5e5241bccc84c3bbc18b1cc9ec1879c..d78496d0307a1ff1e03a27b3491394166850f3e5 100644
--- a/include/linux/ieee80211-eht.h
+++ b/include/linux/ieee80211-eht.h
@@ -558,6 +558,34 @@ struct ieee80211_mle_tdls_common_info {
 
 #define IEEE80211_MLC_PRIO_ACCESS_PRES_AP_MLD_MAC_ADDR	0x0010
 
+#define IEEE80211_EML_CTRL_EMLSR_MODE		BIT(0)
+#define IEEE80211_EML_CTRL_EMLMR_MODE		BIT(1)
+#define IEEE80211_EML_CTRL_EMLSR_PARAM_UPDATE	BIT(2)
+#define IEEE80211_EML_CTRL_INDEV_COEX_ACT	BIT(3)
+
+#define IEEE80211_EML_EMLSR_PAD_DELAY		0x07
+#define IEEE80211_EML_EMLSR_TRANS_DELAY		0x38
+
+static inline u8 ieee80211_get_emlsr_pad_delay_update(u8 param)
+{
+	u8 pad_delay = FIELD_GET(IEEE80211_EML_EMLSR_PAD_DELAY, param);
+
+	if (pad_delay > IEEE80211_EML_CAP_EMLSR_PADDING_DELAY_256US)
+		pad_delay = 0;
+
+	return pad_delay;
+}
+
+static inline u32 ieee80211_get_emlsr_trans_delay_update(u8 param)
+{
+	u16 trans_delay = FIELD_GET(IEEE80211_EML_EMLSR_TRANS_DELAY, param);
+
+	if (trans_delay > IEEE80211_EML_CAP_EMLSR_TRANSITION_DELAY_256US)
+		trans_delay = 0;
+
+	return trans_delay;
+}
+
 /* no fixed fields in PRIO_ACCESS */
 
 /**
diff --git a/include/linux/ieee80211.h b/include/linux/ieee80211.h
index fbde215c25aa79efd339aa530896a29dbb1a8ff8..f2c6f34f39f24ce59cbb2650f70e898d24d12901 100644
--- a/include/linux/ieee80211.h
+++ b/include/linux/ieee80211.h
@@ -1186,6 +1186,12 @@ struct ieee80211_mgmt {
 					u8 action_code;
 					u8 variable[];
 				} __packed epcs;
+				struct {
+					u8 action_code;
+					u8 dialog_token;
+					u8 control;
+					u8 variable[];
+				} __packed eml_omn;
 			} u;
 		} __packed action;
 		DECLARE_FLEX_ARRAY(u8, body); /* Generic frame body */
diff --git a/include/net/mac80211.h b/include/net/mac80211.h
index 36ae7fe9ddf35190921f4fee0fe3294418007a56..fb7ba70ffa639dabba4aa5760c27b2d70f030c28 100644
--- a/include/net/mac80211.h
+++ b/include/net/mac80211.h
@@ -1902,6 +1902,21 @@ enum ieee80211_offload_flags {
 	IEEE80211_OFFLOAD_DECAP_ENABLED		= BIT(2),
 };
 
+struct ieee80211_eml_params {
+	u8 control;
+	u16 link_bitmap;
+	union {
+		struct {
+			u16 emlsr_pad_delay;
+			u16 emlsr_trans_delay;
+		};
+		struct {
+			u8 mcs_map_count;
+			u8 mcs_map_bw[9];
+		};
+	};
+};
+
 /**
  * struct ieee80211_vif_cfg - interface configuration
  * @assoc: association status
@@ -4513,6 +4528,9 @@ struct ieee80211_prep_tx_info {
  *      interface with the specified type would be added, and thus drivers that
  *      implement this callback need to handle such cases. The type is the full
  *      &enum nl80211_iftype.
+ * @set_eml_op_mode: Configure eMLSR/eMLMR operation mode in the underlay
+ *	driver according to the parameter received in the EML Operating mode
+ *	notification frame.
  */
 struct ieee80211_ops {
 	void (*tx)(struct ieee80211_hw *hw,
@@ -4908,6 +4926,11 @@ struct ieee80211_ops {
 			struct ieee80211_neg_ttlm *ttlm);
 	void (*prep_add_interface)(struct ieee80211_hw *hw,
 				   enum nl80211_iftype type);
+	int (*set_eml_op_mode)(struct ieee80211_hw *hw,
+			       struct ieee80211_vif *vif,
+			       struct ieee80211_sta *sta,
+			       unsigned int link_id,
+			       struct ieee80211_eml_params *eml_params);
 };
 
 /**
diff --git a/net/mac80211/driver-ops.h b/net/mac80211/driver-ops.h
index 55105d238d6bc5963eb2863575805bee72c42399..29f2b9bab859b50de5420beb4984211eb2270bae 100644
--- a/net/mac80211/driver-ops.h
+++ b/net/mac80211/driver-ops.h
@@ -1772,4 +1772,26 @@ drv_prep_add_interface(struct ieee80211_local *local,
 	trace_drv_return_void(local);
 }
 
+static inline int drv_set_eml_op_mode(struct ieee80211_sub_if_data *sdata,
+				      struct ieee80211_sta *sta,
+				      unsigned int link_id,
+				      struct ieee80211_eml_params *eml_params)
+{
+	struct ieee80211_local *local = sdata->local;
+	int ret = 0;
+
+	might_sleep();
+	lockdep_assert_wiphy(local->hw.wiphy);
+
+	trace_drv_set_eml_op_mode(local, sdata, sta, link_id,
+				  eml_params->control,
+				  eml_params->link_bitmap);
+	if (local->ops->set_eml_op_mode)
+		ret = local->ops->set_eml_op_mode(&local->hw, &sdata->vif,
+						  sta, link_id, eml_params);
+	trace_drv_return_int(local, ret);
+
+	return ret;
+}
+
 #endif /* __MAC80211_DRIVER_OPS */
diff --git a/net/mac80211/eht.c b/net/mac80211/eht.c
index fd41046e3b681b753e6cc7ddf82046e4bc5df9b3..29cb348e9f1bbd541f6a6d82899087f36be4ef22 100644
--- a/net/mac80211/eht.c
+++ b/net/mac80211/eht.c
@@ -5,6 +5,7 @@
  * Copyright(c) 2021-2025 Intel Corporation
  */
 
+#include "driver-ops.h"
 #include "ieee80211_i.h"
 
 void
@@ -102,3 +103,137 @@ ieee80211_eht_cap_ie_to_sta_eht_cap(struct ieee80211_sub_if_data *sdata,
 
 	ieee80211_sta_recalc_aggregates(&link_sta->sta->sta);
 }
+
+static void
+ieee80211_send_eml_op_mode_notif(struct ieee80211_sub_if_data *sdata,
+				 struct ieee80211_mgmt *req, u8 act_len)
+{
+	int hdr_len = offsetof(struct ieee80211_mgmt, u.action.u.eml_omn);
+	struct ieee80211_local *local = sdata->local;
+	struct ieee80211_mgmt *mgmt;
+	struct sk_buff *skb;
+
+	skb = dev_alloc_skb(local->tx_headroom + hdr_len + act_len);
+	if (!skb)
+		return;
+
+	skb_reserve(skb, local->tx_headroom);
+	mgmt = skb_put_zero(skb, hdr_len);
+	mgmt->frame_control = cpu_to_le16(IEEE80211_FTYPE_MGMT |
+					  IEEE80211_STYPE_ACTION);
+	memcpy(mgmt->da, req->sa, ETH_ALEN);
+	memcpy(mgmt->sa, sdata->vif.addr, ETH_ALEN);
+	memcpy(mgmt->bssid, sdata->vif.addr, ETH_ALEN);
+
+	mgmt->u.action.category = WLAN_CATEGORY_PROTECTED_EHT;
+	memcpy(&mgmt->u.action.u.eml_omn, &req->u.action.u.eml_omn, act_len);
+	mgmt->u.action.u.eml_omn.control &=
+		~(IEEE80211_EML_CTRL_EMLSR_PARAM_UPDATE |
+		  IEEE80211_EML_CTRL_INDEV_COEX_ACT);
+	ieee80211_tx_skb(sdata, skb);
+}
+
+void ieee80211_rx_eml_op_mode_notif(struct ieee80211_sub_if_data *sdata,
+				    struct sk_buff *skb)
+{
+	int hdr_len = offsetof(struct ieee80211_mgmt, u.action.u.eml_omn);
+	enum nl80211_iftype type = ieee80211_vif_type_p2p(&sdata->vif);
+	struct ieee80211_rx_status *status = IEEE80211_SKB_RXCB(skb);
+	const struct wiphy_iftype_ext_capab *ift_ext_capa;
+	struct ieee80211_mgmt *mgmt = (void *)skb->data;
+	struct ieee80211_local *local = sdata->local;
+	u8 control = mgmt->u.action.u.eml_omn.control;
+	u8 *ptr = mgmt->u.action.u.eml_omn.variable;
+	struct ieee80211_eml_params eml_params = {};
+	struct sta_info *sta;
+	u8 act_len = 3; /* action_code + dialog_token + control */
+
+	if (!ieee80211_vif_is_mld(&sdata->vif))
+		return;
+
+	/* eMLSR and eMLMR can't be enabled at the same time */
+	if ((control & IEEE80211_EML_CTRL_EMLSR_MODE) &&
+	    (control & IEEE80211_EML_CTRL_EMLMR_MODE))
+		return;
+
+	if ((control & IEEE80211_EML_CTRL_EMLMR_MODE) &&
+	    (control & IEEE80211_EML_CTRL_EMLSR_PARAM_UPDATE))
+		return;
+
+	ift_ext_capa = cfg80211_get_iftype_ext_capa(local->hw.wiphy, type);
+	if (!ift_ext_capa)
+		return;
+
+	if (!status->link_valid)
+		return;
+
+	sta = sta_info_get_bss(sdata, mgmt->sa);
+	if (!sta)
+		return;
+
+	if (control & IEEE80211_EML_CTRL_EMLSR_MODE) {
+		if (!(ift_ext_capa->eml_capabilities &
+		      IEEE80211_EML_CAP_EMLSR_SUPP))
+			return;
+
+		if (control & IEEE80211_EML_CTRL_EMLSR_PARAM_UPDATE) {
+			u16 eml_cap = sta->sta.eml_cap;
+			u8 pad_delay, trans_delay;
+
+			/* Update sta padding and transition delay */
+			pad_delay =
+				ieee80211_get_emlsr_pad_delay_update(ptr[3]);
+			trans_delay =
+				ieee80211_get_emlsr_pad_delay_update(ptr[3]);
+
+			eml_cap &= ~(IEEE80211_EML_CAP_EMLSR_PADDING_DELAY |
+				     IEEE80211_EML_CAP_EMLSR_TRANSITION_DELAY);
+			eml_cap |= FIELD_PREP(IEEE80211_EML_EMLSR_PAD_DELAY,
+					      pad_delay) |
+				   FIELD_PREP(IEEE80211_EML_EMLSR_TRANS_DELAY,
+					      trans_delay);
+			sta->sta.eml_cap = eml_cap;
+		}
+
+		eml_params.emlsr_pad_delay =
+			ieee80211_emlsr_pad_delay_in_us(sta->sta.eml_cap);
+		eml_params.emlsr_trans_delay =
+			ieee80211_emlsr_trans_delay_in_us(sta->sta.eml_cap);
+	}
+
+	if (control & IEEE80211_EML_CTRL_EMLMR_MODE) {
+		u8 mcs_map_size;
+
+		if (!(ift_ext_capa->eml_capabilities &
+		      IEEE80211_EML_CAP_EMLMR_SUPPORT))
+			return;
+
+		eml_params.mcs_map_count = ptr[3];
+		if (eml_params.mcs_map_count > 2)
+			return;
+
+		/* mcs_map_count_control and mcs_map_bw */
+		mcs_map_size = 3 * (1 + eml_params.mcs_map_count);
+		memcpy(eml_params.mcs_map_bw, &ptr[4], mcs_map_size);
+		act_len += 1 + mcs_map_size;
+	}
+
+	if ((control & IEEE80211_EML_CTRL_EMLSR_MODE) ||
+	    (control & IEEE80211_EML_CTRL_EMLMR_MODE)) {
+		eml_params.link_bitmap = get_unaligned_le16(ptr);
+		if (eml_params.link_bitmap &&
+		    !(eml_params.link_bitmap & sdata->vif.active_links))
+			return;
+
+		act_len += sizeof(__le16); /* eMLSR/eMLMR link_bitmap */
+	}
+
+	if (skb->len < hdr_len + act_len)
+		return;
+
+	if (drv_set_eml_op_mode(sdata, &sta->sta, status->link_id,
+				&eml_params))
+		return;
+
+	ieee80211_send_eml_op_mode_notif(sdata, mgmt, act_len);
+}
diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h
index dc757cb329740d621f6a9deb4e9ffe258e1b7d67..3ef93cd1fb33e2614c3e06c51d6b1ebcafa4824c 100644
--- a/net/mac80211/ieee80211_i.h
+++ b/net/mac80211/ieee80211_i.h
@@ -2837,6 +2837,8 @@ void ieee80211_destroy_frag_cache(struct ieee80211_fragment_cache *cache);
 
 u8 ieee80211_ie_len_eht_cap(struct ieee80211_sub_if_data *sdata);
 
+void ieee80211_rx_eml_op_mode_notif(struct ieee80211_sub_if_data *sdata,
+				    struct sk_buff *skb);
 void
 ieee80211_eht_cap_ie_to_sta_eht_cap(struct ieee80211_sub_if_data *sdata,
 				    struct ieee80211_supported_band *sband,
diff --git a/net/mac80211/iface.c b/net/mac80211/iface.c
index 3ce94b95decd64eea4ea063ae98c111bdfd57e9c..17476fd9259b68b3d9c649eabad10efa42d56cd7 100644
--- a/net/mac80211/iface.c
+++ b/net/mac80211/iface.c
@@ -1664,7 +1664,15 @@ static void ieee80211_iface_process_skb(struct ieee80211_local *local,
 		}
 	} else if (ieee80211_is_action(mgmt->frame_control) &&
 		   mgmt->u.action.category == WLAN_CATEGORY_PROTECTED_EHT) {
-		if (sdata->vif.type == NL80211_IFTYPE_STATION) {
+		if (sdata->vif.type == NL80211_IFTYPE_AP) {
+			switch (mgmt->u.action.u.eml_omn.action_code) {
+			case WLAN_PROTECTED_EHT_ACTION_EML_OP_MODE_NOTIF:
+				ieee80211_rx_eml_op_mode_notif(sdata, skb);
+				break;
+			default:
+				break;
+			}
+		} else if (sdata->vif.type == NL80211_IFTYPE_STATION) {
 			switch (mgmt->u.action.u.ttlm_req.action_code) {
 			case WLAN_PROTECTED_EHT_ACTION_TTLM_REQ:
 				ieee80211_process_neg_ttlm_req(sdata, mgmt,
diff --git a/net/mac80211/rx.c b/net/mac80211/rx.c
index ac437256f5d5c4b34601fd4c8a6f6f80469b2522..521e44c9e8dac498122e7a595d1eb8bb5751789d 100644
--- a/net/mac80211/rx.c
+++ b/net/mac80211/rx.c
@@ -3928,6 +3928,14 @@ ieee80211_rx_h_action(struct ieee80211_rx_data *rx)
 					      u.action.u.epcs))
 				goto invalid;
 			goto queue;
+		case WLAN_PROTECTED_EHT_ACTION_EML_OP_MODE_NOTIF:
+			if (sdata->vif.type != NL80211_IFTYPE_AP)
+				break;
+
+			if (len < offsetofend(typeof(*mgmt),
+					      u.action.u.eml_omn))
+				goto invalid;
+			goto queue;
 		default:
 			break;
 		}
diff --git a/net/mac80211/trace.h b/net/mac80211/trace.h
index 0bfbce1574862b5a6a2ca39794abea7fe9a3f34a..c04d4547e8f4655ad77b83bed6e5a832d959e61b 100644
--- a/net/mac80211/trace.h
+++ b/net/mac80211/trace.h
@@ -3353,6 +3353,38 @@ TRACE_EVENT(drv_prep_add_interface,
 	)
 );
 
+TRACE_EVENT(drv_set_eml_op_mode,
+	    TP_PROTO(struct ieee80211_local *local,
+		     struct ieee80211_sub_if_data *sdata,
+		     struct ieee80211_sta *sta,
+		     unsigned int link_id,
+		     u8 control, u16 link_bitmap),
+
+	TP_ARGS(local, sdata, sta, link_id, control, link_bitmap),
+
+	TP_STRUCT__entry(LOCAL_ENTRY
+			 VIF_ENTRY
+			 STA_ENTRY
+			 __field(u32, link_id)
+			 __field(u8, control)
+			 __field(u16, link_bitmap)),
+
+	TP_fast_assign(LOCAL_ASSIGN;
+		       VIF_ASSIGN;
+		       STA_NAMED_ASSIGN(sta);
+		       __entry->link_id = link_id;
+		       __entry->control = control;
+		       __entry->link_bitmap = link_bitmap;
+	),
+
+	TP_printk(
+		LOCAL_PR_FMT  VIF_PR_FMT  STA_PR_FMT
+		" (link:%d control:%02x link_bitmap:%04x)",
+		LOCAL_PR_ARG, VIF_PR_ARG, STA_PR_ARG, __entry->link_id,
+		__entry->control, __entry->link_bitmap
+	)
+);
+
 #endif /* !__MAC80211_DRIVER_TRACE || TRACE_HEADER_MULTI_READ */
 
 #undef TRACE_INCLUDE_PATH

-- 
2.52.0


^ permalink raw reply related	[flat|nested] 9+ messages in thread

* [PATCH wireless-next v2 2/2] wifi: mt76: mt7996: Add eMLSR support
  2026-01-25 10:51 [PATCH wireless-next v2 0/2] wifi: mac80211: Introduce eMLSR/eMLMR parsing support in AP mode Lorenzo Bianconi
  2026-01-25 10:51 ` [PATCH wireless-next v2 1/2] wifi: mac80211: Add eMLSR/eMLMR action frame parsing support Lorenzo Bianconi
@ 2026-01-25 10:51 ` Lorenzo Bianconi
  2026-01-26 11:03   ` Christian Marangi
  1 sibling, 1 reply; 9+ messages in thread
From: Lorenzo Bianconi @ 2026-01-25 10:51 UTC (permalink / raw)
  To: Johannes Berg, Ryder Lee, Sean Wang, Matthias Brugger,
	AngeloGioacchino Del Regno
  Cc: linux-wireless, Felix Fietkau, Shayne Chen, Lorenzo Bianconi,
	Christian Marangi, linux-mediatek, linux-arm-kernel, MeiChia Chiu

From: MeiChia Chiu <MeiChia.Chiu@mediatek.com>

Implement set_eml_op_mode mac80211 callback in order to introduce eMLSR
support.

Signed-off-by: MeiChia Chiu <MeiChia.Chiu@mediatek.com>
Co-developed-by: Lorenzo Bianconi <lorenzo@kernel.org>
Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
---
 .../net/wireless/mediatek/mt76/mt76_connac_mcu.h   |  9 ++++
 drivers/net/wireless/mediatek/mt76/mt7996/main.c   | 16 +++++++
 drivers/net/wireless/mediatek/mt76/mt7996/mcu.c    | 54 ++++++++++++++++++++++
 drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h |  5 ++
 4 files changed, 84 insertions(+)

diff --git a/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h b/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h
index 8d59cf43f0e2dc8d8e42d6b2ec7565ad69a79ff8..44697498b38a5b9eff92627b63c383ca8f48a760 100644
--- a/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h
+++ b/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h
@@ -628,6 +628,13 @@ struct sta_rec_tx_proc {
 	__le32 flag;
 } __packed;
 
+struct sta_rec_eml_op {
+	__le16 tag;
+	__le16 len;
+	u8 link_bitmap;
+	u8 link_ant_num[3];
+} __packed;
+
 /* wtbl_rec */
 
 struct wtbl_req_hdr {
@@ -796,6 +803,7 @@ struct wtbl_raw {
 					 sizeof(struct sta_rec_he_6g_capa) + \
 					 sizeof(struct sta_rec_pn_info) + \
 					 sizeof(struct sta_rec_tx_proc) + \
+					 sizeof(struct sta_rec_eml_op) + \
 					 sizeof(struct tlv) +		\
 					 MT76_CONNAC_WTBL_UPDATE_MAX_SIZE)
 
@@ -832,6 +840,7 @@ enum {
 	STA_REC_PN_INFO = 0x26,
 	STA_REC_KEY_V3 = 0x27,
 	STA_REC_HDRT = 0x28,
+	STA_REC_EML_OP = 0x29,
 	STA_REC_HDR_TRANS = 0x2B,
 	STA_REC_MAX_NUM
 };
diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c
index beed795edb24c67e1b7b44fe87fd5de125a21d94..d5b88687ba1801a1e50e81e5c27f1f67fa455ce1 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c
@@ -2275,6 +2275,21 @@ mt7996_reconfig_complete(struct ieee80211_hw *hw,
 					     MT7996_WATCHDOG_TIME);
 }
 
+static int
+mt7996_set_eml_op_mode(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
+		       struct ieee80211_sta *sta, unsigned int link_id,
+		       struct ieee80211_eml_params *eml_params)
+{
+	struct mt7996_dev *dev = mt7996_hw_dev(hw);
+	int ret;
+
+	mutex_lock(&dev->mt76.mutex);
+	ret = mt7996_mcu_set_emlsr_mode(dev, vif, sta, link_id, eml_params);
+	mutex_unlock(&dev->mt76.mutex);
+
+	return ret;
+}
+
 const struct ieee80211_ops mt7996_ops = {
 	.add_chanctx = mt76_add_chanctx,
 	.remove_chanctx = mt76_remove_chanctx,
@@ -2337,4 +2352,5 @@ const struct ieee80211_ops mt7996_ops = {
 	.change_vif_links = mt7996_change_vif_links,
 	.change_sta_links = mt7996_mac_sta_change_links,
 	.reconfig_complete = mt7996_reconfig_complete,
+	.set_eml_op_mode = mt7996_set_eml_op_mode,
 };
diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c
index 14a88ef79b6cb9ee60e62d695af4a90ab1d89846..54d6a41281841c3a86e5308971b1e456da8e0e73 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c
@@ -1170,6 +1170,60 @@ int mt7996_mcu_add_bss_info(struct mt7996_phy *phy, struct ieee80211_vif *vif,
 				     MCU_WMWA_UNI_CMD(BSS_INFO_UPDATE), true);
 }
 
+int mt7996_mcu_set_emlsr_mode(struct mt7996_dev *dev,
+			      struct ieee80211_vif *vif,
+			      struct ieee80211_sta *sta,
+			      unsigned int link_id,
+			      struct ieee80211_eml_params *eml_params)
+{
+	struct mt7996_sta *msta = (struct mt7996_sta *)sta->drv_priv;
+	struct mt7996_sta_link *msta_link;
+	struct sta_rec_eml_op *eml_op;
+	struct mt7996_vif_link *link;
+	struct sk_buff *skb;
+	struct tlv *tlv;
+
+	msta_link = mt76_dereference(msta->link[link_id], &dev->mt76);
+	if (!msta_link)
+		return -EINVAL;
+
+	link = mt7996_vif_link(dev, vif, link_id);
+	if (!link)
+		return -EINVAL;
+
+	skb = __mt76_connac_mcu_alloc_sta_req(&dev->mt76, &link->mt76,
+					      &msta_link->wcid,
+					      MT7996_STA_UPDATE_MAX_SIZE);
+	if (IS_ERR(skb))
+		return PTR_ERR(skb);
+
+	tlv = mt76_connac_mcu_add_tlv(skb, STA_REC_EML_OP, sizeof(*eml_op));
+	eml_op = (struct sta_rec_eml_op *)tlv;
+	eml_op->link_bitmap = 0;
+
+	if (eml_params->control & IEEE80211_EML_CTRL_EMLSR_MODE) {
+		unsigned long link_bitmap = eml_params->link_bitmap;
+
+		for_each_set_bit(link_id, &link_bitmap,
+				 IEEE80211_MLD_MAX_NUM_LINKS) {
+			struct mt76_phy *mphy;
+
+			link = mt7996_vif_link(dev, vif, link_id);
+			if (!link)
+				continue;
+
+			mphy = mt76_vif_link_phy(&link->mt76);
+			if (!mphy)
+				continue;
+
+			eml_op->link_bitmap |= BIT(mphy->band_idx);
+		}
+	}
+
+	return mt76_mcu_skb_send_msg(&dev->mt76, skb,
+				     MCU_WMWA_UNI_CMD(STA_REC_UPDATE), true);
+}
+
 int mt7996_mcu_set_timing(struct mt7996_phy *phy, struct ieee80211_vif *vif,
 			  struct ieee80211_bss_conf *link_conf)
 {
diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h
index 7a884311800ea8cfc0e302b2a140a4072ce18b69..da8688e01c1502cbd895ea40561f77e929c6ee38 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h
+++ b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h
@@ -861,6 +861,11 @@ int mt7996_mcu_wtbl_update_hdr_trans(struct mt7996_dev *dev,
 				     struct mt7996_vif_link *link,
 				     struct mt7996_sta_link *msta_link);
 int mt7996_mcu_cp_support(struct mt7996_dev *dev, u8 mode);
+int mt7996_mcu_set_emlsr_mode(struct mt7996_dev *dev,
+			      struct ieee80211_vif *vif,
+			      struct ieee80211_sta *sta,
+			      unsigned int link_id,
+			      struct ieee80211_eml_params *eml_params);
 #ifdef CONFIG_MAC80211_DEBUGFS
 void mt7996_sta_add_debugfs(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
 			    struct ieee80211_sta *sta, struct dentry *dir);

-- 
2.52.0


^ permalink raw reply related	[flat|nested] 9+ messages in thread

* Re: [PATCH wireless-next v2 1/2] wifi: mac80211: Add eMLSR/eMLMR action frame parsing support
  2026-01-25 10:51 ` [PATCH wireless-next v2 1/2] wifi: mac80211: Add eMLSR/eMLMR action frame parsing support Lorenzo Bianconi
@ 2026-01-26 10:59   ` Johannes Berg
  2026-01-26 22:41     ` Lorenzo Bianconi
  0 siblings, 1 reply; 9+ messages in thread
From: Johannes Berg @ 2026-01-26 10:59 UTC (permalink / raw)
  To: Lorenzo Bianconi, Ryder Lee, Sean Wang, Matthias Brugger,
	AngeloGioacchino Del Regno
  Cc: linux-wireless, Felix Fietkau, Shayne Chen, Christian Marangi,
	linux-mediatek, linux-arm-kernel

On Sun, 2026-01-25 at 11:51 +0100, Lorenzo Bianconi wrote:
> 
> +static inline u8 ieee80211_get_emlsr_pad_delay_update(u8 param)
> +{
> +	u8 pad_delay = FIELD_GET(IEEE80211_EML_EMLSR_PAD_DELAY, param);

I generally prefer the typed versions and mac80211 (mostly?) uses those,
i.e. u8_get_bits() and friends, since they also cover endian conversions
where needed. Is there any reason you use FIELD_* versions here?

> +	if (pad_delay > IEEE80211_EML_CAP_EMLSR_PADDING_DELAY_256US)
> +		pad_delay = 0;

Seems that should use a constant, rather than =0?

Also, is that really the right thing to do (also below) to just silently
cap it? Maybe that should be up to the caller? In some(/most/all)? cases
we should probably even just _reject_ frames that carry an invalid
value, which you can't do with this helper?

> +static inline u32 ieee80211_get_emlsr_trans_delay_update(u8 param)
> +{
> +	u16 trans_delay = FIELD_GET(IEEE80211_EML_EMLSR_TRANS_DELAY, param);
> +
> +	if (trans_delay > IEEE80211_EML_CAP_EMLSR_TRANSITION_DELAY_256US)
> +		trans_delay = 0;
> +
> +	return trans_delay;

why does that use _three_ different types? Wouldn't u8 be sufficient?

> --- a/include/net/mac80211.h
> +++ b/include/net/mac80211.h
> @@ -1902,6 +1902,21 @@ enum ieee80211_offload_flags {
>  	IEEE80211_OFFLOAD_DECAP_ENABLED		= BIT(2),
>  };
>  
> +struct ieee80211_eml_params {
> +	u8 control;
> +	u16 link_bitmap;
> +	union {
> +		struct {
> +			u16 emlsr_pad_delay;
> +			u16 emlsr_trans_delay;
> +		};
> +		struct {
> +			u8 mcs_map_count;
> +			u8 mcs_map_bw[9];
> +		};
> +	};
> +};

Maybe add kernel-doc? Also not sure the union really is worth it? It's a
tiny thing. Especially since you don't even label it - maybe if the
parts were labled emlsr and emlmr, and then you had emlsr.pad_delay?

(I'd label them anyway, of course, even if not a union.)

Also now the emlsr pad/trans delay are duplicated in the station info,
is that worth doing? You have to and do track them there too, anyway, as
we discussed on IRC, could just have the driver use them from there?

Per spec I'm also not sure what the MCS map should be when it's not
included in the frame?

> + * @set_eml_op_mode: Configure eMLSR/eMLMR operation mode in the underlay
> + *	driver according to the parameter received in the EML Operating mode
> + *	notification frame.

Maybe describe the link_id here, or move it to the params and describe
it in kernel-doc there?

> +static inline int drv_set_eml_op_mode(struct ieee80211_sub_if_data *sdata,
> +				      struct ieee80211_sta *sta,
> +				      unsigned int link_id,
> +				      struct ieee80211_eml_params *eml_params)
> +{
> +	struct ieee80211_local *local = sdata->local;
> +	int ret = 0;

Shouldn't that be -EOPNOTSUPP?

> +static void
> +ieee80211_send_eml_op_mode_notif(struct ieee80211_sub_if_data *sdata,
> +				 struct ieee80211_mgmt *req, u8 act_len)
> +{
> +	int hdr_len = offsetof(struct ieee80211_mgmt, u.action.u.eml_omn);
> +	struct ieee80211_local *local = sdata->local;
> +	struct ieee80211_mgmt *mgmt;
> +	struct sk_buff *skb;
> +
> +	skb = dev_alloc_skb(local->tx_headroom + hdr_len + act_len);
> +	if (!skb)
> +		return;
> +
> +	skb_reserve(skb, local->tx_headroom);
> +	mgmt = skb_put_zero(skb, hdr_len);
> +	mgmt->frame_control = cpu_to_le16(IEEE80211_FTYPE_MGMT |
> +					  IEEE80211_STYPE_ACTION);
> +	memcpy(mgmt->da, req->sa, ETH_ALEN);
> +	memcpy(mgmt->sa, sdata->vif.addr, ETH_ALEN);
> +	memcpy(mgmt->bssid, sdata->vif.addr, ETH_ALEN);
> +
> +	mgmt->u.action.category = WLAN_CATEGORY_PROTECTED_EHT;
> +	memcpy(&mgmt->u.action.u.eml_omn, &req->u.action.u.eml_omn, act_len);
> +	mgmt->u.action.u.eml_omn.control &=
> +		~(IEEE80211_EML_CTRL_EMLSR_PARAM_UPDATE |
> +		  IEEE80211_EML_CTRL_INDEV_COEX_ACT);
> +	ieee80211_tx_skb(sdata, skb);

It seems to me that it'd be better to not copy the request, but rather
build the response. It's not _that_ much data, and from the spec it
seems to me that e.g. the MCS map should be included in the response,
but you do that now, I think?

> +void ieee80211_rx_eml_op_mode_notif(struct ieee80211_sub_if_data *sdata,
> +				    struct sk_buff *skb)
> +{
> +	int hdr_len = offsetof(struct ieee80211_mgmt, u.action.u.eml_omn);
> +	enum nl80211_iftype type = ieee80211_vif_type_p2p(&sdata->vif);
> +	struct ieee80211_rx_status *status = IEEE80211_SKB_RXCB(skb);
> +	const struct wiphy_iftype_ext_capab *ift_ext_capa;
> +	struct ieee80211_mgmt *mgmt = (void *)skb->data;
> +	struct ieee80211_local *local = sdata->local;
> +	u8 control = mgmt->u.action.u.eml_omn.control;
> +	u8 *ptr = mgmt->u.action.u.eml_omn.variable;
> +	struct ieee80211_eml_params eml_params = {};
> +	struct sta_info *sta;
> +	u8 act_len = 3; /* action_code + dialog_token + control */
> +
> +	if (!ieee80211_vif_is_mld(&sdata->vif))
> +		return;
> +
> +	/* eMLSR and eMLMR can't be enabled at the same time */
> +	if ((control & IEEE80211_EML_CTRL_EMLSR_MODE) &&
> +	    (control & IEEE80211_EML_CTRL_EMLMR_MODE))
> +		return;
> +
> +	if ((control & IEEE80211_EML_CTRL_EMLMR_MODE) &&
> +	    (control & IEEE80211_EML_CTRL_EMLSR_PARAM_UPDATE))
> +		return;
> +
> +	ift_ext_capa = cfg80211_get_iftype_ext_capa(local->hw.wiphy, type);
> +	if (!ift_ext_capa)
> +		return;
> +
> +	if (!status->link_valid)
> +		return;
> +
> +	sta = sta_info_get_bss(sdata, mgmt->sa);
> +	if (!sta)
> +		return;
> +
> +	if (control & IEEE80211_EML_CTRL_EMLSR_MODE) {
> +		if (!(ift_ext_capa->eml_capabilities &
> +		      IEEE80211_EML_CAP_EMLSR_SUPP))
> +			return;
> +
> +		if (control & IEEE80211_EML_CTRL_EMLSR_PARAM_UPDATE) {
> +			u16 eml_cap = sta->sta.eml_cap;
> +			u8 pad_delay, trans_delay;
> +
> +			/* Update sta padding and transition delay */
> +			pad_delay =
> +				ieee80211_get_emlsr_pad_delay_update(ptr[3]);
> +			trans_delay =
> +				ieee80211_get_emlsr_pad_delay_update(ptr[3]);

It seems to me you're missing a bunch of input validation?

> +
> +			eml_cap &= ~(IEEE80211_EML_CAP_EMLSR_PADDING_DELAY |
> +				     IEEE80211_EML_CAP_EMLSR_TRANSITION_DELAY);
> +			eml_cap |= FIELD_PREP(IEEE80211_EML_EMLSR_PAD_DELAY,
> +					      pad_delay) |
> +				   FIELD_PREP(IEEE80211_EML_EMLSR_TRANS_DELAY,
> +					      trans_delay);
> +			sta->sta.eml_cap = eml_cap;

Same comment about typed bitfield accessors, and u8_replace_bits() would
even shorten that quite a bit.

> +	if (skb->len < hdr_len + act_len)
> +		return;

bit late that :)

johannes

^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH wireless-next v2 2/2] wifi: mt76: mt7996: Add eMLSR support
  2026-01-25 10:51 ` [PATCH wireless-next v2 2/2] wifi: mt76: mt7996: Add eMLSR support Lorenzo Bianconi
@ 2026-01-26 11:03   ` Christian Marangi
  0 siblings, 0 replies; 9+ messages in thread
From: Christian Marangi @ 2026-01-26 11:03 UTC (permalink / raw)
  To: Lorenzo Bianconi
  Cc: Johannes Berg, Ryder Lee, Sean Wang, Matthias Brugger,
	AngeloGioacchino Del Regno, linux-wireless, Felix Fietkau,
	Shayne Chen, linux-mediatek, linux-arm-kernel, MeiChia Chiu

On Sun, Jan 25, 2026 at 11:51:31AM +0100, Lorenzo Bianconi wrote:
> From: MeiChia Chiu <MeiChia.Chiu@mediatek.com>
> 
> Implement set_eml_op_mode mac80211 callback in order to introduce eMLSR
> support.
> 
> Signed-off-by: MeiChia Chiu <MeiChia.Chiu@mediatek.com>
> Co-developed-by: Lorenzo Bianconi <lorenzo@kernel.org>
> Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>

Tested-by: Christian Marangi <ansuelsmth@gmail.com>

--

	Ansuel

^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH wireless-next v2 1/2] wifi: mac80211: Add eMLSR/eMLMR action frame parsing support
  2026-01-26 10:59   ` Johannes Berg
@ 2026-01-26 22:41     ` Lorenzo Bianconi
  2026-01-27  8:02       ` Johannes Berg
  0 siblings, 1 reply; 9+ messages in thread
From: Lorenzo Bianconi @ 2026-01-26 22:41 UTC (permalink / raw)
  To: Johannes Berg
  Cc: Ryder Lee, Sean Wang, Matthias Brugger,
	AngeloGioacchino Del Regno, linux-wireless, Felix Fietkau,
	Shayne Chen, Christian Marangi, linux-mediatek, linux-arm-kernel

[-- Attachment #1: Type: text/plain, Size: 7895 bytes --]

> On Sun, 2026-01-25 at 11:51 +0100, Lorenzo Bianconi wrote:
> > 
> > +static inline u8 ieee80211_get_emlsr_pad_delay_update(u8 param)
> > +{
> > +	u8 pad_delay = FIELD_GET(IEEE80211_EML_EMLSR_PAD_DELAY, param);
> 
> I generally prefer the typed versions and mac80211 (mostly?) uses those,
> i.e. u8_get_bits() and friends, since they also cover endian conversions
> where needed. Is there any reason you use FIELD_* versions here?

ack, I will fix it in v3.

> 
> > +	if (pad_delay > IEEE80211_EML_CAP_EMLSR_PADDING_DELAY_256US)
> > +		pad_delay = 0;
> 
> Seems that should use a constant, rather than =0?
> 
> Also, is that really the right thing to do (also below) to just silently
> cap it? Maybe that should be up to the caller? In some(/most/all)? cases
> we should probably even just _reject_ frames that carry an invalid
> value, which you can't do with this helper?

I guess we can just move this code in the caller and reject the frame if the
values are not valid. I will fix it in v3.

> 
> > +static inline u32 ieee80211_get_emlsr_trans_delay_update(u8 param)
> > +{
> > +	u16 trans_delay = FIELD_GET(IEEE80211_EML_EMLSR_TRANS_DELAY, param);
> > +
> > +	if (trans_delay > IEEE80211_EML_CAP_EMLSR_TRANSITION_DELAY_256US)
> > +		trans_delay = 0;
> > +
> > +	return trans_delay;
> 
> why does that use _three_ different types? Wouldn't u8 be sufficient?
> 
> > --- a/include/net/mac80211.h
> > +++ b/include/net/mac80211.h
> > @@ -1902,6 +1902,21 @@ enum ieee80211_offload_flags {
> >  	IEEE80211_OFFLOAD_DECAP_ENABLED		= BIT(2),
> >  };
> >  
> > +struct ieee80211_eml_params {
> > +	u8 control;
> > +	u16 link_bitmap;
> > +	union {
> > +		struct {
> > +			u16 emlsr_pad_delay;
> > +			u16 emlsr_trans_delay;
> > +		};
> > +		struct {
> > +			u8 mcs_map_count;
> > +			u8 mcs_map_bw[9];
> > +		};
> > +	};
> > +};
> 
> Maybe add kernel-doc? Also not sure the union really is worth it? It's a
> tiny thing. Especially since you don't even label it - maybe if the
> parts were labled emlsr and emlmr, and then you had emlsr.pad_delay?
> 
> (I'd label them anyway, of course, even if not a union.)

ack, I will add kernel-doc in v3.

> 
> Also now the emlsr pad/trans delay are duplicated in the station info,
> is that worth doing? You have to and do track them there too, anyway, as
> we discussed on IRC, could just have the driver use them from there?

I agree, I guess we can drop emlsr_pad_delay and emlsr_trans_delay in
ieee80211_eml_params struct and just rely on sta values. I will fix it in v3.

> 
> Per spec I'm also not sure what the MCS map should be when it's not
> included in the frame?

IIUC the mcs map value are supposed to be in Operation mode notification frame
just for eMLMR. I think the driver should check if the bit is set in
ieee80211_eml_params control field to verify if mcs_map_bw values are valid.

> 
> > + * @set_eml_op_mode: Configure eMLSR/eMLMR operation mode in the underlay
> > + *	driver according to the parameter received in the EML Operating mode
> > + *	notification frame.
> 
> Maybe describe the link_id here, or move it to the params and describe
> it in kernel-doc there?

ack, I will fix it in v3.

> 
> > +static inline int drv_set_eml_op_mode(struct ieee80211_sub_if_data *sdata,
> > +				      struct ieee80211_sta *sta,
> > +				      unsigned int link_id,
> > +				      struct ieee80211_eml_params *eml_params)
> > +{
> > +	struct ieee80211_local *local = sdata->local;
> > +	int ret = 0;
> 
> Shouldn't that be -EOPNOTSUPP?

ack, I will fix it in v3.

> 
> > +static void
> > +ieee80211_send_eml_op_mode_notif(struct ieee80211_sub_if_data *sdata,
> > +				 struct ieee80211_mgmt *req, u8 act_len)
> > +{
> > +	int hdr_len = offsetof(struct ieee80211_mgmt, u.action.u.eml_omn);
> > +	struct ieee80211_local *local = sdata->local;
> > +	struct ieee80211_mgmt *mgmt;
> > +	struct sk_buff *skb;
> > +
> > +	skb = dev_alloc_skb(local->tx_headroom + hdr_len + act_len);
> > +	if (!skb)
> > +		return;
> > +
> > +	skb_reserve(skb, local->tx_headroom);
> > +	mgmt = skb_put_zero(skb, hdr_len);
> > +	mgmt->frame_control = cpu_to_le16(IEEE80211_FTYPE_MGMT |
> > +					  IEEE80211_STYPE_ACTION);
> > +	memcpy(mgmt->da, req->sa, ETH_ALEN);
> > +	memcpy(mgmt->sa, sdata->vif.addr, ETH_ALEN);
> > +	memcpy(mgmt->bssid, sdata->vif.addr, ETH_ALEN);
> > +
> > +	mgmt->u.action.category = WLAN_CATEGORY_PROTECTED_EHT;
> > +	memcpy(&mgmt->u.action.u.eml_omn, &req->u.action.u.eml_omn, act_len);
> > +	mgmt->u.action.u.eml_omn.control &=
> > +		~(IEEE80211_EML_CTRL_EMLSR_PARAM_UPDATE |
> > +		  IEEE80211_EML_CTRL_INDEV_COEX_ACT);
> > +	ieee80211_tx_skb(sdata, skb);
> 
> It seems to me that it'd be better to not copy the request, but rather
> build the response. It's not _that_ much data, and from the spec it
> seems to me that e.g. the MCS map should be included in the response,
> but you do that now, I think?

ack for avoiding memcpy().
Reading the standard, it is not clear to me if mcs map values are supposed to be
added in the Notification frame sent by the AP. What do you think?

> 
> > +void ieee80211_rx_eml_op_mode_notif(struct ieee80211_sub_if_data *sdata,
> > +				    struct sk_buff *skb)
> > +{
> > +	int hdr_len = offsetof(struct ieee80211_mgmt, u.action.u.eml_omn);
> > +	enum nl80211_iftype type = ieee80211_vif_type_p2p(&sdata->vif);
> > +	struct ieee80211_rx_status *status = IEEE80211_SKB_RXCB(skb);
> > +	const struct wiphy_iftype_ext_capab *ift_ext_capa;
> > +	struct ieee80211_mgmt *mgmt = (void *)skb->data;
> > +	struct ieee80211_local *local = sdata->local;
> > +	u8 control = mgmt->u.action.u.eml_omn.control;
> > +	u8 *ptr = mgmt->u.action.u.eml_omn.variable;
> > +	struct ieee80211_eml_params eml_params = {};
> > +	struct sta_info *sta;
> > +	u8 act_len = 3; /* action_code + dialog_token + control */
> > +
> > +	if (!ieee80211_vif_is_mld(&sdata->vif))
> > +		return;
> > +
> > +	/* eMLSR and eMLMR can't be enabled at the same time */
> > +	if ((control & IEEE80211_EML_CTRL_EMLSR_MODE) &&
> > +	    (control & IEEE80211_EML_CTRL_EMLMR_MODE))
> > +		return;
> > +
> > +	if ((control & IEEE80211_EML_CTRL_EMLMR_MODE) &&
> > +	    (control & IEEE80211_EML_CTRL_EMLSR_PARAM_UPDATE))
> > +		return;
> > +
> > +	ift_ext_capa = cfg80211_get_iftype_ext_capa(local->hw.wiphy, type);
> > +	if (!ift_ext_capa)
> > +		return;
> > +
> > +	if (!status->link_valid)
> > +		return;
> > +
> > +	sta = sta_info_get_bss(sdata, mgmt->sa);
> > +	if (!sta)
> > +		return;
> > +
> > +	if (control & IEEE80211_EML_CTRL_EMLSR_MODE) {
> > +		if (!(ift_ext_capa->eml_capabilities &
> > +		      IEEE80211_EML_CAP_EMLSR_SUPP))
> > +			return;
> > +
> > +		if (control & IEEE80211_EML_CTRL_EMLSR_PARAM_UPDATE) {
> > +			u16 eml_cap = sta->sta.eml_cap;
> > +			u8 pad_delay, trans_delay;
> > +
> > +			/* Update sta padding and transition delay */
> > +			pad_delay =
> > +				ieee80211_get_emlsr_pad_delay_update(ptr[3]);
> > +			trans_delay =
> > +				ieee80211_get_emlsr_pad_delay_update(ptr[3]);
> 
> It seems to me you're missing a bunch of input validation?

ack, I will fix it in v3.

> 
> > +
> > +			eml_cap &= ~(IEEE80211_EML_CAP_EMLSR_PADDING_DELAY |
> > +				     IEEE80211_EML_CAP_EMLSR_TRANSITION_DELAY);
> > +			eml_cap |= FIELD_PREP(IEEE80211_EML_EMLSR_PAD_DELAY,
> > +					      pad_delay) |
> > +				   FIELD_PREP(IEEE80211_EML_EMLSR_TRANS_DELAY,
> > +					      trans_delay);
> > +			sta->sta.eml_cap = eml_cap;
> 
> Same comment about typed bitfield accessors, and u8_replace_bits() would
> even shorten that quite a bit.

ack, I will fix it in v3.

> 
> > +	if (skb->len < hdr_len + act_len)
> > +		return;
> 
> bit late that :)

ack, I will fix it in v3.

Regards,
Lorenzo

> 
> johannes

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]

^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH wireless-next v2 1/2] wifi: mac80211: Add eMLSR/eMLMR action frame parsing support
  2026-01-26 22:41     ` Lorenzo Bianconi
@ 2026-01-27  8:02       ` Johannes Berg
  2026-01-27  9:37         ` Lorenzo Bianconi
  0 siblings, 1 reply; 9+ messages in thread
From: Johannes Berg @ 2026-01-27  8:02 UTC (permalink / raw)
  To: Lorenzo Bianconi
  Cc: Ryder Lee, Sean Wang, Matthias Brugger,
	AngeloGioacchino Del Regno, linux-wireless, Felix Fietkau,
	Shayne Chen, Christian Marangi, linux-mediatek, linux-arm-kernel

On Mon, 2026-01-26 at 23:41 +0100, Lorenzo Bianconi wrote:
> 
> > Per spec I'm also not sure what the MCS map should be when it's not
> > included in the frame?
> 
> IIUC the mcs map value are supposed to be in Operation mode notification frame
> just for eMLMR. I think the driver should check if the bit is set in
> ieee80211_eml_params control field to verify if mcs_map_bw values are valid.

Yeah you're right, the MCS Map is always present if EMLMR Mode is set to
1. I thought it was also optional and then what values should you use?

Can't mac80211 validate the values?

> Reading the standard, it is not clear to me if mcs map values are supposed to be
> added in the Notification frame sent by the AP. What do you think?

Hmm. I thought no, but then the language says it's present when the
EMLMR Mode is set to 1, so ... it would have to be? Strange, because
it's not really defined (well) in this direction.

OK, I guess the memcpy was right after all:

   An AP affiliated with the AP MLD that receives an EML Operating Mode
   Notification frame from a non-AP STA affiliated with the non-AP MLD
   should send an EML Operating Mode Notification frame to confirm the
   mode switch at the AP MLD to the non-AP STA with EML Control field
   set to the same value as EML Control field in the received EML
   Operating Mode Notification frame from the non-AP STA before the
   transition timeout expires.

But I think better restrict memcpy() then to just the EML Control field
and build the action header etc. directly.

johannes

^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH wireless-next v2 1/2] wifi: mac80211: Add eMLSR/eMLMR action frame parsing support
  2026-01-27  8:02       ` Johannes Berg
@ 2026-01-27  9:37         ` Lorenzo Bianconi
  2026-01-27  9:39           ` Johannes Berg
  0 siblings, 1 reply; 9+ messages in thread
From: Lorenzo Bianconi @ 2026-01-27  9:37 UTC (permalink / raw)
  To: Johannes Berg
  Cc: Ryder Lee, Sean Wang, Matthias Brugger,
	AngeloGioacchino Del Regno, linux-wireless, Felix Fietkau,
	Shayne Chen, Christian Marangi, linux-mediatek, linux-arm-kernel

[-- Attachment #1: Type: text/plain, Size: 1939 bytes --]

> On Mon, 2026-01-26 at 23:41 +0100, Lorenzo Bianconi wrote:
> > 
> > > Per spec I'm also not sure what the MCS map should be when it's not
> > > included in the frame?
> > 
> > IIUC the mcs map value are supposed to be in Operation mode notification frame
> > just for eMLMR. I think the driver should check if the bit is set in
> > ieee80211_eml_params control field to verify if mcs_map_bw values are valid.
> 
> Yeah you're right, the MCS Map is always present if EMLMR Mode is set to
> 1. I thought it was also optional and then what values should you use?
> 
> Can't mac80211 validate the values?

I guess we can validate eMLMR Supported MCS and NSS Set subfiled according to
the table 9-417t available in P802.11be standard.

> 
> > Reading the standard, it is not clear to me if mcs map values are supposed to be
> > added in the Notification frame sent by the AP. What do you think?
> 
> Hmm. I thought no, but then the language says it's present when the
> EMLMR Mode is set to 1, so ... it would have to be? Strange, because
> it's not really defined (well) in this direction.
> 
> OK, I guess the memcpy was right after all:
> 
>    An AP affiliated with the AP MLD that receives an EML Operating Mode
>    Notification frame from a non-AP STA affiliated with the non-AP MLD
>    should send an EML Operating Mode Notification frame to confirm the
>    mode switch at the AP MLD to the non-AP STA with EML Control field
>    set to the same value as EML Control field in the received EML
>    Operating Mode Notification frame from the non-AP STA before the
>    transition timeout expires.
> 
> But I think better restrict memcpy() then to just the EML Control field
> and build the action header etc. directly.

I guess we can just memcpy() control field + link_bitmap (if present) +
eMLMR Supported MCS and NSS Set subfiled (if present). Agree?

Regards,
Lorenzo

> 
> johannes

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]

^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH wireless-next v2 1/2] wifi: mac80211: Add eMLSR/eMLMR action frame parsing support
  2026-01-27  9:37         ` Lorenzo Bianconi
@ 2026-01-27  9:39           ` Johannes Berg
  0 siblings, 0 replies; 9+ messages in thread
From: Johannes Berg @ 2026-01-27  9:39 UTC (permalink / raw)
  To: Lorenzo Bianconi
  Cc: Ryder Lee, Sean Wang, Matthias Brugger,
	AngeloGioacchino Del Regno, linux-wireless, Felix Fietkau,
	Shayne Chen, Christian Marangi, linux-mediatek, linux-arm-kernel

On Tue, 2026-01-27 at 10:37 +0100, Lorenzo Bianconi wrote:
> 
> > OK, I guess the memcpy was right after all:
> > 
> >    An AP affiliated with the AP MLD that receives an EML Operating Mode
> >    Notification frame from a non-AP STA affiliated with the non-AP MLD
> >    should send an EML Operating Mode Notification frame to confirm the
> >    mode switch at the AP MLD to the non-AP STA with EML Control field
> >    set to the same value as EML Control field in the received EML
> >    Operating Mode Notification frame from the non-AP STA before the
> >    transition timeout expires.
> > 
> > But I think better restrict memcpy() then to just the EML Control field
> > and build the action header etc. directly.
> 
> I guess we can just memcpy() control field + link_bitmap (if present) +
> eMLMR Supported MCS and NSS Set subfiled (if present). Agree?

Well, the link bitmap and MCS set subfields are all _part_ of the EML
Control field, so I'd not phrase it that way, but yes.

johannes

^ permalink raw reply	[flat|nested] 9+ messages in thread

end of thread, other threads:[~2026-01-27  9:39 UTC | newest]

Thread overview: 9+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-01-25 10:51 [PATCH wireless-next v2 0/2] wifi: mac80211: Introduce eMLSR/eMLMR parsing support in AP mode Lorenzo Bianconi
2026-01-25 10:51 ` [PATCH wireless-next v2 1/2] wifi: mac80211: Add eMLSR/eMLMR action frame parsing support Lorenzo Bianconi
2026-01-26 10:59   ` Johannes Berg
2026-01-26 22:41     ` Lorenzo Bianconi
2026-01-27  8:02       ` Johannes Berg
2026-01-27  9:37         ` Lorenzo Bianconi
2026-01-27  9:39           ` Johannes Berg
2026-01-25 10:51 ` [PATCH wireless-next v2 2/2] wifi: mt76: mt7996: Add eMLSR support Lorenzo Bianconi
2026-01-26 11:03   ` Christian Marangi

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox