mac80211: handle TX power per virtual interface
authorJohannes Berg <johannes.berg@intel.com>
Wed, 24 Oct 2012 08:59:25 +0000 (10:59 +0200)
committerJohannes Berg <johannes.berg@intel.com>
Tue, 30 Oct 2012 08:11:34 +0000 (09:11 +0100)
Even before channel contexts/multi-channel, having a
single global TX power limit was already problematic,
in particular if two managed interfaces connected to
two APs with different power constraints. The channel
context introduction completely broke this though and
in fact I had disabled TX power configuration there
for drivers using channel contexts.

Change everything to track TX power per interface so
that different user settings and different channel
maxima are treated correctly. Also continue tracking
the global TX power though for compatibility with
applications that attempt to configure the wiphy's
TX power globally.

Signed-off-by: Johannes Berg <johannes.berg@intel.com>
include/net/mac80211.h
net/mac80211/cfg.c
net/mac80211/chan.c
net/mac80211/debugfs_netdev.c
net/mac80211/ieee80211_i.h
net/mac80211/iface.c
net/mac80211/main.c
net/mac80211/mlme.c
net/mac80211/trace.h
net/mac80211/util.c

index 5f5327452c9a03122af32b2bb75c67c978b23d8a..dfa589b721b6ea21082f2143dbeb2729c92fa7be 100644 (file)
@@ -207,6 +207,7 @@ struct ieee80211_chanctx_conf {
  * @BSS_CHANGED_SSID: SSID changed for this BSS (AP mode)
  * @BSS_CHANGED_AP_PROBE_RESP: Probe Response changed for this BSS (AP mode)
  * @BSS_CHANGED_PS: PS changed for this BSS (STA mode)
+ * @BSS_CHANGED_TXPOWER: TX power setting changed for this interface
  */
 enum ieee80211_bss_change {
        BSS_CHANGED_ASSOC               = 1<<0,
@@ -227,6 +228,7 @@ enum ieee80211_bss_change {
        BSS_CHANGED_SSID                = 1<<15,
        BSS_CHANGED_AP_PROBE_RESP       = 1<<16,
        BSS_CHANGED_PS                  = 1<<17,
+       BSS_CHANGED_TXPOWER             = 1<<18,
 
        /* when adding here, make sure to change ieee80211_reconfig */
 };
@@ -309,6 +311,7 @@ enum ieee80211_rssi_event {
  * @ssid: The SSID of the current vif. Only valid in AP-mode.
  * @ssid_len: Length of SSID given in @ssid.
  * @hidden_ssid: The SSID of the current vif is hidden. Only valid in AP-mode.
+ * @txpower: TX power in dBm
  */
 struct ieee80211_bss_conf {
        const u8 *bssid;
@@ -341,6 +344,7 @@ struct ieee80211_bss_conf {
        u8 ssid[IEEE80211_MAX_SSID_LEN];
        size_t ssid_len;
        bool hidden_ssid;
+       int txpower;
 };
 
 /**
@@ -884,7 +888,8 @@ enum ieee80211_smps_mode {
  *     powersave documentation below. This variable is valid only when
  *     the CONF_PS flag is set.
  *
- * @power_level: requested transmit power (in dBm)
+ * @power_level: requested transmit power (in dBm), backward compatibility
+ *     value only that is set to the minimum of all interfaces
  *
  * @channel: the channel to tune to
  * @channel_type: the channel (HT) type
index a352e4d22dd934f7e09cbea251bb063fe82d6045..986e9a139d42ee17cd3d6cb013442110ad7252e8 100644 (file)
@@ -1996,33 +1996,46 @@ static int ieee80211_set_tx_power(struct wiphy *wiphy,
                                  enum nl80211_tx_power_setting type, int mbm)
 {
        struct ieee80211_local *local = wiphy_priv(wiphy);
-       struct ieee80211_channel *chan = local->_oper_channel;
-       u32 changes = 0;
+       struct ieee80211_sub_if_data *sdata;
 
-       /* FIXME */
-       if (local->use_chanctx)
-               return -EOPNOTSUPP;
+       if (wdev) {
+               sdata = IEEE80211_WDEV_TO_SUB_IF(wdev);
+
+               switch (type) {
+               case NL80211_TX_POWER_AUTOMATIC:
+                       sdata->user_power_level = IEEE80211_UNSET_POWER_LEVEL;
+                       break;
+               case NL80211_TX_POWER_LIMITED:
+               case NL80211_TX_POWER_FIXED:
+                       if (mbm < 0 || (mbm % 100))
+                               return -EOPNOTSUPP;
+                       sdata->user_power_level = MBM_TO_DBM(mbm);
+                       break;
+               }
+
+               ieee80211_recalc_txpower(sdata);
+
+               return 0;
+       }
 
        switch (type) {
        case NL80211_TX_POWER_AUTOMATIC:
-               local->user_power_level = -1;
+               local->user_power_level = IEEE80211_UNSET_POWER_LEVEL;
                break;
        case NL80211_TX_POWER_LIMITED:
-               if (mbm < 0 || (mbm % 100))
-                       return -EOPNOTSUPP;
-               local->user_power_level = MBM_TO_DBM(mbm);
-               break;
        case NL80211_TX_POWER_FIXED:
                if (mbm < 0 || (mbm % 100))
                        return -EOPNOTSUPP;
-               /* TODO: move to cfg80211 when it knows the channel */
-               if (MBM_TO_DBM(mbm) > chan->max_power)
-                       return -EINVAL;
                local->user_power_level = MBM_TO_DBM(mbm);
                break;
        }
 
-       ieee80211_hw_config(local, changes);
+       mutex_lock(&local->iflist_mtx);
+       list_for_each_entry(sdata, &local->interfaces, list)
+               sdata->user_power_level = local->user_power_level;
+       list_for_each_entry(sdata, &local->interfaces, list)
+               ieee80211_recalc_txpower(sdata);
+       mutex_unlock(&local->iflist_mtx);
 
        return 0;
 }
@@ -2032,8 +2045,12 @@ static int ieee80211_get_tx_power(struct wiphy *wiphy,
                                  int *dbm)
 {
        struct ieee80211_local *local = wiphy_priv(wiphy);
+       struct ieee80211_sub_if_data *sdata = IEEE80211_WDEV_TO_SUB_IF(wdev);
 
-       *dbm = local->hw.conf.power_level;
+       if (!local->use_chanctx)
+               *dbm = local->hw.conf.power_level;
+       else
+               *dbm = sdata->vif.bss_conf.txpower;
 
        return 0;
 }
index f84b86028a9cd50bc95c10ec3410c4250c190eb1..a2b06d40aebf53be340398ef7e22319b27731cb2 100644 (file)
@@ -173,6 +173,8 @@ static int ieee80211_assign_vif_chanctx(struct ieee80211_sub_if_data *sdata,
        rcu_assign_pointer(sdata->vif.chanctx_conf, &ctx->conf);
        ctx->refcount++;
 
+       ieee80211_recalc_txpower(sdata);
+
        return 0;
 }
 
index 3393ad5b8ab1875c5252bfe921b6e3c28680a524..07c5721323ca6345649d678ff2f2f8c2a3f8ad36 100644 (file)
@@ -168,6 +168,9 @@ IEEE80211_IF_FILE(rc_rateidx_mcs_mask_5ghz,
 IEEE80211_IF_FILE(flags, flags, HEX);
 IEEE80211_IF_FILE(state, state, LHEX);
 IEEE80211_IF_FILE(channel_type, vif.bss_conf.channel_type, DEC);
+IEEE80211_IF_FILE(txpower, vif.bss_conf.txpower, DEC);
+IEEE80211_IF_FILE(ap_power_level, ap_power_level, DEC);
+IEEE80211_IF_FILE(user_power_level, user_power_level, DEC);
 
 /* STA attributes */
 IEEE80211_IF_FILE(bssid, u.mgd.bssid, MAC);
@@ -632,6 +635,9 @@ static void add_files(struct ieee80211_sub_if_data *sdata)
        DEBUGFS_ADD(flags);
        DEBUGFS_ADD(state);
        DEBUGFS_ADD(channel_type);
+       DEBUGFS_ADD(txpower);
+       DEBUGFS_ADD(user_power_level);
+       DEBUGFS_ADD(ap_power_level);
 
        if (sdata->vif.type != NL80211_IFTYPE_MONITOR)
                add_common_files(sdata);
index 3026519b236afb0f7cd9c32907e790d18d93e018..a1f7c139308e67edcd84a342fc5c34332204be8e 100644 (file)
@@ -56,6 +56,9 @@ struct ieee80211_local;
 #define TU_TO_JIFFIES(x)       (usecs_to_jiffies((x) * 1024))
 #define TU_TO_EXP_TIME(x)      (jiffies + TU_TO_JIFFIES(x))
 
+/* power level hasn't been configured (or set to automatic) */
+#define IEEE80211_UNSET_POWER_LEVEL    INT_MIN
+
 /*
  * Some APs experience problems when working with U-APSD. Decrease the
  * probability of that happening by using legacy mode for all ACs but VO.
@@ -743,6 +746,9 @@ struct ieee80211_sub_if_data {
        u8 needed_rx_chains;
        enum ieee80211_smps_mode smps_mode;
 
+       int user_power_level; /* in dBm */
+       int ap_power_level; /* in dBm */
+
        /*
         * AP this belongs to: self in AP mode and
         * corresponding AP in VLAN mode, NULL for
@@ -1117,8 +1123,7 @@ struct ieee80211_local {
        int dynamic_ps_user_timeout;
        bool disable_dynamic_ps;
 
-       int user_power_level; /* in dBm */
-       int ap_power_level; /* in dBm */
+       int user_power_level; /* in dBm, for all interfaces */
 
        enum ieee80211_smps_mode smps_mode;
 
@@ -1365,6 +1370,9 @@ void ieee80211_adjust_monitor_flags(struct ieee80211_sub_if_data *sdata,
 int ieee80211_do_open(struct wireless_dev *wdev, bool coming_up);
 void ieee80211_sdata_stop(struct ieee80211_sub_if_data *sdata);
 
+bool __ieee80211_recalc_txpower(struct ieee80211_sub_if_data *sdata);
+void ieee80211_recalc_txpower(struct ieee80211_sub_if_data *sdata);
+
 static inline bool ieee80211_sdata_running(struct ieee80211_sub_if_data *sdata)
 {
        return test_bit(SDATA_STATE_RUNNING, &sdata->state);
index 1a6fe135f201a1dc70e7a89b970289ce0fb6a8d2..80ce90b29d9d77d59be4b22361bd22ef16911aa3 100644 (file)
  * by either the RTNL, the iflist_mtx or RCU.
  */
 
+bool __ieee80211_recalc_txpower(struct ieee80211_sub_if_data *sdata)
+{
+       struct ieee80211_chanctx_conf *chanctx_conf;
+       int power;
+
+       rcu_read_lock();
+       chanctx_conf = rcu_dereference(sdata->vif.chanctx_conf);
+       if (!chanctx_conf) {
+               rcu_read_unlock();
+               return false;
+       }
+
+       power = chanctx_conf->channel->max_power;
+       rcu_read_unlock();
+
+       if (sdata->user_power_level != IEEE80211_UNSET_POWER_LEVEL)
+               power = min(power, sdata->user_power_level);
+
+       if (sdata->ap_power_level != IEEE80211_UNSET_POWER_LEVEL)
+               power = min(power, sdata->ap_power_level);
+
+       if (power != sdata->vif.bss_conf.txpower) {
+               sdata->vif.bss_conf.txpower = power;
+               ieee80211_hw_config(sdata->local, 0);
+               return true;
+       }
+
+       return false;
+}
+
+void ieee80211_recalc_txpower(struct ieee80211_sub_if_data *sdata)
+{
+       if (__ieee80211_recalc_txpower(sdata))
+               ieee80211_bss_info_change_notify(sdata, BSS_CHANGED_TXPOWER);
+}
 
 static u32 ieee80211_idle_off(struct ieee80211_local *local,
                              const char *reason)
@@ -1510,6 +1545,9 @@ int ieee80211_if_add(struct ieee80211_local *local, const char *name,
 
        ieee80211_set_default_queues(sdata);
 
+       sdata->ap_power_level = IEEE80211_UNSET_POWER_LEVEL;
+       sdata->user_power_level = local->user_power_level;
+
        /* setup type-dependent data */
        ieee80211_setup_sdata(sdata, type);
 
index fd8345c200512ded20e1bf47e960599bbc042bc6..70e87600cacc196ca870faab86530096c8a65efd 100644 (file)
@@ -95,11 +95,13 @@ static void ieee80211_reconfig_filter(struct work_struct *work)
 
 static u32 ieee80211_hw_conf_chan(struct ieee80211_local *local)
 {
+       struct ieee80211_sub_if_data *sdata;
        struct ieee80211_channel *chan;
        u32 changed = 0;
        int power;
        enum nl80211_channel_type channel_type;
        u32 offchannel_flag;
+       bool scanning = false;
 
        offchannel_flag = local->hw.conf.flags & IEEE80211_CONF_OFFCHANNEL;
        if (local->scan_channel) {
@@ -146,16 +148,18 @@ static u32 ieee80211_hw_conf_chan(struct ieee80211_local *local)
                changed |= IEEE80211_CONF_CHANGE_SMPS;
        }
 
-       if (test_bit(SCAN_SW_SCANNING, &local->scanning) ||
-           test_bit(SCAN_ONCHANNEL_SCANNING, &local->scanning) ||
-           test_bit(SCAN_HW_SCANNING, &local->scanning) ||
-           !local->ap_power_level)
-               power = chan->max_power;
-       else
-               power = min(chan->max_power, local->ap_power_level);
+       scanning = test_bit(SCAN_SW_SCANNING, &local->scanning) ||
+                  test_bit(SCAN_ONCHANNEL_SCANNING, &local->scanning) ||
+                  test_bit(SCAN_HW_SCANNING, &local->scanning);
+       power = chan->max_power;
 
-       if (local->user_power_level >= 0)
-               power = min(power, local->user_power_level);
+       rcu_read_lock();
+       list_for_each_entry_rcu(sdata, &local->interfaces, list) {
+               if (!rcu_access_pointer(sdata->vif.chanctx_conf))
+                       continue;
+               power = min(power, sdata->vif.bss_conf.txpower);
+       }
+       rcu_read_unlock();
 
        if (local->hw.conf.power_level != power) {
                changed |= IEEE80211_CONF_CHANGE_POWER;
@@ -600,7 +604,8 @@ struct ieee80211_hw *ieee80211_alloc_hw(size_t priv_data_len,
 
        wiphy->features |= NL80211_FEATURE_SK_TX_STATUS |
                           NL80211_FEATURE_SAE |
-                          NL80211_FEATURE_HT_IBSS;
+                          NL80211_FEATURE_HT_IBSS |
+                          NL80211_FEATURE_VIF_TXPOWER;
 
        if (!ops->hw_scan)
                wiphy->features |= NL80211_FEATURE_LOW_PRIORITY_SCAN |
@@ -633,7 +638,7 @@ struct ieee80211_hw *ieee80211_alloc_hw(size_t priv_data_len,
        local->hw.radiotap_mcs_details = IEEE80211_RADIOTAP_MCS_HAVE_MCS |
                                         IEEE80211_RADIOTAP_MCS_HAVE_GI |
                                         IEEE80211_RADIOTAP_MCS_HAVE_BW;
-       local->user_power_level = -1;
+       local->user_power_level = IEEE80211_UNSET_POWER_LEVEL;
        wiphy->ht_capa_mod_mask = &mac80211_ht_capa_mod_mask;
 
        INIT_LIST_HEAD(&local->interfaces);
index 1d1fdf0791f06e96ef56ee039f62c070d45b0371..d29762fdd887ae69e0f3b11886918f9daa834c23 100644 (file)
@@ -820,10 +820,10 @@ void ieee80211_sta_process_chanswitch(struct ieee80211_sub_if_data *sdata,
                                         cbss->beacon_interval));
 }
 
-static void ieee80211_handle_pwr_constr(struct ieee80211_sub_if_data *sdata,
-                                       struct ieee80211_channel *channel,
-                                       const u8 *country_ie, u8 country_ie_len,
-                                       const u8 *pwr_constr_elem)
+static u32 ieee80211_handle_pwr_constr(struct ieee80211_sub_if_data *sdata,
+                                      struct ieee80211_channel *channel,
+                                      const u8 *country_ie, u8 country_ie_len,
+                                      const u8 *pwr_constr_elem)
 {
        struct ieee80211_country_ie_triplet *triplet;
        int chan = ieee80211_frequency_to_channel(channel->center_freq);
@@ -832,7 +832,7 @@ static void ieee80211_handle_pwr_constr(struct ieee80211_sub_if_data *sdata,
 
        /* Invalid IE */
        if (country_ie_len % 2 || country_ie_len < IEEE80211_COUNTRY_IE_MIN_LEN)
-               return;
+               return 0;
 
        triplet = (void *)(country_ie + 3);
        country_ie_len -= 3;
@@ -873,19 +873,21 @@ static void ieee80211_handle_pwr_constr(struct ieee80211_sub_if_data *sdata,
        }
 
        if (!have_chan_pwr)
-               return;
+               return 0;
 
        new_ap_level = max_t(int, 0, chan_pwr - *pwr_constr_elem);
 
-       if (sdata->local->ap_power_level == new_ap_level)
-               return;
+       if (sdata->ap_power_level == new_ap_level)
+               return 0;
 
        sdata_info(sdata,
                   "Limiting TX power to %d (%d - %d) dBm as advertised by %pM\n",
                   new_ap_level, chan_pwr, *pwr_constr_elem,
                   sdata->u.mgd.bssid);
-       sdata->local->ap_power_level = new_ap_level;
-       ieee80211_hw_config(sdata->local, 0);
+       sdata->ap_power_level = new_ap_level;
+       if (__ieee80211_recalc_txpower(sdata))
+               return BSS_CHANGED_TXPOWER;
+       return 0;
 }
 
 void ieee80211_enable_dyn_ps(struct ieee80211_vif *vif)
@@ -1489,7 +1491,7 @@ static void ieee80211_set_disassoc(struct ieee80211_sub_if_data *sdata,
        memset(&ifmgd->ht_capa, 0, sizeof(ifmgd->ht_capa));
        memset(&ifmgd->ht_capa_mask, 0, sizeof(ifmgd->ht_capa_mask));
 
-       local->ap_power_level = 0;
+       sdata->ap_power_level = IEEE80211_UNSET_POWER_LEVEL;
 
        del_timer_sync(&local->dynamic_ps_timer);
        cancel_work_sync(&local->dynamic_ps_enable_work);
@@ -2623,10 +2625,10 @@ static void ieee80211_rx_mgmt_beacon(struct ieee80211_sub_if_data *sdata,
        if (elems.country_elem && elems.pwr_constr_elem &&
            mgmt->u.probe_resp.capab_info &
                                cpu_to_le16(WLAN_CAPABILITY_SPECTRUM_MGMT))
-               ieee80211_handle_pwr_constr(sdata, chan,
-                                           elems.country_elem,
-                                           elems.country_elem_len,
-                                           elems.pwr_constr_elem);
+               changed |= ieee80211_handle_pwr_constr(sdata, chan,
+                                                      elems.country_elem,
+                                                      elems.country_elem_len,
+                                                      elems.pwr_constr_elem);
 
        ieee80211_bss_info_change_notify(sdata, changed);
 }
index 5e74e77cba9a0caf7242a62d3fd139206846fc68..eeebbd9cb8881479c5064a433a4688ec73db6220 100644 (file)
@@ -342,6 +342,7 @@ TRACE_EVENT(drv_bss_info_changed,
                __field(bool, ps);
                __dynamic_array(u8, ssid, info->ssid_len);
                __field(bool, hidden_ssid);
+               __field(int, txpower)
        ),
 
        TP_fast_assign(
@@ -376,6 +377,7 @@ TRACE_EVENT(drv_bss_info_changed,
                __entry->ps = info->ps;
                memcpy(__get_dynamic_array(ssid), info->ssid, info->ssid_len);
                __entry->hidden_ssid = info->hidden_ssid;
+               __entry->txpower = info->txpower;
        ),
 
        TP_printk(
index 6636d39623171a3df514dc1a21d9476b0f392b71..1a511afbdf074fc799db08e59ce284aabe1050f8 100644 (file)
@@ -1478,7 +1478,8 @@ int ieee80211_reconfig(struct ieee80211_local *local)
                          BSS_CHANGED_BSSID |
                          BSS_CHANGED_CQM |
                          BSS_CHANGED_QOS |
-                         BSS_CHANGED_IDLE;
+                         BSS_CHANGED_IDLE |
+                         BSS_CHANGED_TXPOWER;
 
                switch (sdata->vif.type) {
                case NL80211_IFTYPE_STATION: