*     flag can't be changed, it is only valid while adding a station, and
  *     attempts to change it will silently be ignored (rather than rejected
  *     as errors.)
+ * @NL80211_STA_FLAG_ASSOCIATED: station is associated; used with drivers
+ *     that support %NL80211_FEATURE_FULL_AP_CLIENT_STATE to transition a
+ *     previously added station into associated state
  * @NL80211_STA_FLAG_MAX: highest station flag number currently defined
  * @__NL80211_STA_FLAG_AFTER_LAST: internal use
  */
        NL80211_STA_FLAG_MFP,
        NL80211_STA_FLAG_AUTHENTICATED,
        NL80211_STA_FLAG_TDLS_PEER,
+       NL80211_STA_FLAG_ASSOCIATED,
 
        /* keep last */
        __NL80211_STA_FLAG_AFTER_LAST,
  *     setting
  * @NL80211_FEATURE_P2P_GO_OPPPS: P2P GO implementation supports opportunistic
  *     powersave
+ * @NL80211_FEATURE_FULL_AP_CLIENT_STATE: The driver supports full state
+ *     transitions for AP clients. Without this flag (and if the driver
+ *     doesn't have the AP SME in the device) the driver supports adding
+ *     stations only when they're associated and adds them in associated
+ *     state (to later be transitioned into authorized), with this flag
+ *     they should be added before even sending the authentication reply
+ *     and then transitioned into authenticated, associated and authorized
+ *     states using station flags.
+ *     Note that even for drivers that support this, the default is to add
+ *     stations in authenticated/associated state, so to add unauthenticated
+ *     stations the authenticated/associated bits have to be set in the mask.
  */
 enum nl80211_feature_flags {
        NL80211_FEATURE_SK_TX_STATUS                    = 1 << 0,
        NL80211_FEATURE_NEED_OBSS_SCAN                  = 1 << 10,
        NL80211_FEATURE_P2P_GO_CTWIN                    = 1 << 11,
        NL80211_FEATURE_P2P_GO_OPPPS                    = 1 << 12,
+       NL80211_FEATURE_FULL_AP_CLIENT_STATE            = 1 << 13,
 };
 
 /**
 
                                BIT(NL80211_STA_FLAG_WME) |
                                BIT(NL80211_STA_FLAG_MFP) |
                                BIT(NL80211_STA_FLAG_AUTHENTICATED) |
+                               BIT(NL80211_STA_FLAG_ASSOCIATED) |
                                BIT(NL80211_STA_FLAG_TDLS_PEER);
        if (test_sta_flag(sta, WLAN_STA_AUTHORIZED))
                sinfo->sta_flags.set |= BIT(NL80211_STA_FLAG_AUTHORIZED);
                sinfo->sta_flags.set |= BIT(NL80211_STA_FLAG_MFP);
        if (test_sta_flag(sta, WLAN_STA_AUTH))
                sinfo->sta_flags.set |= BIT(NL80211_STA_FLAG_AUTHENTICATED);
+       if (test_sta_flag(sta, WLAN_STA_ASSOC))
+               sinfo->sta_flags.set |= BIT(NL80211_STA_FLAG_ASSOCIATED);
        if (test_sta_flag(sta, WLAN_STA_TDLS_PEER))
                sinfo->sta_flags.set |= BIT(NL80211_STA_FLAG_TDLS_PEER);
 }
        netif_rx_ni(skb);
 }
 
+static int sta_apply_auth_flags(struct ieee80211_local *local,
+                               struct sta_info *sta,
+                               u32 mask, u32 set)
+{
+       int ret;
+
+       if (mask & BIT(NL80211_STA_FLAG_AUTHENTICATED) &&
+           set & BIT(NL80211_STA_FLAG_AUTHENTICATED) &&
+           !test_sta_flag(sta, WLAN_STA_AUTH)) {
+               ret = sta_info_move_state(sta, IEEE80211_STA_AUTH);
+               if (ret)
+                       return ret;
+       }
+
+       if (mask & BIT(NL80211_STA_FLAG_ASSOCIATED) &&
+           set & BIT(NL80211_STA_FLAG_ASSOCIATED) &&
+           !test_sta_flag(sta, WLAN_STA_ASSOC)) {
+               ret = sta_info_move_state(sta, IEEE80211_STA_ASSOC);
+               if (ret)
+                       return ret;
+       }
+
+       if (mask & BIT(NL80211_STA_FLAG_AUTHORIZED)) {
+               if (set & BIT(NL80211_STA_FLAG_AUTHORIZED))
+                       ret = sta_info_move_state(sta, IEEE80211_STA_AUTHORIZED);
+               else if (test_sta_flag(sta, WLAN_STA_AUTHORIZED))
+                       ret = sta_info_move_state(sta, IEEE80211_STA_ASSOC);
+               else
+                       ret = 0;
+               if (ret)
+                       return ret;
+       }
+
+       if (mask & BIT(NL80211_STA_FLAG_ASSOCIATED) &&
+           !(set & BIT(NL80211_STA_FLAG_ASSOCIATED)) &&
+           test_sta_flag(sta, WLAN_STA_ASSOC)) {
+               ret = sta_info_move_state(sta, IEEE80211_STA_AUTH);
+               if (ret)
+                       return ret;
+       }
+
+       if (mask & BIT(NL80211_STA_FLAG_AUTHENTICATED) &&
+           !(set & BIT(NL80211_STA_FLAG_AUTHENTICATED)) &&
+           test_sta_flag(sta, WLAN_STA_AUTH)) {
+               ret = sta_info_move_state(sta, IEEE80211_STA_NONE);
+               if (ret)
+                       return ret;
+       }
+
+       return 0;
+}
+
 static int sta_apply_parameters(struct ieee80211_local *local,
                                struct sta_info *sta,
                                struct station_parameters *params)
        mask = params->sta_flags_mask;
        set = params->sta_flags_set;
 
-       /*
-        * In mesh mode, we can clear AUTHENTICATED flag but must
-        * also make ASSOCIATED follow appropriately for the driver
-        * API. See also below, after AUTHORIZED changes.
-        */
-       if (mask & BIT(NL80211_STA_FLAG_AUTHENTICATED)) {
-               /* cfg80211 should not allow this in non-mesh modes */
-               if (WARN_ON(!ieee80211_vif_is_mesh(&sdata->vif)))
-                       return -EINVAL;
-
-               if (set & BIT(NL80211_STA_FLAG_AUTHENTICATED) &&
-                   !test_sta_flag(sta, WLAN_STA_AUTH)) {
-                       ret = sta_info_move_state(sta, IEEE80211_STA_AUTH);
-                       if (ret)
-                               return ret;
-                       ret = sta_info_move_state(sta, IEEE80211_STA_ASSOC);
-                       if (ret)
-                               return ret;
-               }
-       }
-
-       if (mask & BIT(NL80211_STA_FLAG_AUTHORIZED)) {
-               if (set & BIT(NL80211_STA_FLAG_AUTHORIZED))
-                       ret = sta_info_move_state(sta, IEEE80211_STA_AUTHORIZED);
-               else if (test_sta_flag(sta, WLAN_STA_AUTHORIZED))
-                       ret = sta_info_move_state(sta, IEEE80211_STA_ASSOC);
-               if (ret)
-                       return ret;
-       }
-
-       if (mask & BIT(NL80211_STA_FLAG_AUTHENTICATED)) {
-               /* cfg80211 should not allow this in non-mesh modes */
-               if (WARN_ON(!ieee80211_vif_is_mesh(&sdata->vif)))
-                       return -EINVAL;
-
-               if (!(set & BIT(NL80211_STA_FLAG_AUTHENTICATED)) &&
-                   test_sta_flag(sta, WLAN_STA_AUTH)) {
-                       ret = sta_info_move_state(sta, IEEE80211_STA_AUTH);
-                       if (ret)
-                               return ret;
-                       ret = sta_info_move_state(sta, IEEE80211_STA_NONE);
-                       if (ret)
-                               return ret;
-               }
+       if (ieee80211_vif_is_mesh(&sdata->vif)) {
+               /*
+                * In mesh mode, ASSOCIATED isn't part of the nl80211
+                * API but must follow AUTHENTICATED for driver state.
+                */
+               if (mask & BIT(NL80211_STA_FLAG_AUTHENTICATED))
+                       mask |= BIT(NL80211_STA_FLAG_ASSOCIATED);
+               if (set & BIT(NL80211_STA_FLAG_AUTHENTICATED))
+                       set |= BIT(NL80211_STA_FLAG_ASSOCIATED);
        }
 
+       ret = sta_apply_auth_flags(local, sta, mask, set);
+       if (ret)
+               return ret;
 
        if (mask & BIT(NL80211_STA_FLAG_SHORT_PREAMBLE)) {
                if (set & BIT(NL80211_STA_FLAG_SHORT_PREAMBLE))
        if (!sta)
                return -ENOMEM;
 
+       /*
+        * defaults -- if userspace wants something else we'll
+        * change it accordingly in sta_apply_parameters()
+        */
        sta_info_pre_move_state(sta, IEEE80211_STA_AUTH);
        sta_info_pre_move_state(sta, IEEE80211_STA_ASSOC);
 
 
                /* accept only the listed bits */
                if (params.sta_flags_mask &
                                ~(BIT(NL80211_STA_FLAG_AUTHORIZED) |
+                                 BIT(NL80211_STA_FLAG_AUTHENTICATED) |
+                                 BIT(NL80211_STA_FLAG_ASSOCIATED) |
                                  BIT(NL80211_STA_FLAG_SHORT_PREAMBLE) |
                                  BIT(NL80211_STA_FLAG_WME) |
                                  BIT(NL80211_STA_FLAG_MFP)))
                        return -EINVAL;
 
+               /* but authenticated/associated only if driver handles it */
+               if (!(rdev->wiphy.features &
+                               NL80211_FEATURE_FULL_AP_CLIENT_STATE) &&
+                   params.sta_flags_mask &
+                               (BIT(NL80211_STA_FLAG_AUTHENTICATED) |
+                                BIT(NL80211_STA_FLAG_ASSOCIATED)))
+                       return -EINVAL;
+
                /* must be last in here for error handling */
                params.vlan = get_vlan(info, rdev);
                if (IS_ERR(params.vlan))
                /* but don't bother the driver with it */
                params.sta_flags_mask &= ~BIT(NL80211_STA_FLAG_TDLS_PEER);
 
+               /* allow authenticated/associated only if driver handles it */
+               if (!(rdev->wiphy.features &
+                               NL80211_FEATURE_FULL_AP_CLIENT_STATE) &&
+                   params.sta_flags_mask &
+                               (BIT(NL80211_STA_FLAG_AUTHENTICATED) |
+                                BIT(NL80211_STA_FLAG_ASSOCIATED)))
+                       return -EINVAL;
+
                /* must be last in here for error handling */
                params.vlan = get_vlan(info, rdev);
                if (IS_ERR(params.vlan))
                        return PTR_ERR(params.vlan);
                break;
        case NL80211_IFTYPE_MESH_POINT:
+               /* associated is disallowed */
+               if (params.sta_flags_mask & BIT(NL80211_STA_FLAG_ASSOCIATED))
+                       return -EINVAL;
                /* TDLS peers cannot be added */
                if (params.sta_flags_set & BIT(NL80211_STA_FLAG_TDLS_PEER))
                        return -EINVAL;
                break;
        case NL80211_IFTYPE_STATION:
+               /* associated is disallowed */
+               if (params.sta_flags_mask & BIT(NL80211_STA_FLAG_ASSOCIATED))
+                       return -EINVAL;
                /* Only TDLS peers can be added */
                if (!(params.sta_flags_set & BIT(NL80211_STA_FLAG_TDLS_PEER)))
                        return -EINVAL;