]> Pileus Git - ~andy/linux/blobdiff - net/mac80211/chan.c
Merge branch 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/sage/ceph...
[~andy/linux] / net / mac80211 / chan.c
index 03ba6b5c5373b373d47956518fafeaf1338b2ad0..0c1ecfdf9a128b05f76e545d3e0b95de50123176 100644 (file)
@@ -9,6 +9,146 @@
 #include "ieee80211_i.h"
 #include "driver-ops.h"
 
+static enum nl80211_chan_width ieee80211_get_sta_bw(struct ieee80211_sta *sta)
+{
+       switch (sta->bandwidth) {
+       case IEEE80211_STA_RX_BW_20:
+               if (sta->ht_cap.ht_supported)
+                       return NL80211_CHAN_WIDTH_20;
+               else
+                       return NL80211_CHAN_WIDTH_20_NOHT;
+       case IEEE80211_STA_RX_BW_40:
+               return NL80211_CHAN_WIDTH_40;
+       case IEEE80211_STA_RX_BW_80:
+               return NL80211_CHAN_WIDTH_80;
+       case IEEE80211_STA_RX_BW_160:
+               /*
+                * This applied for both 160 and 80+80. since we use
+                * the returned value to consider degradation of
+                * ctx->conf.min_def, we have to make sure to take
+                * the bigger one (NL80211_CHAN_WIDTH_160).
+                * Otherwise we might try degrading even when not
+                * needed, as the max required sta_bw returned (80+80)
+                * might be smaller than the configured bw (160).
+                */
+               return NL80211_CHAN_WIDTH_160;
+       default:
+               WARN_ON(1);
+               return NL80211_CHAN_WIDTH_20;
+       }
+}
+
+static enum nl80211_chan_width
+ieee80211_get_max_required_bw(struct ieee80211_sub_if_data *sdata)
+{
+       enum nl80211_chan_width max_bw = NL80211_CHAN_WIDTH_20_NOHT;
+       struct sta_info *sta;
+
+       rcu_read_lock();
+       list_for_each_entry_rcu(sta, &sdata->local->sta_list, list) {
+               if (sdata != sta->sdata &&
+                   !(sta->sdata->bss && sta->sdata->bss == sdata->bss))
+                       continue;
+
+               if (!sta->uploaded)
+                       continue;
+
+               max_bw = max(max_bw, ieee80211_get_sta_bw(&sta->sta));
+       }
+       rcu_read_unlock();
+
+       return max_bw;
+}
+
+static enum nl80211_chan_width
+ieee80211_get_chanctx_max_required_bw(struct ieee80211_local *local,
+                                     struct ieee80211_chanctx_conf *conf)
+{
+       struct ieee80211_sub_if_data *sdata;
+       enum nl80211_chan_width max_bw = NL80211_CHAN_WIDTH_20_NOHT;
+
+       rcu_read_lock();
+       list_for_each_entry_rcu(sdata, &local->interfaces, list) {
+               struct ieee80211_vif *vif = &sdata->vif;
+               enum nl80211_chan_width width = NL80211_CHAN_WIDTH_20_NOHT;
+
+               if (!ieee80211_sdata_running(sdata))
+                       continue;
+
+               if (rcu_access_pointer(sdata->vif.chanctx_conf) != conf)
+                       continue;
+
+               switch (vif->type) {
+               case NL80211_IFTYPE_AP:
+               case NL80211_IFTYPE_AP_VLAN:
+                       width = ieee80211_get_max_required_bw(sdata);
+                       break;
+               case NL80211_IFTYPE_P2P_DEVICE:
+                       continue;
+               case NL80211_IFTYPE_STATION:
+               case NL80211_IFTYPE_ADHOC:
+               case NL80211_IFTYPE_WDS:
+               case NL80211_IFTYPE_MESH_POINT:
+                       width = vif->bss_conf.chandef.width;
+                       break;
+               case NL80211_IFTYPE_UNSPECIFIED:
+               case NUM_NL80211_IFTYPES:
+               case NL80211_IFTYPE_MONITOR:
+               case NL80211_IFTYPE_P2P_CLIENT:
+               case NL80211_IFTYPE_P2P_GO:
+                       WARN_ON_ONCE(1);
+               }
+               max_bw = max(max_bw, width);
+       }
+
+       /* use the configured bandwidth in case of monitor interface */
+       sdata = rcu_dereference(local->monitor_sdata);
+       if (sdata && rcu_access_pointer(sdata->vif.chanctx_conf) == conf)
+               max_bw = max(max_bw, conf->def.width);
+
+       rcu_read_unlock();
+
+       return max_bw;
+}
+
+/*
+ * recalc the min required chan width of the channel context, which is
+ * the max of min required widths of all the interfaces bound to this
+ * channel context.
+ */
+void ieee80211_recalc_chanctx_min_def(struct ieee80211_local *local,
+                                     struct ieee80211_chanctx *ctx)
+{
+       enum nl80211_chan_width max_bw;
+       struct cfg80211_chan_def min_def;
+
+       lockdep_assert_held(&local->chanctx_mtx);
+
+       /* don't optimize 5MHz, 10MHz, and radar_enabled confs */
+       if (ctx->conf.def.width == NL80211_CHAN_WIDTH_5 ||
+           ctx->conf.def.width == NL80211_CHAN_WIDTH_10 ||
+           ctx->conf.radar_enabled) {
+               ctx->conf.min_def = ctx->conf.def;
+               return;
+       }
+
+       max_bw = ieee80211_get_chanctx_max_required_bw(local, &ctx->conf);
+
+       /* downgrade chandef up to max_bw */
+       min_def = ctx->conf.def;
+       while (min_def.width > max_bw)
+               ieee80211_chandef_downgrade(&min_def);
+
+       if (cfg80211_chandef_identical(&ctx->conf.min_def, &min_def))
+               return;
+
+       ctx->conf.min_def = min_def;
+       if (!ctx->driver_present)
+               return;
+
+       drv_change_chanctx(local, ctx, IEEE80211_CHANCTX_CHANGE_MIN_WIDTH);
+}
+
 static void ieee80211_change_chanctx(struct ieee80211_local *local,
                                     struct ieee80211_chanctx *ctx,
                                     const struct cfg80211_chan_def *chandef)
@@ -20,6 +160,7 @@ static void ieee80211_change_chanctx(struct ieee80211_local *local,
 
        ctx->conf.def = *chandef;
        drv_change_chanctx(local, ctx, IEEE80211_CHANCTX_CHANGE_WIDTH);
+       ieee80211_recalc_chanctx_min_def(local, ctx);
 
        if (!local->use_chanctx) {
                local->_oper_chandef = *chandef;
@@ -93,11 +234,12 @@ ieee80211_new_chanctx(struct ieee80211_local *local,
        ctx->conf.rx_chains_dynamic = 1;
        ctx->mode = mode;
        ctx->conf.radar_enabled = ieee80211_is_radar_required(local);
+       ieee80211_recalc_chanctx_min_def(local, ctx);
        if (!local->use_chanctx)
                local->hw.conf.radar_enabled = ctx->conf.radar_enabled;
 
-       /* acquire mutex to prevent idle from changing */
-       mutex_lock(&local->mtx);
+       /* we hold the mutex to prevent idle from changing */
+       lockdep_assert_held(&local->mtx);
        /* turn idle off *before* setting channel -- some drivers need that */
        changed = ieee80211_idle_off(local);
        if (changed)
@@ -110,19 +252,14 @@ ieee80211_new_chanctx(struct ieee80211_local *local,
                err = drv_add_chanctx(local, ctx);
                if (err) {
                        kfree(ctx);
-                       ctx = ERR_PTR(err);
-
                        ieee80211_recalc_idle(local);
-                       goto out;
+                       return ERR_PTR(err);
                }
        }
 
        /* and keep the mutex held until the new chanctx is on the list */
        list_add_rcu(&ctx->list, &local->chanctx_list);
 
- out:
-       mutex_unlock(&local->mtx);
-
        return ctx;
 }
 
@@ -158,9 +295,7 @@ static void ieee80211_free_chanctx(struct ieee80211_local *local,
        /* throw a warning if this wasn't the only channel context. */
        WARN_ON(check_single_channel && !list_empty(&local->chanctx_list));
 
-       mutex_lock(&local->mtx);
        ieee80211_recalc_idle(local);
-       mutex_unlock(&local->mtx);
 }
 
 static int ieee80211_assign_vif_chanctx(struct ieee80211_sub_if_data *sdata,
@@ -179,6 +314,7 @@ static int ieee80211_assign_vif_chanctx(struct ieee80211_sub_if_data *sdata,
        ctx->refcount++;
 
        ieee80211_recalc_txpower(sdata);
+       ieee80211_recalc_chanctx_min_def(local, ctx);
        sdata->vif.bss_conf.idle = false;
 
        if (sdata->vif.type != NL80211_IFTYPE_P2P_DEVICE &&
@@ -221,6 +357,31 @@ static void ieee80211_recalc_chanctx_chantype(struct ieee80211_local *local,
        ieee80211_change_chanctx(local, ctx, compat);
 }
 
+static void ieee80211_recalc_radar_chanctx(struct ieee80211_local *local,
+                                          struct ieee80211_chanctx *chanctx)
+{
+       bool radar_enabled;
+
+       lockdep_assert_held(&local->chanctx_mtx);
+       /* for setting local->radar_detect_enabled */
+       lockdep_assert_held(&local->mtx);
+
+       radar_enabled = ieee80211_is_radar_required(local);
+
+       if (radar_enabled == chanctx->conf.radar_enabled)
+               return;
+
+       chanctx->conf.radar_enabled = radar_enabled;
+       local->radar_detect_enabled = chanctx->conf.radar_enabled;
+
+       if (!local->use_chanctx) {
+               local->hw.conf.radar_enabled = chanctx->conf.radar_enabled;
+               ieee80211_hw_config(local, IEEE80211_CONF_CHANGE_CHANNEL);
+       }
+
+       drv_change_chanctx(local, chanctx, IEEE80211_CHANCTX_CHANGE_RADAR);
+}
+
 static void ieee80211_unassign_vif_chanctx(struct ieee80211_sub_if_data *sdata,
                                           struct ieee80211_chanctx *ctx)
 {
@@ -243,6 +404,7 @@ static void ieee80211_unassign_vif_chanctx(struct ieee80211_sub_if_data *sdata,
                ieee80211_recalc_chanctx_chantype(sdata->local, ctx);
                ieee80211_recalc_smps_chanctx(local, ctx);
                ieee80211_recalc_radar_chanctx(local, ctx);
+               ieee80211_recalc_chanctx_min_def(local, ctx);
        }
 }
 
@@ -266,29 +428,6 @@ static void __ieee80211_vif_release_channel(struct ieee80211_sub_if_data *sdata)
                ieee80211_free_chanctx(local, ctx);
 }
 
-void ieee80211_recalc_radar_chanctx(struct ieee80211_local *local,
-                                   struct ieee80211_chanctx *chanctx)
-{
-       bool radar_enabled;
-
-       lockdep_assert_held(&local->chanctx_mtx);
-
-       radar_enabled = ieee80211_is_radar_required(local);
-
-       if (radar_enabled == chanctx->conf.radar_enabled)
-               return;
-
-       chanctx->conf.radar_enabled = radar_enabled;
-       local->radar_detect_enabled = chanctx->conf.radar_enabled;
-
-       if (!local->use_chanctx) {
-               local->hw.conf.radar_enabled = chanctx->conf.radar_enabled;
-               ieee80211_hw_config(local, IEEE80211_CONF_CHANGE_CHANNEL);
-       }
-
-       drv_change_chanctx(local, chanctx, IEEE80211_CHANCTX_CHANGE_RADAR);
-}
-
 void ieee80211_recalc_smps_chanctx(struct ieee80211_local *local,
                                   struct ieee80211_chanctx *chanctx)
 {
@@ -380,6 +519,8 @@ int ieee80211_vif_use_channel(struct ieee80211_sub_if_data *sdata,
        struct ieee80211_chanctx *ctx;
        int ret;
 
+       lockdep_assert_held(&local->mtx);
+
        WARN_ON(sdata->dev && netif_carrier_ok(sdata->dev));
 
        mutex_lock(&local->chanctx_mtx);
@@ -411,15 +552,17 @@ int ieee80211_vif_use_channel(struct ieee80211_sub_if_data *sdata,
 }
 
 int ieee80211_vif_change_channel(struct ieee80211_sub_if_data *sdata,
-                                const struct cfg80211_chan_def *chandef,
                                 u32 *changed)
 {
        struct ieee80211_local *local = sdata->local;
        struct ieee80211_chanctx_conf *conf;
        struct ieee80211_chanctx *ctx;
+       const struct cfg80211_chan_def *chandef = &sdata->csa_chandef;
        int ret;
        u32 chanctx_changed = 0;
 
+       lockdep_assert_held(&local->mtx);
+
        /* should never be called if not performing a channel switch. */
        if (WARN_ON(!sdata->vif.csa_active))
                return -EINVAL;
@@ -456,6 +599,7 @@ int ieee80211_vif_change_channel(struct ieee80211_sub_if_data *sdata,
        ieee80211_recalc_chanctx_chantype(local, ctx);
        ieee80211_recalc_smps_chanctx(local, ctx);
        ieee80211_recalc_radar_chanctx(local, ctx);
+       ieee80211_recalc_chanctx_min_def(local, ctx);
 
        ret = 0;
  out:
@@ -516,6 +660,8 @@ void ieee80211_vif_release_channel(struct ieee80211_sub_if_data *sdata)
 {
        WARN_ON(sdata->dev && netif_carrier_ok(sdata->dev));
 
+       lockdep_assert_held(&sdata->local->mtx);
+
        mutex_lock(&sdata->local->chanctx_mtx);
        __ieee80211_vif_release_channel(sdata);
        mutex_unlock(&sdata->local->chanctx_mtx);