Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

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 <ansuelsmth@gmail.com>
Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
Link: https://patch.msgid.link/20260129-mac80211-emlsr-v4-1-14bdadf57380@kernel.org
Signed-off-by: Johannes Berg <johannes.berg@intel.com>

authored by

Lorenzo Bianconi and committed by
Johannes Berg
0d95280a a1085114

+296 -1
+11
include/linux/ieee80211-eht.h
··· 558 558 559 559 #define IEEE80211_MLC_PRIO_ACCESS_PRES_AP_MLD_MAC_ADDR 0x0010 560 560 561 + #define IEEE80211_EML_CTRL_EMLSR_MODE BIT(0) 562 + #define IEEE80211_EML_CTRL_EMLMR_MODE BIT(1) 563 + #define IEEE80211_EML_CTRL_EMLSR_PARAM_UPDATE BIT(2) 564 + #define IEEE80211_EML_CTRL_INDEV_COEX_ACT BIT(3) 565 + 566 + #define IEEE80211_EML_EMLSR_PAD_DELAY 0x07 567 + #define IEEE80211_EML_EMLSR_TRANS_DELAY 0x38 568 + 569 + #define IEEE80211_EML_EMLMR_RX_MCS_MAP 0xf0 570 + #define IEEE80211_EML_EMLMR_TX_MCS_MAP 0x0f 571 + 561 572 /* no fixed fields in PRIO_ACCESS */ 562 573 563 574 /**
+6
include/linux/ieee80211.h
··· 1186 1186 u8 action_code; 1187 1187 u8 variable[]; 1188 1188 } __packed epcs; 1189 + struct { 1190 + u8 action_code; 1191 + u8 dialog_token; 1192 + u8 control; 1193 + u8 variable[]; 1194 + } __packed eml_omn; 1189 1195 } u; 1190 1196 } __packed action; 1191 1197 DECLARE_FLEX_ARRAY(u8, body); /* Generic frame body */
+32
include/net/mac80211.h
··· 1918 1918 }; 1919 1919 1920 1920 /** 1921 + * struct ieee80211_eml_params - EHT Operating mode notification parameters 1922 + * 1923 + * EML Operating mode notification parameters received in the Operating mode 1924 + * notification frame. This struct is used as a container to pass the info to 1925 + * the underlay driver. 1926 + * 1927 + * @link_id: the link ID where the Operating mode notification frame has been 1928 + * received. 1929 + * @control: EML control field defined in P802.11be section 9.4.1.76. 1930 + * @link_bitmap: eMLSR/eMLMR enabled links defined in P802.11be 1931 + * section 9.4.1.76. 1932 + * @emlmr_mcs_map_count: eMLMR number of valid mcs_map_bw fields according to 1933 + * P802.11be section 9.4.1.76 (valid if eMLMR mode control bit is set). 1934 + * @emlmr_mcs_map_bw: eMLMR supported MCS and NSS set subfileds defined in 1935 + * P802.11be section 9.4.1.76 (valid if eMLMR mode control bit is set). 1936 + */ 1937 + struct ieee80211_eml_params { 1938 + u8 link_id; 1939 + u8 control; 1940 + u16 link_bitmap; 1941 + u8 emlmr_mcs_map_count; 1942 + u8 emlmr_mcs_map_bw[9]; 1943 + }; 1944 + 1945 + /** 1921 1946 * struct ieee80211_vif_cfg - interface configuration 1922 1947 * @assoc: association status 1923 1948 * @ibss_joined: indicates whether this station is part of an IBSS or not ··· 4555 4530 * interface with the specified type would be added, and thus drivers that 4556 4531 * implement this callback need to handle such cases. The type is the full 4557 4532 * &enum nl80211_iftype. 4533 + * @set_eml_op_mode: Configure eMLSR/eMLMR operation mode in the underlay 4534 + * driver according to the parameter received in the EML Operating mode 4535 + * notification frame. 4558 4536 */ 4559 4537 struct ieee80211_ops { 4560 4538 void (*tx)(struct ieee80211_hw *hw, ··· 4953 4925 struct ieee80211_neg_ttlm *ttlm); 4954 4926 void (*prep_add_interface)(struct ieee80211_hw *hw, 4955 4927 enum nl80211_iftype type); 4928 + int (*set_eml_op_mode)(struct ieee80211_hw *hw, 4929 + struct ieee80211_vif *vif, 4930 + struct ieee80211_sta *sta, 4931 + struct ieee80211_eml_params *eml_params); 4956 4932 }; 4957 4933 4958 4934 /**
+21
net/mac80211/driver-ops.h
··· 1772 1772 trace_drv_return_void(local); 1773 1773 } 1774 1774 1775 + static inline int drv_set_eml_op_mode(struct ieee80211_sub_if_data *sdata, 1776 + struct ieee80211_sta *sta, 1777 + struct ieee80211_eml_params *eml_params) 1778 + { 1779 + struct ieee80211_local *local = sdata->local; 1780 + int ret = -EOPNOTSUPP; 1781 + 1782 + might_sleep(); 1783 + lockdep_assert_wiphy(local->hw.wiphy); 1784 + 1785 + trace_drv_set_eml_op_mode(local, sdata, sta, eml_params->link_id, 1786 + eml_params->control, 1787 + eml_params->link_bitmap); 1788 + if (local->ops->set_eml_op_mode) 1789 + ret = local->ops->set_eml_op_mode(&local->hw, &sdata->vif, 1790 + sta, eml_params); 1791 + trace_drv_return_int(local, ret); 1792 + 1793 + return ret; 1794 + } 1795 + 1775 1796 #endif /* __MAC80211_DRIVER_OPS */
+175
net/mac80211/eht.c
··· 5 5 * Copyright(c) 2021-2025 Intel Corporation 6 6 */ 7 7 8 + #include "driver-ops.h" 8 9 #include "ieee80211_i.h" 9 10 10 11 void ··· 102 101 } 103 102 104 103 ieee80211_sta_recalc_aggregates(&link_sta->sta->sta); 104 + } 105 + 106 + static void 107 + ieee80211_send_eml_op_mode_notif(struct ieee80211_sub_if_data *sdata, 108 + struct ieee80211_mgmt *req, int opt_len) 109 + { 110 + int len = offsetofend(struct ieee80211_mgmt, u.action.u.eml_omn); 111 + struct ieee80211_local *local = sdata->local; 112 + struct ieee80211_mgmt *mgmt; 113 + struct sk_buff *skb; 114 + 115 + len += opt_len; /* optional len */ 116 + skb = dev_alloc_skb(local->tx_headroom + len); 117 + if (!skb) 118 + return; 119 + 120 + skb_reserve(skb, local->tx_headroom); 121 + mgmt = skb_put_zero(skb, len); 122 + mgmt->frame_control = cpu_to_le16(IEEE80211_FTYPE_MGMT | 123 + IEEE80211_STYPE_ACTION); 124 + memcpy(mgmt->da, req->sa, ETH_ALEN); 125 + memcpy(mgmt->sa, sdata->vif.addr, ETH_ALEN); 126 + memcpy(mgmt->bssid, sdata->vif.addr, ETH_ALEN); 127 + 128 + mgmt->u.action.category = WLAN_CATEGORY_PROTECTED_EHT; 129 + mgmt->u.action.u.eml_omn.action_code = 130 + WLAN_PROTECTED_EHT_ACTION_EML_OP_MODE_NOTIF; 131 + mgmt->u.action.u.eml_omn.dialog_token = 132 + req->u.action.u.eml_omn.dialog_token; 133 + mgmt->u.action.u.eml_omn.control = req->u.action.u.eml_omn.control & 134 + ~(IEEE80211_EML_CTRL_EMLSR_PARAM_UPDATE | 135 + IEEE80211_EML_CTRL_INDEV_COEX_ACT); 136 + /* Copy optional fields from the received notification frame */ 137 + memcpy(mgmt->u.action.u.eml_omn.variable, 138 + req->u.action.u.eml_omn.variable, opt_len); 139 + 140 + ieee80211_tx_skb(sdata, skb); 141 + } 142 + 143 + void ieee80211_rx_eml_op_mode_notif(struct ieee80211_sub_if_data *sdata, 144 + struct sk_buff *skb) 145 + { 146 + int len = offsetofend(struct ieee80211_mgmt, u.action.u.eml_omn); 147 + enum nl80211_iftype type = ieee80211_vif_type_p2p(&sdata->vif); 148 + struct ieee80211_rx_status *status = IEEE80211_SKB_RXCB(skb); 149 + const struct wiphy_iftype_ext_capab *ift_ext_capa; 150 + struct ieee80211_mgmt *mgmt = (void *)skb->data; 151 + struct ieee80211_local *local = sdata->local; 152 + u8 control = mgmt->u.action.u.eml_omn.control; 153 + u8 *ptr = mgmt->u.action.u.eml_omn.variable; 154 + struct ieee80211_eml_params eml_params = { 155 + .link_id = status->link_id, 156 + }; 157 + struct sta_info *sta; 158 + int opt_len = 0; 159 + 160 + if (!ieee80211_vif_is_mld(&sdata->vif)) 161 + return; 162 + 163 + /* eMLSR and eMLMR can't be enabled at the same time */ 164 + if ((control & IEEE80211_EML_CTRL_EMLSR_MODE) && 165 + (control & IEEE80211_EML_CTRL_EMLMR_MODE)) 166 + return; 167 + 168 + if ((control & IEEE80211_EML_CTRL_EMLMR_MODE) && 169 + (control & IEEE80211_EML_CTRL_EMLSR_PARAM_UPDATE)) 170 + return; 171 + 172 + ift_ext_capa = cfg80211_get_iftype_ext_capa(local->hw.wiphy, type); 173 + if (!ift_ext_capa) 174 + return; 175 + 176 + if (!status->link_valid) 177 + return; 178 + 179 + sta = sta_info_get_bss(sdata, mgmt->sa); 180 + if (!sta) 181 + return; 182 + 183 + if (control & IEEE80211_EML_CTRL_EMLSR_MODE) { 184 + u8 emlsr_param_update_len; 185 + 186 + if (!(ift_ext_capa->eml_capabilities & 187 + IEEE80211_EML_CAP_EMLSR_SUPP)) 188 + return; 189 + 190 + opt_len += sizeof(__le16); /* eMLSR link_bitmap */ 191 + /* eMLSR param update field is not part of Notfication frame 192 + * sent by the AP to client so account it separately. 193 + */ 194 + emlsr_param_update_len = 195 + !!(control & IEEE80211_EML_CTRL_EMLSR_PARAM_UPDATE); 196 + 197 + if (skb->len < len + opt_len + emlsr_param_update_len) 198 + return; 199 + 200 + if (control & IEEE80211_EML_CTRL_EMLSR_PARAM_UPDATE) { 201 + u8 pad_delay, trans_delay; 202 + 203 + pad_delay = u8_get_bits(ptr[2], 204 + IEEE80211_EML_EMLSR_PAD_DELAY); 205 + if (pad_delay > 206 + IEEE80211_EML_CAP_EMLSR_PADDING_DELAY_256US) 207 + return; 208 + 209 + trans_delay = u8_get_bits(ptr[2], 210 + IEEE80211_EML_EMLSR_TRANS_DELAY); 211 + if (trans_delay > 212 + IEEE80211_EML_CAP_EMLSR_TRANSITION_DELAY_256US) 213 + return; 214 + 215 + /* Update sta padding and transition delay */ 216 + sta->sta.eml_cap = 217 + u8_replace_bits(sta->sta.eml_cap, 218 + pad_delay, 219 + IEEE80211_EML_CAP_EMLSR_PADDING_DELAY); 220 + sta->sta.eml_cap = 221 + u8_replace_bits(sta->sta.eml_cap, 222 + trans_delay, 223 + IEEE80211_EML_CAP_EMLSR_TRANSITION_DELAY); 224 + } 225 + } 226 + 227 + if (control & IEEE80211_EML_CTRL_EMLMR_MODE) { 228 + u8 mcs_map_size; 229 + int i; 230 + 231 + if (!(ift_ext_capa->eml_capabilities & 232 + IEEE80211_EML_CAP_EMLMR_SUPPORT)) 233 + return; 234 + 235 + opt_len += sizeof(__le16); /* eMLMR link_bitmap */ 236 + opt_len++; /* eMLMR mcs_map_count */ 237 + if (skb->len < len + opt_len) 238 + return; 239 + 240 + eml_params.emlmr_mcs_map_count = ptr[2]; 241 + if (eml_params.emlmr_mcs_map_count > 2) 242 + return; 243 + 244 + mcs_map_size = 3 * (1 + eml_params.emlmr_mcs_map_count); 245 + opt_len += mcs_map_size; 246 + if (skb->len < len + opt_len) 247 + return; 248 + 249 + for (i = 0; i < mcs_map_size; i++) { 250 + u8 rx_mcs, tx_mcs; 251 + 252 + rx_mcs = u8_get_bits(ptr[3 + i], 253 + IEEE80211_EML_EMLMR_RX_MCS_MAP); 254 + if (rx_mcs > 8) 255 + return; 256 + 257 + tx_mcs = u8_get_bits(ptr[3 + i], 258 + IEEE80211_EML_EMLMR_TX_MCS_MAP); 259 + if (tx_mcs > 8) 260 + return; 261 + } 262 + 263 + memcpy(eml_params.emlmr_mcs_map_bw, &ptr[3], mcs_map_size); 264 + } 265 + 266 + if ((control & IEEE80211_EML_CTRL_EMLSR_MODE) || 267 + (control & IEEE80211_EML_CTRL_EMLMR_MODE)) { 268 + eml_params.link_bitmap = get_unaligned_le16(ptr); 269 + if ((eml_params.link_bitmap & sdata->vif.active_links) != 270 + eml_params.link_bitmap) 271 + return; 272 + } 273 + 274 + if (drv_set_eml_op_mode(sdata, &sta->sta, &eml_params)) 275 + return; 276 + 277 + ieee80211_send_eml_op_mode_notif(sdata, mgmt, opt_len); 105 278 }
+2
net/mac80211/ieee80211_i.h
··· 2843 2843 2844 2844 u8 ieee80211_ie_len_eht_cap(struct ieee80211_sub_if_data *sdata); 2845 2845 2846 + void ieee80211_rx_eml_op_mode_notif(struct ieee80211_sub_if_data *sdata, 2847 + struct sk_buff *skb); 2846 2848 void 2847 2849 ieee80211_eht_cap_ie_to_sta_eht_cap(struct ieee80211_sub_if_data *sdata, 2848 2850 struct ieee80211_supported_band *sband,
+9 -1
net/mac80211/iface.c
··· 1668 1668 } 1669 1669 } else if (ieee80211_is_action(mgmt->frame_control) && 1670 1670 mgmt->u.action.category == WLAN_CATEGORY_PROTECTED_EHT) { 1671 - if (sdata->vif.type == NL80211_IFTYPE_STATION) { 1671 + if (sdata->vif.type == NL80211_IFTYPE_AP) { 1672 + switch (mgmt->u.action.u.eml_omn.action_code) { 1673 + case WLAN_PROTECTED_EHT_ACTION_EML_OP_MODE_NOTIF: 1674 + ieee80211_rx_eml_op_mode_notif(sdata, skb); 1675 + break; 1676 + default: 1677 + break; 1678 + } 1679 + } else if (sdata->vif.type == NL80211_IFTYPE_STATION) { 1672 1680 switch (mgmt->u.action.u.ttlm_req.action_code) { 1673 1681 case WLAN_PROTECTED_EHT_ACTION_TTLM_REQ: 1674 1682 ieee80211_process_neg_ttlm_req(sdata, mgmt,
+8
net/mac80211/rx.c
··· 3928 3928 u.action.u.epcs)) 3929 3929 goto invalid; 3930 3930 goto queue; 3931 + case WLAN_PROTECTED_EHT_ACTION_EML_OP_MODE_NOTIF: 3932 + if (sdata->vif.type != NL80211_IFTYPE_AP) 3933 + break; 3934 + 3935 + if (len < offsetofend(typeof(*mgmt), 3936 + u.action.u.eml_omn)) 3937 + goto invalid; 3938 + goto queue; 3931 3939 default: 3932 3940 break; 3933 3941 }
+32
net/mac80211/trace.h
··· 3353 3353 ) 3354 3354 ); 3355 3355 3356 + TRACE_EVENT(drv_set_eml_op_mode, 3357 + TP_PROTO(struct ieee80211_local *local, 3358 + struct ieee80211_sub_if_data *sdata, 3359 + struct ieee80211_sta *sta, 3360 + unsigned int link_id, 3361 + u8 control, u16 link_bitmap), 3362 + 3363 + TP_ARGS(local, sdata, sta, link_id, control, link_bitmap), 3364 + 3365 + TP_STRUCT__entry(LOCAL_ENTRY 3366 + VIF_ENTRY 3367 + STA_ENTRY 3368 + __field(u32, link_id) 3369 + __field(u8, control) 3370 + __field(u16, link_bitmap)), 3371 + 3372 + TP_fast_assign(LOCAL_ASSIGN; 3373 + VIF_ASSIGN; 3374 + STA_NAMED_ASSIGN(sta); 3375 + __entry->link_id = link_id; 3376 + __entry->control = control; 3377 + __entry->link_bitmap = link_bitmap; 3378 + ), 3379 + 3380 + TP_printk( 3381 + LOCAL_PR_FMT VIF_PR_FMT STA_PR_FMT 3382 + " (link:%d control:%02x link_bitmap:%04x)", 3383 + LOCAL_PR_ARG, VIF_PR_ARG, STA_PR_ARG, __entry->link_id, 3384 + __entry->control, __entry->link_bitmap 3385 + ) 3386 + ); 3387 + 3356 3388 #endif /* !__MAC80211_DRIVER_TRACE || TRACE_HEADER_MULTI_READ */ 3357 3389 3358 3390 #undef TRACE_INCLUDE_PATH