From 55c01365debb988d78ca6efadba28c29ee2b5c5d Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Sat, 21 Feb 2026 10:14:13 +0000 Subject: [PATCH] mac80211: backport eMLSR/eMLMR parsing support Needed for an upcoming mt76 update Signed-off-by: Felix Fietkau --- ...d-eMLSR-eMLMR-action-frame-parsing-s.patch | 412 ++++++++++++++++++ 1 file changed, 412 insertions(+) create mode 100644 package/kernel/mac80211/patches/subsys/371-wifi-mac80211-Add-eMLSR-eMLMR-action-frame-parsing-s.patch diff --git a/package/kernel/mac80211/patches/subsys/371-wifi-mac80211-Add-eMLSR-eMLMR-action-frame-parsing-s.patch b/package/kernel/mac80211/patches/subsys/371-wifi-mac80211-Add-eMLSR-eMLMR-action-frame-parsing-s.patch new file mode 100644 index 0000000000..c20ed61a62 --- /dev/null +++ b/package/kernel/mac80211/patches/subsys/371-wifi-mac80211-Add-eMLSR-eMLMR-action-frame-parsing-s.patch @@ -0,0 +1,412 @@ +From: Lorenzo Bianconi +Date: Thu, 29 Jan 2026 14:15:46 +0100 +Subject: [PATCH] wifi: mac80211: Add eMLSR/eMLMR action frame parsing support + +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 +Signed-off-by: Lorenzo Bianconi +Link: https://patch.msgid.link/20260129-mac80211-emlsr-v4-1-14bdadf57380@kernel.org +Signed-off-by: Johannes Berg +--- + +--- a/include/linux/ieee80211.h ++++ b/include/linux/ieee80211.h +@@ -1612,6 +1612,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 */ +@@ -5462,6 +5468,17 @@ struct ieee80211_mle_tdls_common_info { + + /* no fixed fields in PRIO_ACCESS */ + ++#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 ++ ++#define IEEE80211_EML_EMLMR_RX_MCS_MAP 0xf0 ++#define IEEE80211_EML_EMLMR_TX_MCS_MAP 0x0f ++ + /** + * ieee80211_mle_common_size - check multi-link element common size + * @data: multi-link element, must already be checked for size using +--- a/include/net/mac80211.h ++++ b/include/net/mac80211.h +@@ -1901,6 +1901,31 @@ enum ieee80211_offload_flags { + }; + + /** ++ * struct ieee80211_eml_params - EHT Operating mode notification parameters ++ * ++ * EML Operating mode notification parameters received in the Operating mode ++ * notification frame. This struct is used as a container to pass the info to ++ * the underlay driver. ++ * ++ * @link_id: the link ID where the Operating mode notification frame has been ++ * received. ++ * @control: EML control field defined in P802.11be section 9.4.1.76. ++ * @link_bitmap: eMLSR/eMLMR enabled links defined in P802.11be ++ * section 9.4.1.76. ++ * @emlmr_mcs_map_count: eMLMR number of valid mcs_map_bw fields according to ++ * P802.11be section 9.4.1.76 (valid if eMLMR mode control bit is set). ++ * @emlmr_mcs_map_bw: eMLMR supported MCS and NSS set subfileds defined in ++ * P802.11be section 9.4.1.76 (valid if eMLMR mode control bit is set). ++ */ ++struct ieee80211_eml_params { ++ u8 link_id; ++ u8 control; ++ u16 link_bitmap; ++ u8 emlmr_mcs_map_count; ++ u8 emlmr_mcs_map_bw[9]; ++}; ++ ++/** + * struct ieee80211_vif_cfg - interface configuration + * @assoc: association status + * @ibss_joined: indicates whether this station is part of an IBSS or not +@@ -4509,6 +4534,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, +@@ -4904,6 +4932,10 @@ 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, ++ struct ieee80211_eml_params *eml_params); + }; + + /** +--- a/net/mac80211/driver-ops.h ++++ b/net/mac80211/driver-ops.h +@@ -1772,4 +1772,25 @@ drv_prep_add_interface(struct ieee80211_ + trace_drv_return_void(local); + } + ++static inline int drv_set_eml_op_mode(struct ieee80211_sub_if_data *sdata, ++ struct ieee80211_sta *sta, ++ struct ieee80211_eml_params *eml_params) ++{ ++ struct ieee80211_local *local = sdata->local; ++ int ret = -EOPNOTSUPP; ++ ++ might_sleep(); ++ lockdep_assert_wiphy(local->hw.wiphy); ++ ++ trace_drv_set_eml_op_mode(local, sdata, sta, eml_params->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, eml_params); ++ trace_drv_return_int(local, ret); ++ ++ return ret; ++} ++ + #endif /* __MAC80211_DRIVER_OPS */ +--- 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,177 @@ ieee80211_eht_cap_ie_to_sta_eht_cap(stru + + 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, int opt_len) ++{ ++ int len = offsetofend(struct ieee80211_mgmt, u.action.u.eml_omn); ++ struct ieee80211_local *local = sdata->local; ++ struct ieee80211_mgmt *mgmt; ++ struct sk_buff *skb; ++ ++ len += opt_len; /* optional len */ ++ skb = dev_alloc_skb(local->tx_headroom + len); ++ if (!skb) ++ return; ++ ++ skb_reserve(skb, local->tx_headroom); ++ mgmt = skb_put_zero(skb, 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; ++ mgmt->u.action.u.eml_omn.action_code = ++ WLAN_PROTECTED_EHT_ACTION_EML_OP_MODE_NOTIF; ++ mgmt->u.action.u.eml_omn.dialog_token = ++ req->u.action.u.eml_omn.dialog_token; ++ mgmt->u.action.u.eml_omn.control = req->u.action.u.eml_omn.control & ++ ~(IEEE80211_EML_CTRL_EMLSR_PARAM_UPDATE | ++ IEEE80211_EML_CTRL_INDEV_COEX_ACT); ++ /* Copy optional fields from the received notification frame */ ++ memcpy(mgmt->u.action.u.eml_omn.variable, ++ req->u.action.u.eml_omn.variable, opt_len); ++ ++ ieee80211_tx_skb(sdata, skb); ++} ++ ++void ieee80211_rx_eml_op_mode_notif(struct ieee80211_sub_if_data *sdata, ++ struct sk_buff *skb) ++{ ++ int len = offsetofend(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 = { ++ .link_id = status->link_id, ++ }; ++ struct sta_info *sta; ++ int opt_len = 0; ++ ++ 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) { ++ u8 emlsr_param_update_len; ++ ++ if (!(ift_ext_capa->eml_capabilities & ++ IEEE80211_EML_CAP_EMLSR_SUPP)) ++ return; ++ ++ opt_len += sizeof(__le16); /* eMLSR link_bitmap */ ++ /* eMLSR param update field is not part of Notfication frame ++ * sent by the AP to client so account it separately. ++ */ ++ emlsr_param_update_len = ++ !!(control & IEEE80211_EML_CTRL_EMLSR_PARAM_UPDATE); ++ ++ if (skb->len < len + opt_len + emlsr_param_update_len) ++ return; ++ ++ if (control & IEEE80211_EML_CTRL_EMLSR_PARAM_UPDATE) { ++ u8 pad_delay, trans_delay; ++ ++ pad_delay = u8_get_bits(ptr[2], ++ IEEE80211_EML_EMLSR_PAD_DELAY); ++ if (pad_delay > ++ IEEE80211_EML_CAP_EMLSR_PADDING_DELAY_256US) ++ return; ++ ++ trans_delay = u8_get_bits(ptr[2], ++ IEEE80211_EML_EMLSR_TRANS_DELAY); ++ if (trans_delay > ++ IEEE80211_EML_CAP_EMLSR_TRANSITION_DELAY_256US) ++ return; ++ ++ /* Update sta padding and transition delay */ ++ sta->sta.eml_cap = ++ u8_replace_bits(sta->sta.eml_cap, ++ pad_delay, ++ IEEE80211_EML_CAP_EMLSR_PADDING_DELAY); ++ sta->sta.eml_cap = ++ u8_replace_bits(sta->sta.eml_cap, ++ trans_delay, ++ IEEE80211_EML_CAP_EMLSR_TRANSITION_DELAY); ++ } ++ } ++ ++ if (control & IEEE80211_EML_CTRL_EMLMR_MODE) { ++ u8 mcs_map_size; ++ int i; ++ ++ if (!(ift_ext_capa->eml_capabilities & ++ IEEE80211_EML_CAP_EMLMR_SUPPORT)) ++ return; ++ ++ opt_len += sizeof(__le16); /* eMLMR link_bitmap */ ++ opt_len++; /* eMLMR mcs_map_count */ ++ if (skb->len < len + opt_len) ++ return; ++ ++ eml_params.emlmr_mcs_map_count = ptr[2]; ++ if (eml_params.emlmr_mcs_map_count > 2) ++ return; ++ ++ mcs_map_size = 3 * (1 + eml_params.emlmr_mcs_map_count); ++ opt_len += mcs_map_size; ++ if (skb->len < len + opt_len) ++ return; ++ ++ for (i = 0; i < mcs_map_size; i++) { ++ u8 rx_mcs, tx_mcs; ++ ++ rx_mcs = u8_get_bits(ptr[3 + i], ++ IEEE80211_EML_EMLMR_RX_MCS_MAP); ++ if (rx_mcs > 8) ++ return; ++ ++ tx_mcs = u8_get_bits(ptr[3 + i], ++ IEEE80211_EML_EMLMR_TX_MCS_MAP); ++ if (tx_mcs > 8) ++ return; ++ } ++ ++ memcpy(eml_params.emlmr_mcs_map_bw, &ptr[3], 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 & sdata->vif.active_links) != ++ eml_params.link_bitmap) ++ return; ++ } ++ ++ if (drv_set_eml_op_mode(sdata, &sta->sta, &eml_params)) ++ return; ++ ++ ieee80211_send_eml_op_mode_notif(sdata, mgmt, opt_len); ++} +--- a/net/mac80211/ieee80211_i.h ++++ b/net/mac80211/ieee80211_i.h +@@ -2859,6 +2859,8 @@ void ieee80211_destroy_frag_cache(struct + + 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, +--- a/net/mac80211/iface.c ++++ b/net/mac80211/iface.c +@@ -1630,7 +1630,15 @@ static void ieee80211_iface_process_skb( + } + } 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, +--- a/net/mac80211/rx.c ++++ b/net/mac80211/rx.c +@@ -3834,6 +3834,14 @@ ieee80211_rx_h_action(struct ieee80211_r + 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; + } +--- a/net/mac80211/trace.h ++++ b/net/mac80211/trace.h +@@ -3359,6 +3359,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 +--- /dev/null ++++ b/include/linux/ieee80211-eht.h +@@ -0,0 +1 @@ ++#include