IEEE P802.11bn/D2.0, Aug 2026, subclause 11.3.1, defines three new MLD
state sub-states (4a, 4b, 4c) that apply when a non-AP MLD and its
associated SMD-ME are in state 4 and an SMD BSS Transition is
in progress.

Add the corresponding nl80211 STA flags and internal STA flags to track
the SMD BSS Transition sub-states (4a, 4b, or 4c) in mac80211:
 - NL80211_STA_FLAG_SMD_PREP_TARGET (WLAN_STA_SMD_PREP_TARGET):
     State 4a: non-AP MLD is prepared with target AP MLD
 - NL80211_STA_FLAG_SMD_EXEC_CURRENT (WLAN_STA_SMD_EXEC_CURRENT):
     State 4b: ST execution active on current AP MLD
 - NL80211_STA_FLAG_SMD_DL_DRAIN (WLAN_STA_SMD_DL_DRAIN):
     State 4c: DL draining period

These are transient sub-states of WLAN_STA_AUTHORIZED with the SMD-ME.
The station remains authenticated, associated, and RSNA-established
throughout. At most one of the three may be set at a time; mutual
exclusion is enforced in sta_apply_smd_state_flags().

Signed-off-by: Pooventhiran G <[email protected]>
---
 include/uapi/linux/nl80211.h |  9 +++++++
 net/mac80211/cfg.c           | 60 ++++++++++++++++++++++++++++++++++++++++++++
 net/mac80211/debugfs_sta.c   |  3 +++
 net/mac80211/sta_info.h      |  8 ++++++
 net/wireless/nl80211.c       | 18 ++++++++++---
 5 files changed, 94 insertions(+), 4 deletions(-)

diff --git a/include/uapi/linux/nl80211.h b/include/uapi/linux/nl80211.h
index 3f9acbd4830f..cd9a320143d7 100644
--- a/include/uapi/linux/nl80211.h
+++ b/include/uapi/linux/nl80211.h
@@ -3973,6 +3973,12 @@ enum nl80211_iftype {
  *     previously added station into associated state
  * @NL80211_STA_FLAG_SPP_AMSDU: station supports SPP A-MSDUs
  * @NL80211_STA_FLAG_SMD: station has negotiated SMD.
+ * @NL80211_STA_FLAG_SMD_PREP_TARGET: station is prepared at SMD target AP MLD
+ *     (State 4a).
+ * @NL80211_STA_FLAG_SMD_EXEC_CURRENT: current AP MLD is executing SMD BSS
+ *     Transition (State 4b).
+ * @NL80211_STA_FLAG_SMD_DL_DRAIN: current AP MLD is in DL draining period
+ *     (State 4c).
  * @NL80211_STA_FLAG_MAX: highest station flag number currently defined
  * @__NL80211_STA_FLAG_AFTER_LAST: internal use
  */
@@ -3987,6 +3993,9 @@ enum nl80211_sta_flags {
        NL80211_STA_FLAG_ASSOCIATED,
        NL80211_STA_FLAG_SPP_AMSDU,
        NL80211_STA_FLAG_SMD,
+       NL80211_STA_FLAG_SMD_PREP_TARGET,
+       NL80211_STA_FLAG_SMD_EXEC_CURRENT,
+       NL80211_STA_FLAG_SMD_DL_DRAIN,
 
        /* keep last */
        __NL80211_STA_FLAG_AFTER_LAST,
diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c
index 138b67f4bda4..69b3ac0b48f3 100644
--- a/net/mac80211/cfg.c
+++ b/net/mac80211/cfg.c
@@ -2452,6 +2452,55 @@ static int sta_link_apply_parameters(struct 
ieee80211_local *local,
        return 0;
 }
 
+static int sta_apply_smd_state_flags(struct sta_info *sta,
+                                    u32 mask, u32 set)
+{
+       bool smd_prep_target, smd_exec_current, smd_dl_drain;
+       u32 expected_mask = BIT(NL80211_STA_FLAG_SMD_PREP_TARGET) |
+                           BIT(NL80211_STA_FLAG_SMD_EXEC_CURRENT) |
+                           BIT(NL80211_STA_FLAG_SMD_DL_DRAIN);
+       u32 smd_flags;
+
+       if (!(mask & expected_mask))
+               return 0;
+
+       smd_flags = mask & expected_mask & set;
+       if (WARN_ON(hweight32(smd_flags) > 1))
+               return -EINVAL;
+
+       smd_prep_target =
+               !!(smd_flags & BIT(NL80211_STA_FLAG_SMD_PREP_TARGET));
+       smd_exec_current =
+               !!(smd_flags & BIT(NL80211_STA_FLAG_SMD_EXEC_CURRENT));
+       smd_dl_drain =
+               !!(smd_flags & BIT(NL80211_STA_FLAG_SMD_DL_DRAIN));
+
+       if (!test_sta_flag(sta, WLAN_STA_AUTHORIZED) &&
+           (smd_exec_current || smd_dl_drain))
+               return -EINVAL;
+
+       if (smd_prep_target) {
+               clear_sta_flag(sta, WLAN_STA_SMD_EXEC_CURRENT);
+               clear_sta_flag(sta, WLAN_STA_SMD_DL_DRAIN);
+               set_sta_flag(sta, WLAN_STA_SMD_PREP_TARGET);
+       } else if (smd_exec_current) {
+               clear_sta_flag(sta, WLAN_STA_SMD_PREP_TARGET);
+               clear_sta_flag(sta, WLAN_STA_SMD_DL_DRAIN);
+               set_sta_flag(sta, WLAN_STA_SMD_EXEC_CURRENT);
+       } else if (smd_dl_drain) {
+               clear_sta_flag(sta, WLAN_STA_SMD_PREP_TARGET);
+               clear_sta_flag(sta, WLAN_STA_SMD_EXEC_CURRENT);
+               set_sta_flag(sta, WLAN_STA_SMD_DL_DRAIN);
+       } else {
+               /* all flags revert back to none being set */
+               clear_sta_flag(sta, WLAN_STA_SMD_PREP_TARGET);
+               clear_sta_flag(sta, WLAN_STA_SMD_EXEC_CURRENT);
+               clear_sta_flag(sta, WLAN_STA_SMD_DL_DRAIN);
+       }
+
+       return 0;
+}
+
 static int sta_apply_parameters(struct ieee80211_local *local,
                                struct sta_info *sta,
                                struct station_parameters *params)
@@ -2538,6 +2587,17 @@ static int sta_apply_parameters(struct ieee80211_local 
*local,
        if (params->smd_params.smd_sta)
                sta->sta.smd_params = params->smd_params;
 
+       /*
+        * SMD BSS Transition sub-states (IEEE P802.11bn/D2.0, Aug 2026,
+        * subclause 11.3.1).
+        * Only meaningful for SMD-capable stations (WLAN_STA_SMD set).
+        */
+       if (test_sta_flag(sta, WLAN_STA_SMD)) {
+               ret = sta_apply_smd_state_flags(sta, mask, set);
+               if (ret)
+                       return ret;
+       }
+
        /* mark TDLS channel switch support, if the AP allows it */
        if (test_sta_flag(sta, WLAN_STA_TDLS_PEER) &&
            !sdata->deflink.u.mgd.tdls_chan_switch_prohibited &&
diff --git a/net/mac80211/debugfs_sta.c b/net/mac80211/debugfs_sta.c
index d7e7c9c578e4..24c985687bd2 100644
--- a/net/mac80211/debugfs_sta.c
+++ b/net/mac80211/debugfs_sta.c
@@ -79,6 +79,9 @@ static const char * const sta_flag_names[] = {
        FLAG(USES_ENCRYPTION),
        FLAG(DECAP_OFFLOAD),
        FLAG(SMD),
+       FLAG(SMD_PREP_TARGET),
+       FLAG(SMD_EXEC_CURRENT),
+       FLAG(SMD_DL_DRAIN),
 #undef FLAG
 };
 
diff --git a/net/mac80211/sta_info.h b/net/mac80211/sta_info.h
index 506aeb241a6d..89137b18a862 100644
--- a/net/mac80211/sta_info.h
+++ b/net/mac80211/sta_info.h
@@ -73,6 +73,11 @@
  *     so drop all packets without a key later.
  * @WLAN_STA_DECAP_OFFLOAD: This station uses rx decap offload
  * @WLAN_STA_SMD: this station is associated to an SMD-ME.
+ * @WLAN_STA_SMD_PREP_TARGET: this SMD station is prepared at the target AP 
MLD.
+ * @WLAN_STA_SMD_EXEC_CURRENT: this SMD station has started execution at
+ *     the current AP MLD.
+ * @WLAN_STA_SMD_DL_DRAIN: this SMD station has started draining at
+ *     the current AP MLD.
  *
  * @NUM_WLAN_STA_FLAGS: number of defined flags
  */
@@ -106,6 +111,9 @@ enum ieee80211_sta_info_flags {
        WLAN_STA_USES_ENCRYPTION,
        WLAN_STA_DECAP_OFFLOAD,
        WLAN_STA_SMD,
+       WLAN_STA_SMD_PREP_TARGET,
+       WLAN_STA_SMD_EXEC_CURRENT,
+       WLAN_STA_SMD_DL_DRAIN,
 
        NUM_WLAN_STA_FLAGS,
 };
diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c
index eee6ef08950d..26277b3352ae 100644
--- a/net/wireless/nl80211.c
+++ b/net/wireless/nl80211.c
@@ -7708,6 +7708,9 @@ static const struct nla_policy 
sta_flags_policy[NL80211_STA_FLAG_MAX + 1] = {
        [NL80211_STA_FLAG_AUTHENTICATED] = { .type = NLA_FLAG },
        [NL80211_STA_FLAG_TDLS_PEER] = { .type = NLA_FLAG },
        [NL80211_STA_FLAG_SMD] = { .type = NLA_FLAG },
+       [NL80211_STA_FLAG_SMD_PREP_TARGET] = { .type = NLA_FLAG },
+       [NL80211_STA_FLAG_SMD_EXEC_CURRENT] = { .type = NLA_FLAG },
+       [NL80211_STA_FLAG_SMD_DL_DRAIN] = { .type = NLA_FLAG },
 };
 
 static int parse_station_flags(struct genl_info *info,
@@ -7774,7 +7777,11 @@ static int parse_station_flags(struct genl_info *info,
        switch (iftype) {
        case NL80211_IFTYPE_AP:
        case NL80211_IFTYPE_AP_VLAN:
-               params->sta_flags_mask = BIT(NL80211_STA_FLAG_SMD);
+               params->sta_flags_mask =
+                       BIT(NL80211_STA_FLAG_SMD) |
+                       BIT(NL80211_STA_FLAG_SMD_PREP_TARGET) |
+                       BIT(NL80211_STA_FLAG_SMD_EXEC_CURRENT) |
+                       BIT(NL80211_STA_FLAG_SMD_DL_DRAIN);
                fallthrough;
        case NL80211_IFTYPE_P2P_GO:
                params->sta_flags_mask |= BIT(NL80211_STA_FLAG_AUTHORIZED) |
@@ -8921,7 +8928,7 @@ int cfg80211_check_station_change(struct wiphy *wiphy,
                return -EINVAL;
 
        /* When you run into this, adjust the code below for the new flag */
-       BUILD_BUG_ON(NL80211_STA_FLAG_MAX != 9);
+       BUILD_BUG_ON(NL80211_STA_FLAG_MAX != 12);
 
        switch (statype) {
        case CFG80211_STA_MESH_PEER_KERNEL:
@@ -9016,7 +9023,10 @@ int cfg80211_check_station_change(struct wiphy *wiphy,
                                  BIT(NL80211_STA_FLAG_WME) |
                                  BIT(NL80211_STA_FLAG_MFP) |
                                  BIT(NL80211_STA_FLAG_SPP_AMSDU) |
-                                 BIT(NL80211_STA_FLAG_SMD)))
+                                 BIT(NL80211_STA_FLAG_SMD) |
+                                 BIT(NL80211_STA_FLAG_SMD_PREP_TARGET) |
+                                 BIT(NL80211_STA_FLAG_SMD_EXEC_CURRENT) |
+                                 BIT(NL80211_STA_FLAG_SMD_DL_DRAIN)))
                        return -EINVAL;
 
                /* but authenticated/associated only if driver handles it */
@@ -9703,7 +9713,7 @@ static int nl80211_new_station(struct sk_buff *skb, 
struct genl_info *info)
                return -EINVAL;
 
        /* When you run into this, adjust the code below for the new flag */
-       BUILD_BUG_ON(NL80211_STA_FLAG_MAX != 9);
+       BUILD_BUG_ON(NL80211_STA_FLAG_MAX != 12);
 
        switch (wdev->iftype) {
        case NL80211_IFTYPE_AP:

-- 
2.34.1


Reply via email to