netif_rx_ni(skb);
 }
 
-static void sta_apply_parameters(struct ieee80211_local *local,
-                                struct sta_info *sta,
-                                struct station_parameters *params)
+static int sta_apply_parameters(struct ieee80211_local *local,
+                               struct sta_info *sta,
+                               struct station_parameters *params)
 {
+       int ret = 0;
        u32 rates;
        int i, j;
        struct ieee80211_supported_band *sband;
        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_checked(sta,
+                                       IEEE80211_STA_AUTH);
+                       if (ret)
+                               return ret;
+                       ret = sta_info_move_state_checked(sta,
+                                       IEEE80211_STA_ASSOC);
+                       if (ret)
+                               return ret;
+               }
+       }
+
        if (mask & BIT(NL80211_STA_FLAG_AUTHORIZED)) {
                if (set & BIT(NL80211_STA_FLAG_AUTHORIZED))
-                       set_sta_flag(sta, WLAN_STA_AUTHORIZED);
+                       ret = sta_info_move_state_checked(sta,
+                                       IEEE80211_STA_AUTHORIZED);
                else
-                       clear_sta_flag(sta, WLAN_STA_AUTHORIZED);
+                       ret = sta_info_move_state_checked(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_checked(sta,
+                                       IEEE80211_STA_AUTH);
+                       if (ret)
+                               return ret;
+                       ret = sta_info_move_state_checked(sta,
+                                       IEEE80211_STA_NONE);
+                       if (ret)
+                               return ret;
+               }
+       }
+
+
        if (mask & BIT(NL80211_STA_FLAG_SHORT_PREAMBLE)) {
                if (set & BIT(NL80211_STA_FLAG_SHORT_PREAMBLE))
                        set_sta_flag(sta, WLAN_STA_SHORT_PREAMBLE);
                        clear_sta_flag(sta, WLAN_STA_MFP);
        }
 
-       if (mask & BIT(NL80211_STA_FLAG_AUTHENTICATED)) {
-               if (set & BIT(NL80211_STA_FLAG_AUTHENTICATED))
-                       set_sta_flag(sta, WLAN_STA_AUTH);
-               else
-                       clear_sta_flag(sta, WLAN_STA_AUTH);
-       }
-
        if (mask & BIT(NL80211_STA_FLAG_TDLS_PEER)) {
                if (set & BIT(NL80211_STA_FLAG_TDLS_PEER))
                        set_sta_flag(sta, WLAN_STA_TDLS_PEER);
                        }
 #endif
        }
+
+       return 0;
 }
 
 static int ieee80211_add_station(struct wiphy *wiphy, struct net_device *dev,
        if (!sta)
                return -ENOMEM;
 
-       set_sta_flag(sta, WLAN_STA_AUTH);
-       set_sta_flag(sta, WLAN_STA_ASSOC);
+       sta_info_move_state(sta, IEEE80211_STA_AUTH);
+       sta_info_move_state(sta, IEEE80211_STA_ASSOC);
 
-       sta_apply_parameters(local, sta, params);
+       err = sta_apply_parameters(local, sta, params);
+       if (err) {
+               sta_info_free(local, sta);
+               return err;
+       }
 
        /*
         * for TDLS, rate control should be initialized only when supported
 
                return NULL;
 
        sta->last_rx = jiffies;
-       set_sta_flag(sta, WLAN_STA_AUTHORIZED);
+
+       sta_info_move_state(sta, IEEE80211_STA_AUTH);
+       sta_info_move_state(sta, IEEE80211_STA_ASSOC);
+       sta_info_move_state(sta, IEEE80211_STA_AUTHORIZED);
 
        /* make sure mandatory rates are always added */
        sta->sta.supp_rates[band] = supp_rates |
 
                        goto err_del_interface;
                }
 
-               /* no atomic bitop required since STA is not live yet */
-               set_sta_flag(sta, WLAN_STA_AUTHORIZED);
+               sta_info_move_state(sta, IEEE80211_STA_AUTH);
+               sta_info_move_state(sta, IEEE80211_STA_ASSOC);
+               sta_info_move_state(sta, IEEE80211_STA_AUTHORIZED);
 
                res = sta_info_insert(sta);
                if (res) {
 
        if (!sta)
                return NULL;
 
-       set_sta_flag(sta, WLAN_STA_AUTH);
-       set_sta_flag(sta, WLAN_STA_AUTHORIZED);
+       sta_info_move_state(sta, IEEE80211_STA_AUTH);
+       sta_info_move_state(sta, IEEE80211_STA_ASSOC);
+       sta_info_move_state(sta, IEEE80211_STA_AUTHORIZED);
+
        set_sta_flag(sta, WLAN_STA_WME);
+
        sta->sta.supp_rates[local->hw.conf.channel->band] = rates;
        if (elems->ht_cap_elem)
                ieee80211_ht_cap_ie_to_sta_ht_cap(sdata, sband,
 
                return false;
        }
 
-       set_sta_flag(sta, WLAN_STA_AUTH);
-       set_sta_flag(sta, WLAN_STA_ASSOC);
+       sta_info_move_state(sta, IEEE80211_STA_AUTH);
+       sta_info_move_state(sta, IEEE80211_STA_ASSOC);
        if (!(ifmgd->flags & IEEE80211_STA_CONTROL_PORT))
-               set_sta_flag(sta, WLAN_STA_AUTHORIZED);
+               sta_info_move_state(sta, IEEE80211_STA_AUTHORIZED);
 
        rates = 0;
        basic_rates = 0;
 
 }
 
 /**
- * __sta_info_free - internal STA free helper
+ * sta_info_free - free STA
  *
  * @local: pointer to the global information
  * @sta: STA info to free
  *
  * This function must undo everything done by sta_info_alloc()
- * that may happen before sta_info_insert().
+ * that may happen before sta_info_insert(). It may only be
+ * called when sta_info_insert() has not been attempted (and
+ * if that fails, the station is freed anyway.)
  */
-static void __sta_info_free(struct ieee80211_local *local,
-                           struct sta_info *sta)
+void sta_info_free(struct ieee80211_local *local, struct sta_info *sta)
 {
        if (sta->rate_ctrl) {
                rate_control_free_sta(sta);
        return 0;
  out_free:
        BUG_ON(!err);
-       __sta_info_free(local, sta);
+       sta_info_free(local, sta);
        return err;
 }
 
        if (sdata->vif.type == NL80211_IFTYPE_AP_VLAN)
                RCU_INIT_POINTER(sdata->u.vlan.sta, NULL);
 
+       while (sta->sta_state > IEEE80211_STA_NONE)
+               sta_info_move_state(sta, sta->sta_state - 1);
+
        if (sta->uploaded) {
                if (sdata->vif.type == NL80211_IFTYPE_AP_VLAN)
                        sdata = container_of(sdata->bss,
                kfree_rcu(tid_tx, rcu_head);
        }
 
-       __sta_info_free(local, sta);
+       sta_info_free(local, sta);
 
        return 0;
 }
        sta_info_recalc_tim(sta);
 }
 EXPORT_SYMBOL(ieee80211_sta_set_buffered);
+
+int sta_info_move_state_checked(struct sta_info *sta,
+                               enum ieee80211_sta_state new_state)
+{
+       /* might_sleep(); -- for driver notify later, fix IBSS first */
+
+       if (sta->sta_state == new_state)
+               return 0;
+
+       switch (new_state) {
+       case IEEE80211_STA_NONE:
+               if (sta->sta_state == IEEE80211_STA_AUTH)
+                       clear_bit(WLAN_STA_AUTH, &sta->_flags);
+               else
+                       return -EINVAL;
+               break;
+       case IEEE80211_STA_AUTH:
+               if (sta->sta_state == IEEE80211_STA_NONE)
+                       set_bit(WLAN_STA_AUTH, &sta->_flags);
+               else if (sta->sta_state == IEEE80211_STA_ASSOC)
+                       clear_bit(WLAN_STA_ASSOC, &sta->_flags);
+               else
+                       return -EINVAL;
+               break;
+       case IEEE80211_STA_ASSOC:
+               if (sta->sta_state == IEEE80211_STA_AUTH)
+                       set_bit(WLAN_STA_ASSOC, &sta->_flags);
+               else if (sta->sta_state == IEEE80211_STA_AUTHORIZED)
+                       clear_bit(WLAN_STA_AUTHORIZED, &sta->_flags);
+               else
+                       return -EINVAL;
+               break;
+       case IEEE80211_STA_AUTHORIZED:
+               if (sta->sta_state == IEEE80211_STA_ASSOC)
+                       set_bit(WLAN_STA_AUTHORIZED, &sta->_flags);
+               else
+                       return -EINVAL;
+               break;
+       default:
+               WARN(1, "invalid state %d", new_state);
+               return -EINVAL;
+       }
+
+       printk(KERN_DEBUG "%s: moving STA %pM to state %d\n",
+               sta->sdata->name, sta->sta.addr, new_state);
+       sta->sta_state = new_state;
+
+       return 0;
+}
 
        WLAN_STA_4ADDR_EVENT,
 };
 
+enum ieee80211_sta_state {
+       /* NOTE: These need to be ordered correctly! */
+       IEEE80211_STA_NONE,
+       IEEE80211_STA_AUTH,
+       IEEE80211_STA_ASSOC,
+       IEEE80211_STA_AUTHORIZED,
+};
+
 #define STA_TID_NUM 16
 #define ADDBA_RESP_INTERVAL HZ
 #define HT_AGG_MAX_RETRIES             0x3
  * @dummy: indicate a dummy station created for receiving
  *     EAP frames before association
  * @sta: station information we share with the driver
+ * @sta_state: duplicates information about station state (for debug)
  */
 struct sta_info {
        /* General information, mostly static */
 
        bool uploaded;
 
+       enum ieee80211_sta_state sta_state;
+
        /* use the accessors defined below */
        unsigned long _flags;
 
 static inline void set_sta_flag(struct sta_info *sta,
                                enum ieee80211_sta_info_flags flag)
 {
+       WARN_ON(flag == WLAN_STA_AUTH ||
+               flag == WLAN_STA_ASSOC ||
+               flag == WLAN_STA_AUTHORIZED);
        set_bit(flag, &sta->_flags);
 }
 
 static inline void clear_sta_flag(struct sta_info *sta,
                                  enum ieee80211_sta_info_flags flag)
 {
+       WARN_ON(flag == WLAN_STA_AUTH ||
+               flag == WLAN_STA_ASSOC ||
+               flag == WLAN_STA_AUTHORIZED);
        clear_bit(flag, &sta->_flags);
 }
 
 static inline int test_and_clear_sta_flag(struct sta_info *sta,
                                          enum ieee80211_sta_info_flags flag)
 {
+       WARN_ON(flag == WLAN_STA_AUTH ||
+               flag == WLAN_STA_ASSOC ||
+               flag == WLAN_STA_AUTHORIZED);
        return test_and_clear_bit(flag, &sta->_flags);
 }
 
 static inline int test_and_set_sta_flag(struct sta_info *sta,
                                        enum ieee80211_sta_info_flags flag)
 {
+       WARN_ON(flag == WLAN_STA_AUTH ||
+               flag == WLAN_STA_ASSOC ||
+               flag == WLAN_STA_AUTHORIZED);
        return test_and_set_bit(flag, &sta->_flags);
 }
 
+int sta_info_move_state_checked(struct sta_info *sta,
+                               enum ieee80211_sta_state new_state);
+
+static inline void sta_info_move_state(struct sta_info *sta,
+                                      enum ieee80211_sta_state new_state)
+{
+       int ret = sta_info_move_state_checked(sta, new_state);
+       WARN_ON_ONCE(ret);
+}
+
+
 void ieee80211_assign_tid_tx(struct sta_info *sta, int tid,
                             struct tid_ampdu_tx *tid_tx);
 
  */
 struct sta_info *sta_info_alloc(struct ieee80211_sub_if_data *sdata,
                                u8 *addr, gfp_t gfp);
+
+void sta_info_free(struct ieee80211_local *local, struct sta_info *sta);
+
 /*
  * Insert STA info into hash table/list, returns zero or a
  * -EEXIST if (if the same MAC address is already present).
 
 
        if (likely(tx->flags & IEEE80211_TX_UNICAST)) {
                if (unlikely(!assoc &&
-                            tx->sdata->vif.type != NL80211_IFTYPE_ADHOC &&
                             ieee80211_is_data(hdr->frame_control))) {
 #ifdef CONFIG_MAC80211_VERBOSE_DEBUG
                        printk(KERN_DEBUG "%s: dropped data frame to not "