mac80211: support runtime interface type changes
authorJohannes Berg <johannes.berg@intel.com>
Fri, 27 Aug 2010 10:35:58 +0000 (12:35 +0200)
committerJohn W. Linville <linville@tuxdriver.com>
Fri, 27 Aug 2010 17:53:31 +0000 (13:53 -0400)
Add support to mac80211 for changing the interface
type even when the interface is UP, if the driver
supports it.

To achieve this
 * add a new driver callback for switching,
 * split some of the interface up/down code out
   into new functions (do_open/do_stop), and
 * maintain an own __SDATA_RUNNING bit that will
   not be set during interface type, so that any
   other code doesn't use the interface.

Signed-off-by: Johannes Berg <johannes.berg@intel.com>
Signed-off-by: John W. Linville <linville@tuxdriver.com>
include/net/mac80211.h
net/mac80211/cfg.c
net/mac80211/driver-ops.h
net/mac80211/driver-trace.h
net/mac80211/ieee80211_i.h
net/mac80211/iface.c

index 8f97548..f91fc33 100644 (file)
@@ -1537,6 +1537,12 @@ enum ieee80211_ampdu_mlme_action {
  *     negative error code (which will be seen in userspace.)
  *     Must be implemented and can sleep.
  *
+ * @change_interface: Called when a netdevice changes type. This callback
+ *     is optional, but only if it is supported can interface types be
+ *     switched while the interface is UP. The callback may sleep.
+ *     Note that while an interface is being switched, it will not be
+ *     found by the interface iteration callbacks.
+ *
  * @remove_interface: Notifies a driver that an interface is going down.
  *     The @stop callback is called after this if it is the last interface
  *     and no monitor interfaces are present.
@@ -1693,6 +1699,9 @@ struct ieee80211_ops {
        void (*stop)(struct ieee80211_hw *hw);
        int (*add_interface)(struct ieee80211_hw *hw,
                             struct ieee80211_vif *vif);
+       int (*change_interface)(struct ieee80211_hw *hw,
+                               struct ieee80211_vif *vif,
+                               enum nl80211_iftype new_type);
        void (*remove_interface)(struct ieee80211_hw *hw,
                                 struct ieee80211_vif *vif);
        int (*config)(struct ieee80211_hw *hw, u32 changed);
index f82b18e..5de1ca3 100644 (file)
@@ -52,9 +52,6 @@ static int ieee80211_change_iface(struct wiphy *wiphy,
        struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
        int ret;
 
-       if (ieee80211_sdata_running(sdata))
-               return -EBUSY;
-
        ret = ieee80211_if_change_type(sdata, type);
        if (ret)
                return ret;
index 14123dc..6064b7b 100644 (file)
@@ -54,6 +54,20 @@ static inline int drv_add_interface(struct ieee80211_local *local,
        return ret;
 }
 
+static inline int drv_change_interface(struct ieee80211_local *local,
+                                      struct ieee80211_sub_if_data *sdata,
+                                      enum nl80211_iftype type)
+{
+       int ret;
+
+       might_sleep();
+
+       trace_drv_change_interface(local, sdata, type);
+       ret = local->ops->change_interface(&local->hw, &sdata->vif, type);
+       trace_drv_return_int(local, ret);
+       return ret;
+}
+
 static inline void drv_remove_interface(struct ieee80211_local *local,
                                        struct ieee80211_vif *vif)
 {
index b5a9558..f6f3d89 100644 (file)
@@ -136,6 +136,31 @@ TRACE_EVENT(drv_add_interface,
        )
 );
 
+TRACE_EVENT(drv_change_interface,
+       TP_PROTO(struct ieee80211_local *local,
+                struct ieee80211_sub_if_data *sdata,
+                enum nl80211_iftype type),
+
+       TP_ARGS(local, sdata, type),
+
+       TP_STRUCT__entry(
+               LOCAL_ENTRY
+               VIF_ENTRY
+               __field(u32, new_type)
+       ),
+
+       TP_fast_assign(
+               LOCAL_ASSIGN;
+               VIF_ASSIGN;
+               __entry->new_type = type;
+       ),
+
+       TP_printk(
+               LOCAL_PR_FMT  VIF_PR_FMT " new type:%d",
+               LOCAL_PR_ARG, VIF_PR_ARG, __entry->new_type
+       )
+);
+
 TRACE_EVENT(drv_remove_interface,
        TP_PROTO(struct ieee80211_local *local, struct ieee80211_sub_if_data *sdata),
 
index f648377..d529bd5 100644 (file)
@@ -472,6 +472,16 @@ enum ieee80211_sub_if_data_flags {
        IEEE80211_SDATA_DONT_BRIDGE_PACKETS     = BIT(3),
 };
 
+/**
+ * enum ieee80211_sdata_state_bits - virtual interface state bits
+ * @SDATA_STATE_RUNNING: virtual interface is up & running; this
+ *     mirrors netif_running() but is separate for interface type
+ *     change handling while the interface is up
+ */
+enum ieee80211_sdata_state_bits {
+       SDATA_STATE_RUNNING,
+};
+
 struct ieee80211_sub_if_data {
        struct list_head list;
 
@@ -485,6 +495,8 @@ struct ieee80211_sub_if_data {
 
        unsigned int flags;
 
+       unsigned long state;
+
        int drop_unencrypted;
 
        char name[IFNAMSIZ];
@@ -1087,7 +1099,7 @@ void ieee80211_recalc_idle(struct ieee80211_local *local);
 
 static inline bool ieee80211_sdata_running(struct ieee80211_sub_if_data *sdata)
 {
-       return netif_running(sdata->dev);
+       return test_bit(SDATA_STATE_RUNNING, &sdata->state);
 }
 
 /* tx handling */
index cba3d80..c1cc200 100644 (file)
@@ -148,7 +148,12 @@ static int ieee80211_check_concurrent_iface(struct ieee80211_sub_if_data *sdata,
        return 0;
 }
 
-static int ieee80211_open(struct net_device *dev)
+/*
+ * NOTE: Be very careful when changing this function, it must NOT return
+ * an error on interface type changes that have been pre-checked, so most
+ * checks should be in ieee80211_check_concurrent_iface.
+ */
+static int ieee80211_do_open(struct net_device *dev, bool coming_up)
 {
        struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
        struct ieee80211_local *local = sdata->local;
@@ -157,15 +162,6 @@ static int ieee80211_open(struct net_device *dev)
        int res;
        u32 hw_reconf_flags = 0;
 
-       /* fail early if user set an invalid address */
-       if (!is_zero_ether_addr(dev->dev_addr) &&
-           !is_valid_ether_addr(dev->dev_addr))
-               return -EADDRNOTAVAIL;
-
-       res = ieee80211_check_concurrent_iface(sdata, sdata->vif.type);
-       if (res)
-               return res;
-
        switch (sdata->vif.type) {
        case NL80211_IFTYPE_WDS:
                if (!is_valid_ether_addr(sdata->u.wds.remote_addr))
@@ -258,9 +254,11 @@ static int ieee80211_open(struct net_device *dev)
                netif_carrier_on(dev);
                break;
        default:
-               res = drv_add_interface(local, &sdata->vif);
-               if (res)
-                       goto err_stop;
+               if (coming_up) {
+                       res = drv_add_interface(local, &sdata->vif);
+                       if (res)
+                               goto err_stop;
+               }
 
                if (ieee80211_vif_is_mesh(&sdata->vif)) {
                        local->fif_other_bss++;
@@ -316,7 +314,9 @@ static int ieee80211_open(struct net_device *dev)
        hw_reconf_flags |= __ieee80211_recalc_idle(local);
        mutex_unlock(&local->mtx);
 
-       local->open_count++;
+       if (coming_up)
+               local->open_count++;
+
        if (hw_reconf_flags) {
                ieee80211_hw_config(local, hw_reconf_flags);
                /*
@@ -331,6 +331,8 @@ static int ieee80211_open(struct net_device *dev)
 
        netif_tx_start_all_queues(dev);
 
+       set_bit(SDATA_STATE_RUNNING, &sdata->state);
+
        return 0;
  err_del_interface:
        drv_remove_interface(local, &sdata->vif);
@@ -344,19 +346,38 @@ static int ieee80211_open(struct net_device *dev)
        return res;
 }
 
-static int ieee80211_stop(struct net_device *dev)
+static int ieee80211_open(struct net_device *dev)
 {
        struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
+       int err;
+
+       /* fail early if user set an invalid address */
+       if (!is_zero_ether_addr(dev->dev_addr) &&
+           !is_valid_ether_addr(dev->dev_addr))
+               return -EADDRNOTAVAIL;
+
+       err = ieee80211_check_concurrent_iface(sdata, sdata->vif.type);
+       if (err)
+               return err;
+
+       return ieee80211_do_open(dev, true);
+}
+
+static void ieee80211_do_stop(struct ieee80211_sub_if_data *sdata,
+                             bool going_down)
+{
        struct ieee80211_local *local = sdata->local;
        unsigned long flags;
        struct sk_buff *skb, *tmp;
        u32 hw_reconf_flags = 0;
        int i;
 
+       clear_bit(SDATA_STATE_RUNNING, &sdata->state);
+
        /*
         * Stop TX on this interface first.
         */
-       netif_tx_stop_all_queues(dev);
+       netif_tx_stop_all_queues(sdata->dev);
 
        /*
         * Purge work for this interface.
@@ -394,11 +415,12 @@ static int ieee80211_stop(struct net_device *dev)
        if (sdata->vif.type == NL80211_IFTYPE_AP)
                local->fif_pspoll--;
 
-       netif_addr_lock_bh(dev);
+       netif_addr_lock_bh(sdata->dev);
        spin_lock_bh(&local->filter_lock);
-       __hw_addr_unsync(&local->mc_list, &dev->mc, dev->addr_len);
+       __hw_addr_unsync(&local->mc_list, &sdata->dev->mc,
+                        sdata->dev->addr_len);
        spin_unlock_bh(&local->filter_lock);
-       netif_addr_unlock_bh(dev);
+       netif_addr_unlock_bh(sdata->dev);
 
        ieee80211_configure_filter(local);
 
@@ -432,7 +454,8 @@ static int ieee80211_stop(struct net_device *dev)
                WARN_ON(!list_empty(&sdata->u.ap.vlans));
        }
 
-       local->open_count--;
+       if (going_down)
+               local->open_count--;
 
        switch (sdata->vif.type) {
        case NL80211_IFTYPE_AP_VLAN:
@@ -504,7 +527,8 @@ static int ieee80211_stop(struct net_device *dev)
                 */
                ieee80211_free_keys(sdata);
 
-               drv_remove_interface(local, &sdata->vif);
+               if (going_down)
+                       drv_remove_interface(local, &sdata->vif);
        }
 
        sdata->bss = NULL;
@@ -540,6 +564,13 @@ static int ieee80211_stop(struct net_device *dev)
                }
        }
        spin_unlock_irqrestore(&local->queue_stop_reason_lock, flags);
+}
+
+static int ieee80211_stop(struct net_device *dev)
+{
+       struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
+
+       ieee80211_do_stop(sdata, true);
 
        return 0;
 }
@@ -857,9 +888,72 @@ static void ieee80211_setup_sdata(struct ieee80211_sub_if_data *sdata,
        ieee80211_debugfs_add_netdev(sdata);
 }
 
+static int ieee80211_runtime_change_iftype(struct ieee80211_sub_if_data *sdata,
+                                          enum nl80211_iftype type)
+{
+       struct ieee80211_local *local = sdata->local;
+       int ret, err;
+
+       ASSERT_RTNL();
+
+       if (!local->ops->change_interface)
+               return -EBUSY;
+
+       switch (sdata->vif.type) {
+       case NL80211_IFTYPE_AP:
+       case NL80211_IFTYPE_STATION:
+       case NL80211_IFTYPE_ADHOC:
+               /*
+                * Could maybe also all others here?
+                * Just not sure how that interacts
+                * with the RX/config path e.g. for
+                * mesh.
+                */
+               break;
+       default:
+               return -EBUSY;
+       }
+
+       switch (type) {
+       case NL80211_IFTYPE_AP:
+       case NL80211_IFTYPE_STATION:
+       case NL80211_IFTYPE_ADHOC:
+               /*
+                * Could probably support everything
+                * but WDS here (WDS do_open can fail
+                * under memory pressure, which this
+                * code isn't prepared to handle).
+                */
+               break;
+       default:
+               return -EBUSY;
+       }
+
+       ret = ieee80211_check_concurrent_iface(sdata, type);
+       if (ret)
+               return ret;
+
+       ieee80211_do_stop(sdata, false);
+
+       ieee80211_teardown_sdata(sdata->dev);
+
+       ret = drv_change_interface(local, sdata, type);
+       if (ret)
+               type = sdata->vif.type;
+
+       ieee80211_setup_sdata(sdata, type);
+
+       err = ieee80211_do_open(sdata->dev, false);
+       WARN(err, "type change: do_open returned %d", err);
+
+       return ret;
+}
+
 int ieee80211_if_change_type(struct ieee80211_sub_if_data *sdata,
                             enum nl80211_iftype type)
 {
+       int ret;
+
        ASSERT_RTNL();
 
        if (type == sdata->vif.type)
@@ -870,18 +964,15 @@ int ieee80211_if_change_type(struct ieee80211_sub_if_data *sdata,
            type == NL80211_IFTYPE_ADHOC)
                return -EOPNOTSUPP;
 
-       /*
-        * We could, here, on changes between IBSS/STA/MESH modes,
-        * invoke an MLME function instead that disassociates etc.
-        * and goes into the requested mode.
-        */
-
-       if (ieee80211_sdata_running(sdata))
-               return -EBUSY;
-
-       /* Purge and reset type-dependent state. */
-       ieee80211_teardown_sdata(sdata->dev);
-       ieee80211_setup_sdata(sdata, type);
+       if (ieee80211_sdata_running(sdata)) {
+               ret = ieee80211_runtime_change_iftype(sdata, type);
+               if (ret)
+                       return ret;
+       } else {
+               /* Purge and reset type-dependent state. */
+               ieee80211_teardown_sdata(sdata->dev);
+               ieee80211_setup_sdata(sdata, type);
+       }
 
        /* reset some values that shouldn't be kept across type changes */
        sdata->vif.bss_conf.basic_rates =