mac80211: add sta_state callback
authorJohannes Berg <johannes.berg@intel.com>
Fri, 20 Jan 2012 12:55:21 +0000 (13:55 +0100)
committerJohn W. Linville <linville@tuxdriver.com>
Mon, 6 Feb 2012 19:48:24 +0000 (14:48 -0500)
(based on Eliad's patch)

Add a callback to notify the low-level driver whenever
the state of a station changes. The driver is only
notified when the station is actually in the mac80211
hash table, not for pre-insert state transitions.

To allow the driver to replace sta_add/remove calls
with this, call extra transitions with the NOTEXIST
state.

This callback can fail, so we need to be careful in
handling it when a station is inserted, particularly
in the IBSS case where we still keep the station entry
around for mac80211 purposes.

Signed-off-by: Eliad Peller <eliad@wizery.com>
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
Signed-off-by: John W. Linville <linville@tuxdriver.com>
include/net/mac80211.h
net/mac80211/driver-ops.h
net/mac80211/driver-trace.h
net/mac80211/main.c
net/mac80211/pm.c
net/mac80211/sta_info.c
net/mac80211/sta_info.h
net/mac80211/util.c

index 520eb4c5e5a2827464988d9658cb6da22ee4cd05..922313f0b39bcca99cb34cb5f180e3b3e63dbd15 100644 (file)
@@ -981,6 +981,25 @@ enum set_key_cmd {
        SET_KEY, DISABLE_KEY,
 };
 
+/**
+ * enum ieee80211_sta_state - station state
+ *
+ * @IEEE80211_STA_NOTEXIST: station doesn't exist at all,
+ *     this is a special state for add/remove transitions
+ * @IEEE80211_STA_NONE: station exists without special state
+ * @IEEE80211_STA_AUTH: station is authenticated
+ * @IEEE80211_STA_ASSOC: station is associated
+ * @IEEE80211_STA_AUTHORIZED: station is authorized (802.1X)
+ */
+enum ieee80211_sta_state {
+       /* NOTE: These need to be ordered correctly! */
+       IEEE80211_STA_NOTEXIST,
+       IEEE80211_STA_NONE,
+       IEEE80211_STA_AUTH,
+       IEEE80211_STA_ASSOC,
+       IEEE80211_STA_AUTHORIZED,
+};
+
 /**
  * struct ieee80211_sta - station table entry
  *
@@ -1974,6 +1993,13 @@ enum ieee80211_frame_release_type {
  *     in AP mode, this callback will not be called when the flag
  *     %IEEE80211_HW_AP_LINK_PS is set. Must be atomic.
  *
+ * @sta_state: Notifies low level driver about state transition of a
+ *     station (which can be the AP, a client, IBSS/WDS/mesh peer etc.)
+ *     This callback is mutually exclusive with @sta_add/@sta_remove.
+ *     It must not fail for down transitions but may fail for transitions
+ *     up the list of states.
+ *     The callback can sleep.
+ *
  * @conf_tx: Configure TX queue parameters (EDCF (aifs, cw_min, cw_max),
  *     bursting) for a hardware TX queue.
  *     Returns a negative error code on failure.
@@ -2193,6 +2219,10 @@ struct ieee80211_ops {
                          struct ieee80211_sta *sta);
        void (*sta_notify)(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
                        enum sta_notify_cmd, struct ieee80211_sta *sta);
+       int (*sta_state)(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
+                        struct ieee80211_sta *sta,
+                        enum ieee80211_sta_state old_state,
+                        enum ieee80211_sta_state new_state);
        int (*conf_tx)(struct ieee80211_hw *hw,
                       struct ieee80211_vif *vif, u16 queue,
                       const struct ieee80211_tx_queue_params *params);
index b8673f6100df5f32e8084e58f460a294fc3a90f5..4bd266ec5333ff46c95f2e7b3e596da981cec3fd 100644 (file)
@@ -478,6 +478,28 @@ static inline void drv_sta_remove(struct ieee80211_local *local,
        trace_drv_return_void(local);
 }
 
+static inline __must_check
+int drv_sta_state(struct ieee80211_local *local,
+                 struct ieee80211_sub_if_data *sdata,
+                 struct sta_info *sta,
+                 enum ieee80211_sta_state old_state,
+                 enum ieee80211_sta_state new_state)
+{
+       int ret = 0;
+
+       might_sleep();
+
+       sdata = get_bss_sdata(sdata);
+       check_sdata_in_driver(sdata);
+
+       trace_drv_sta_state(local, sdata, &sta->sta, old_state, new_state);
+       if (local->ops->sta_state)
+               ret = local->ops->sta_state(&local->hw, &sdata->vif, &sta->sta,
+                                           old_state, new_state);
+       trace_drv_return_int(local, ret);
+       return ret;
+}
+
 static inline int drv_conf_tx(struct ieee80211_local *local,
                              struct ieee80211_sub_if_data *sdata, u16 queue,
                              const struct ieee80211_tx_queue_params *params)
index 6e9df8fd8fb8c7d04e2d834232152a0377952823..384e2f08c187051f3bfb647adb66204a6c2153e0 100644 (file)
@@ -635,6 +635,38 @@ TRACE_EVENT(drv_sta_notify,
        )
 );
 
+TRACE_EVENT(drv_sta_state,
+       TP_PROTO(struct ieee80211_local *local,
+                struct ieee80211_sub_if_data *sdata,
+                struct ieee80211_sta *sta,
+                enum ieee80211_sta_state old_state,
+                enum ieee80211_sta_state new_state),
+
+       TP_ARGS(local, sdata, sta, old_state, new_state),
+
+       TP_STRUCT__entry(
+               LOCAL_ENTRY
+               VIF_ENTRY
+               STA_ENTRY
+               __field(u32, old_state)
+               __field(u32, new_state)
+       ),
+
+       TP_fast_assign(
+               LOCAL_ASSIGN;
+               VIF_ASSIGN;
+               STA_ASSIGN;
+               __entry->old_state = old_state;
+               __entry->new_state = new_state;
+       ),
+
+       TP_printk(
+               LOCAL_PR_FMT  VIF_PR_FMT  STA_PR_FMT " state: %d->%d",
+               LOCAL_PR_ARG, VIF_PR_ARG, STA_PR_ARG,
+               __entry->old_state, __entry->new_state
+       )
+);
+
 TRACE_EVENT(drv_sta_add,
        TP_PROTO(struct ieee80211_local *local,
                 struct ieee80211_sub_if_data *sdata,
index 6192caadfab99689f5eb3886bf2032adcf14a2f6..f4fc540aac1720c2bfb3c414752d1dec24e8231a 100644 (file)
@@ -535,6 +535,9 @@ struct ieee80211_hw *ieee80211_alloc_hw(size_t priv_data_len,
        int priv_size, i;
        struct wiphy *wiphy;
 
+       if (WARN_ON(ops->sta_state && (ops->sta_add || ops->sta_remove)))
+               return NULL;
+
        /* Ensure 32-byte alignment of our private data and hw private data.
         * We use the wiphy priv data for both our ieee80211_local and for
         * the driver's private data
index c65ff471acce952aec185056384e113f3588f9ec..af49ac4f08261ec8f199591bbd1155f8c5f97a1d 100644 (file)
@@ -97,9 +97,17 @@ int __ieee80211_suspend(struct ieee80211_hw *hw, struct cfg80211_wowlan *wowlan)
        /* tear down aggregation sessions and remove STAs */
        mutex_lock(&local->sta_mtx);
        list_for_each_entry(sta, &local->sta_list, list) {
-               if (sta->uploaded)
+               if (sta->uploaded) {
+                       enum ieee80211_sta_state state;
+
                        drv_sta_remove(local, sta->sdata, &sta->sta);
 
+                       state = sta->sta_state;
+                       for (; state > IEEE80211_STA_NOTEXIST; state--)
+                               WARN_ON(drv_sta_state(local, sdata, sta,
+                                                     state, state - 1));
+               }
+
                mesh_plink_quiesce(sta);
        }
        mutex_unlock(&local->sta_mtx);
index 464bc691644b90938e5eca7e17520761f2f2b1cf..fcd9027c669961aa873cc82e6ba3ecbd138582d6 100644 (file)
@@ -351,6 +351,38 @@ static int sta_info_insert_check(struct sta_info *sta)
        return 0;
 }
 
+static int sta_info_insert_drv_state(struct ieee80211_local *local,
+                                    struct ieee80211_sub_if_data *sdata,
+                                    struct sta_info *sta)
+{
+       enum ieee80211_sta_state state;
+       int err = 0;
+
+       for (state = IEEE80211_STA_NOTEXIST; state < sta->sta_state; state++) {
+               err = drv_sta_state(local, sdata, sta, state, state + 1);
+               if (err)
+                       break;
+       }
+
+       if (!err) {
+               sta->uploaded = true;
+               return 0;
+       }
+
+       if (sdata->vif.type == NL80211_IFTYPE_ADHOC) {
+               printk(KERN_DEBUG
+                      "%s: failed to move IBSS STA %pM to state %d (%d) - keeping it anyway.\n",
+                      sdata->name, sta->sta.addr, state + 1, err);
+               err = 0;
+       }
+
+       /* unwind on error */
+       for (; state > IEEE80211_STA_NOTEXIST; state--)
+               WARN_ON(drv_sta_state(local, sdata, sta, state, state - 1));
+
+       return err;
+}
+
 /*
  * should be called with sta_mtx locked
  * this function replaces the mutex lock
@@ -392,8 +424,11 @@ static int sta_info_insert_finish(struct sta_info *sta) __acquires(RCU)
                        printk(KERN_DEBUG "%s: failed to add IBSS STA %pM to "
                                          "driver (%d) - keeping it anyway.\n",
                               sdata->name, sta->sta.addr, err);
-               } else
-                       sta->uploaded = true;
+               } else {
+                       err = sta_info_insert_drv_state(local, sdata, sta);
+                       if (err)
+                               goto out_err;
+               }
        }
 
        if (!dummy_reinsert) {
@@ -759,15 +794,19 @@ int __must_check __sta_info_destroy(struct sta_info *sta)
                RCU_INIT_POINTER(sdata->u.vlan.sta, NULL);
 
        while (sta->sta_state > IEEE80211_STA_NONE) {
-               int err = sta_info_move_state(sta, sta->sta_state - 1);
-               if (err) {
+               ret = sta_info_move_state(sta, sta->sta_state - 1);
+               if (ret) {
                        WARN_ON_ONCE(1);
                        break;
                }
        }
 
-       if (sta->uploaded)
+       if (sta->uploaded) {
                drv_sta_remove(local, sdata, &sta->sta);
+               ret = drv_sta_state(local, sdata, sta, IEEE80211_STA_NONE,
+                                   IEEE80211_STA_NOTEXIST);
+               WARN_ON_ONCE(ret != 0);
+       }
 
        /*
         * At this point, after we wait for an RCU grace period,
@@ -1404,20 +1443,58 @@ int sta_info_move_state(struct sta_info *sta,
        if (sta->sta_state == new_state)
                return 0;
 
+       /* check allowed transitions first */
+
+       switch (new_state) {
+       case IEEE80211_STA_NONE:
+               if (sta->sta_state != IEEE80211_STA_AUTH)
+                       return -EINVAL;
+               break;
+       case IEEE80211_STA_AUTH:
+               if (sta->sta_state != IEEE80211_STA_NONE &&
+                   sta->sta_state != IEEE80211_STA_ASSOC)
+                       return -EINVAL;
+               break;
+       case IEEE80211_STA_ASSOC:
+               if (sta->sta_state != IEEE80211_STA_AUTH &&
+                   sta->sta_state != IEEE80211_STA_AUTHORIZED)
+                       return -EINVAL;
+               break;
+       case IEEE80211_STA_AUTHORIZED:
+               if (sta->sta_state != IEEE80211_STA_ASSOC)
+                       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);
+
+       /*
+        * notify the driver before the actual changes so it can
+        * fail the transition
+        */
+       if (test_sta_flag(sta, WLAN_STA_INSERTED)) {
+               int err = drv_sta_state(sta->local, sta->sdata, sta,
+                                       sta->sta_state, new_state);
+               if (err)
+                       return err;
+       }
+
+       /* reflect the change in all state variables */
+
        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) {
@@ -1426,24 +1503,19 @@ int sta_info_move_state(struct sta_info *sta,
                        if (sta->sdata->vif.type == NL80211_IFTYPE_AP)
                                atomic_dec(&sta->sdata->u.ap.num_sta_authorized);
                        clear_bit(WLAN_STA_AUTHORIZED, &sta->_flags);
-               } else
-                       return -EINVAL;
+               }
                break;
        case IEEE80211_STA_AUTHORIZED:
                if (sta->sta_state == IEEE80211_STA_ASSOC) {
                        if (sta->sdata->vif.type == NL80211_IFTYPE_AP)
                                atomic_inc(&sta->sdata->u.ap.num_sta_authorized);
                        set_bit(WLAN_STA_AUTHORIZED, &sta->_flags);
-               } else
-                       return -EINVAL;
+               }
                break;
        default:
-               WARN(1, "invalid state %d", new_state);
-               return -EINVAL;
+               break;
        }
 
-       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;
index da4b03c1c3bcf8748163888f265291eeab44f41d..2ee808860007173105ec2f389c345e9ddea5d353 100644 (file)
@@ -75,15 +75,6 @@ enum ieee80211_sta_info_flags {
        WLAN_STA_INSERTED,
 };
 
-enum ieee80211_sta_state {
-       /* NOTE: These need to be ordered correctly! */
-       IEEE80211_STA_NOTEXIST,
-       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             15
index d4966e88aa4995a651e735a3befbe3829717fd75..8f8b4ecc776fd7b08e3989d33d550b7ad94b38d1 100644 (file)
@@ -1184,8 +1184,16 @@ int ieee80211_reconfig(struct ieee80211_local *local)
        /* add STAs back */
        mutex_lock(&local->sta_mtx);
        list_for_each_entry(sta, &local->sta_list, list) {
-               if (sta->uploaded)
+               if (sta->uploaded) {
+                       enum ieee80211_sta_state state;
+
                        WARN_ON(drv_sta_add(local, sta->sdata, &sta->sta));
+
+                       for (state = IEEE80211_STA_NOTEXIST;
+                            state < sta->sta_state - 1; state++)
+                               WARN_ON(drv_sta_state(local, sta->sdata, sta,
+                                                     state, state + 1));
+               }
        }
        mutex_unlock(&local->sta_mtx);