mac80211: add explicit monitor interface if needed
authorJohannes Berg <johannes.berg@intel.com>
Tue, 3 Apr 2012 12:35:57 +0000 (14:35 +0200)
committerJohn W. Linville <linville@tuxdriver.com>
Wed, 11 Apr 2012 20:23:49 +0000 (16:23 -0400)
The queue mapping redesign that I'm planning to do
will break pure injection unless we handle monitor
interfaces explicitly. One possible option would
be to have the driver tell mac80211 about monitor
mode queues etc., but that would duplicate the API
since we already need to have queue assignments
handled per virtual interface.

So in order to solve this, have a virtual monitor
interface that is added whenever all active vifs
are monitors. We could also use the state of one
of the monitor interfaces, but managing that would
be complicated, so allocate separate state.

Signed-off-by: Johannes Berg <johannes.berg@intel.com>
Signed-off-by: John W. Linville <linville@tuxdriver.com>
drivers/net/wireless/mac80211_hwsim.c
include/net/mac80211.h
net/mac80211/driver-ops.h
net/mac80211/ieee80211_i.h
net/mac80211/iface.c
net/mac80211/pm.c
net/mac80211/tx.c
net/mac80211/util.c

index a257df72782109c3098caca658fd4505c4d69dce..2d2bfce24fc17633b6d3be370f7211bb0f94a9cb 100644 (file)
@@ -1789,7 +1789,8 @@ static int __init init_mac80211_hwsim(void)
                            IEEE80211_HW_SIGNAL_DBM |
                            IEEE80211_HW_SUPPORTS_STATIC_SMPS |
                            IEEE80211_HW_SUPPORTS_DYNAMIC_SMPS |
-                           IEEE80211_HW_AMPDU_AGGREGATION;
+                           IEEE80211_HW_AMPDU_AGGREGATION |
+                           IEEE80211_HW_WANT_MONITOR_VIF;
 
                hw->wiphy->flags |= WIPHY_FLAG_SUPPORTS_TDLS |
                                    WIPHY_FLAG_HAS_REMAIN_ON_CHANNEL;
index c8ef45176b3e910556ebcce4d61e3adcc57d3922..2956a206235f4ce88d1b0d705bd79157a070727e 100644 (file)
@@ -1175,6 +1175,10 @@ enum sta_notify_cmd {
  * @IEEE80211_HW_SCAN_WHILE_IDLE: The device can do hw scan while
  *     being idle (i.e. mac80211 doesn't have to go idle-off during the
  *     the scan).
+ *
+ * @IEEE80211_HW_WANT_MONITOR_VIF: The driver would like to be informed of
+ *     a virtual monitor interface when monitor interfaces are the only
+ *     active interfaces.
  */
 enum ieee80211_hw_flags {
        IEEE80211_HW_HAS_RATE_CONTROL                   = 1<<0,
@@ -1191,7 +1195,7 @@ enum ieee80211_hw_flags {
        IEEE80211_HW_PS_NULLFUNC_STACK                  = 1<<11,
        IEEE80211_HW_SUPPORTS_DYNAMIC_PS                = 1<<12,
        IEEE80211_HW_MFP_CAPABLE                        = 1<<13,
-       /* reuse bit 14 */
+       IEEE80211_HW_WANT_MONITOR_VIF                   = 1<<14,
        IEEE80211_HW_SUPPORTS_STATIC_SMPS               = 1<<15,
        IEEE80211_HW_SUPPORTS_DYNAMIC_SMPS              = 1<<16,
        IEEE80211_HW_SUPPORTS_UAPSD                     = 1<<17,
index 8ad40f68f2c34e72e7828a6f32ce85346ec2b452..492c08c27c5fc02715673d00cf09aa89a4e660d6 100644 (file)
@@ -101,7 +101,8 @@ static inline int drv_add_interface(struct ieee80211_local *local,
        might_sleep();
 
        if (WARN_ON(sdata->vif.type == NL80211_IFTYPE_AP_VLAN ||
-                   sdata->vif.type == NL80211_IFTYPE_MONITOR))
+                   (sdata->vif.type == NL80211_IFTYPE_MONITOR &&
+                    !(local->hw.flags & IEEE80211_HW_WANT_MONITOR_VIF))))
                return -EINVAL;
 
        trace_drv_add_interface(local, sdata);
index 41f7295cd891b9db227797676e6552676f785b40..8ed074f7e6cdcca2ad79e24736d15085a929aece 100644 (file)
@@ -1100,6 +1100,9 @@ struct ieee80211_local {
        struct net_device napi_dev;
 
        struct napi_struct napi;
+
+       /* virtual monitor interface */
+       struct ieee80211_sub_if_data __rcu *monitor_sdata;
 };
 
 static inline struct ieee80211_sub_if_data *
index 56a38a3088d4ad4171432018a788b6d82fe2bc53..2b88cb278fc47f5a799e388400920fc4d4266ca3 100644 (file)
@@ -169,6 +169,59 @@ void ieee80211_adjust_monitor_flags(struct ieee80211_sub_if_data *sdata,
 #undef ADJUST
 }
 
+static int ieee80211_add_virtual_monitor(struct ieee80211_local *local)
+{
+       struct ieee80211_sub_if_data *sdata;
+       int ret;
+
+       if (!(local->hw.flags & IEEE80211_HW_WANT_MONITOR_VIF))
+               return 0;
+
+       if (local->monitor_sdata)
+               return 0;
+
+       sdata = kzalloc(sizeof(*sdata) + local->hw.vif_data_size, GFP_KERNEL);
+       if (!sdata)
+               return -ENOMEM;
+
+       /* set up data */
+       sdata->local = local;
+       sdata->vif.type = NL80211_IFTYPE_MONITOR;
+       snprintf(sdata->name, IFNAMSIZ, "%s-monitor",
+                wiphy_name(local->hw.wiphy));
+
+       ret = drv_add_interface(local, sdata);
+       if (WARN_ON(ret)) {
+               /* ok .. stupid driver, it asked for this! */
+               kfree(sdata);
+               return ret;
+       }
+
+       rcu_assign_pointer(local->monitor_sdata, sdata);
+
+       return 0;
+}
+
+static void ieee80211_del_virtual_monitor(struct ieee80211_local *local)
+{
+       struct ieee80211_sub_if_data *sdata;
+
+       if (!(local->hw.flags & IEEE80211_HW_WANT_MONITOR_VIF))
+               return;
+
+       sdata = rtnl_dereference(local->monitor_sdata);
+
+       if (!sdata)
+               return;
+
+       rcu_assign_pointer(local->monitor_sdata, NULL);
+       synchronize_net();
+
+       drv_remove_interface(local, sdata);
+
+       kfree(sdata);
+}
+
 /*
  * 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
@@ -266,6 +319,12 @@ static int ieee80211_do_open(struct net_device *dev, bool coming_up)
                        break;
                }
 
+               if (local->monitors == 0 && local->open_count == 0) {
+                       res = ieee80211_add_virtual_monitor(local);
+                       if (res)
+                               goto err_stop;
+               }
+
                /* must be before the call to ieee80211_configure_filter */
                local->monitors++;
                if (local->monitors == 1) {
@@ -280,6 +339,8 @@ static int ieee80211_do_open(struct net_device *dev, bool coming_up)
                break;
        default:
                if (coming_up) {
+                       ieee80211_del_virtual_monitor(local);
+
                        res = drv_add_interface(local, sdata);
                        if (res)
                                goto err_stop;
@@ -511,6 +572,7 @@ static void ieee80211_do_stop(struct ieee80211_sub_if_data *sdata,
                if (local->monitors == 0) {
                        local->hw.conf.flags &= ~IEEE80211_CONF_MONITOR;
                        hw_reconf_flags |= IEEE80211_CONF_CHANGE_MONITOR;
+                       ieee80211_del_virtual_monitor(local);
                }
 
                ieee80211_adjust_monitor_flags(sdata, -1);
@@ -584,6 +646,9 @@ static void ieee80211_do_stop(struct ieee80211_sub_if_data *sdata,
                }
        }
        spin_unlock_irqrestore(&local->queue_stop_reason_lock, flags);
+
+       if (local->monitors == local->open_count && local->monitors > 0)
+               ieee80211_add_virtual_monitor(local);
 }
 
 static int ieee80211_stop(struct net_device *dev)
index ef8eba1d736d125cffa30c5a8a1431f2c6fdb1b1..af1c4e26e9657ead3f75d9e622ae472170a15b07 100644 (file)
@@ -127,6 +127,10 @@ int __ieee80211_suspend(struct ieee80211_hw *hw, struct cfg80211_wowlan *wowlan)
                drv_remove_interface(local, sdata);
        }
 
+       sdata = rtnl_dereference(local->monitor_sdata);
+       if (sdata)
+               drv_remove_interface(local, sdata);
+
        /* stop hardware - this must stop RX */
        if (local->open_count)
                ieee80211_stop_device(local);
index 4109ec7999a372206fc217bc853917a05cbd6727..a8d0188ab4089dec3a9b6eb882be5a2926372f40 100644 (file)
@@ -1283,8 +1283,11 @@ static bool __ieee80211_tx(struct ieee80211_local *local,
 
        switch (sdata->vif.type) {
        case NL80211_IFTYPE_MONITOR:
-               sdata = NULL;
-               vif = NULL;
+               sdata = rcu_dereference(local->monitor_sdata);
+               if (sdata)
+                       vif = &sdata->vif;
+               else
+                       vif = NULL;
                break;
        case NL80211_IFTYPE_AP_VLAN:
                sdata = container_of(sdata->bss,
index a18b693042b2b5a4ce8f1a07d628b0ac83534cfa..9e8f4b892555f71036191f2e9fad7404cb51e8c0 100644 (file)
@@ -1223,6 +1223,16 @@ int ieee80211_reconfig(struct ieee80211_local *local)
                                   IEEE80211_TPT_LEDTRIG_FL_RADIO, 0);
 
        /* add interfaces */
+       sdata = rtnl_dereference(local->monitor_sdata);
+       if (sdata) {
+               res = drv_add_interface(local, sdata);
+               if (WARN_ON(res)) {
+                       rcu_assign_pointer(local->monitor_sdata, NULL);
+                       synchronize_net();
+                       kfree(sdata);
+               }
+       }
+
        list_for_each_entry(sdata, &local->interfaces, list) {
                if (sdata->vif.type != NL80211_IFTYPE_AP_VLAN &&
                    sdata->vif.type != NL80211_IFTYPE_MONITOR &&