mac80211: Add airtime accounting and scheduling to TXQs
authorToke Høiland-Jørgensen <toke@toke.dk>
Wed, 19 Dec 2018 01:02:08 +0000 (17:02 -0800)
committerJohannes Berg <johannes.berg@intel.com>
Sat, 19 Jan 2019 08:31:43 +0000 (09:31 +0100)
This adds airtime accounting and scheduling to the mac80211 TXQ
scheduler. A new callback, ieee80211_sta_register_airtime(), is added
that drivers can call to report airtime usage for stations.

When airtime information is present, mac80211 will schedule TXQs
(through ieee80211_next_txq()) in a way that enforces airtime fairness
between active stations. This scheduling works the same way as the ath9k
in-driver airtime fairness scheduling. If no airtime usage is reported
by the driver, the scheduler will default to round-robin scheduling.

For drivers that don't control TXQ scheduling in software, a new API
function, ieee80211_txq_may_transmit(), is added which the driver can use
to check if the TXQ is eligible for transmission, or should be throttled to
enforce fairness. Calls to this function must also be enclosed in
ieee80211_txq_schedule_{start,end}() calls to ensure proper locking.

The API ieee80211_txq_may_transmit() also ensures that TXQ list will be
aligned aginst driver's own round-robin scheduler list. i.e it rotates
the TXQ list till it makes the requested node becomes the first entry
in TXQ list. Thus both the TXQ list and driver's list are in sync.

Co-developed-by: Rajkumar Manoharan <rmanohar@codeaurora.org>
Signed-off-by: Louie Lu <git@louie.lu>
[added debugfs write op to reset airtime counter]
Signed-off-by: Toke Høiland-Jørgensen <toke@toke.dk>
Signed-off-by: Rajkumar Manoharan <rmanohar@codeaurora.org>
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
include/net/mac80211.h
net/mac80211/cfg.c
net/mac80211/debugfs.c
net/mac80211/debugfs_sta.c
net/mac80211/ieee80211_i.h
net/mac80211/main.c
net/mac80211/sta_info.c
net/mac80211/sta_info.h
net/mac80211/status.c
net/mac80211/tx.c

index e8a057f071c4ce0a1cae6018dddd9d870c3c0bdf..5d36eacf401e42a916696659b6a2b48773c4e877 100644 (file)
@@ -2360,6 +2360,9 @@ enum ieee80211_hw_flags {
  * @tx_sk_pacing_shift: Pacing shift to set on TCP sockets when frames from
  *     them are encountered. The default should typically not be changed,
  *     unless the driver has good reasons for needing more buffers.
+ *
+ * @weight_multipler: Driver specific airtime weight multiplier used while
+ *     refilling deficit of each TXQ.
  */
 struct ieee80211_hw {
        struct ieee80211_conf conf;
@@ -2396,6 +2399,7 @@ struct ieee80211_hw {
        const struct ieee80211_cipher_scheme *cipher_schemes;
        u8 max_nan_de_entries;
        u8 tx_sk_pacing_shift;
+       u8 weight_multiplier;
 };
 
 static inline bool _ieee80211_hw_check(struct ieee80211_hw *hw,
@@ -5407,6 +5411,34 @@ void ieee80211_sta_eosp(struct ieee80211_sta *pubsta);
  */
 void ieee80211_send_eosp_nullfunc(struct ieee80211_sta *pubsta, int tid);
 
+/**
+ * ieee80211_sta_register_airtime - register airtime usage for a sta/tid
+ *
+ * Register airtime usage for a given sta on a given tid. The driver can call
+ * this function to notify mac80211 that a station used a certain amount of
+ * airtime. This information will be used by the TXQ scheduler to schedule
+ * stations in a way that ensures airtime fairness.
+ *
+ * The reported airtime should as a minimum include all time that is spent
+ * transmitting to the remote station, including overhead and padding, but not
+ * including time spent waiting for a TXOP. If the time is not reported by the
+ * hardware it can in some cases be calculated from the rate and known frame
+ * composition. When possible, the time should include any failed transmission
+ * attempts.
+ *
+ * The driver can either call this function synchronously for every packet or
+ * aggregate, or asynchronously as airtime usage information becomes available.
+ * TX and RX airtime can be reported together, or separately by setting one of
+ * them to 0.
+ *
+ * @pubsta: the station
+ * @tid: the TID to register airtime for
+ * @tx_airtime: airtime used during TX (in usec)
+ * @rx_airtime: airtime used during RX (in usec)
+ */
+void ieee80211_sta_register_airtime(struct ieee80211_sta *pubsta, u8 tid,
+                                   u32 tx_airtime, u32 rx_airtime);
+
 /**
  * ieee80211_iter_keys - iterate keys programmed into the device
  * @hw: pointer obtained from ieee80211_alloc_hw()
@@ -6173,6 +6205,33 @@ void ieee80211_txq_schedule_start(struct ieee80211_hw *hw, u8 ac)
 void ieee80211_txq_schedule_end(struct ieee80211_hw *hw, u8 ac)
        __releases(txq_lock);
 
+/**
+ * ieee80211_txq_may_transmit - check whether TXQ is allowed to transmit
+ *
+ * This function is used to check whether given txq is allowed to transmit by
+ * the airtime scheduler, and can be used by drivers to access the airtime
+ * fairness accounting without going using the scheduling order enfored by
+ * next_txq().
+ *
+ * Returns %true if the airtime scheduler thinks the TXQ should be allowed to
+ * transmit, and %false if it should be throttled. This function can also have
+ * the side effect of rotating the TXQ in the scheduler rotation, which will
+ * eventually bring the deficit to positive and allow the station to transmit
+ * again.
+ *
+ * The API ieee80211_txq_may_transmit() also ensures that TXQ list will be
+ * aligned aginst driver's own round-robin scheduler list. i.e it rotates
+ * the TXQ list till it makes the requested node becomes the first entry
+ * in TXQ list. Thus both the TXQ list and driver's list are in sync. If this
+ * function returns %true, the driver is expected to schedule packets
+ * for transmission, and then return the TXQ through ieee80211_return_txq().
+ *
+ * @hw: pointer as obtained from ieee80211_alloc_hw()
+ * @txq: pointer obtained from station or virtual interface
+ */
+bool ieee80211_txq_may_transmit(struct ieee80211_hw *hw,
+                               struct ieee80211_txq *txq);
+
 /**
  * ieee80211_txq_get_depth - get pending frame/byte count of given txq
  *
index de65fe3ed9cc66e9d6373c4df22d356e8b36579d..83ee573b18043647f2a7e7aab47022ac665044dd 100644 (file)
@@ -1447,6 +1447,9 @@ static int sta_apply_parameters(struct ieee80211_local *local,
        if (ieee80211_vif_is_mesh(&sdata->vif))
                sta_apply_mesh_params(local, sta, params);
 
+       if (params->airtime_weight)
+               sta->airtime_weight = params->airtime_weight;
+
        /* set the STA state after all sta info from usermode has been set */
        if (test_sta_flag(sta, WLAN_STA_TDLS_PEER) ||
            set & BIT(NL80211_STA_FLAG_ASSOCIATED)) {
index 3fe541e358f3c16815ca0896638bd4093235b4f5..81c5fec2eae7270cb16d09c31798c9e30a55d484 100644 (file)
@@ -383,6 +383,9 @@ void debugfs_hw_add(struct ieee80211_local *local)
        if (local->ops->wake_tx_queue)
                DEBUGFS_ADD_MODE(aqm, 0600);
 
+       debugfs_create_u16("airtime_flags", 0600,
+                          phyd, &local->airtime_flags);
+
        statsd = debugfs_create_dir("statistics", phyd);
 
        /* if the dir failed, don't put all the other things into the root! */
index b753194710ad099d6918230aef5a9f125d1721b6..3aa618dcc58e19101a9fbb51c1a72f77d6d83310 100644 (file)
@@ -181,9 +181,9 @@ static ssize_t sta_aqm_read(struct file *file, char __user *userbuf,
                               txqi->tin.tx_bytes,
                               txqi->tin.tx_packets,
                               txqi->flags,
-                              txqi->flags & (1<<IEEE80211_TXQ_STOP) ? "STOP" : "RUN",
-                              txqi->flags & (1<<IEEE80211_TXQ_AMPDU) ? " AMPDU" : "",
-                              txqi->flags & (1<<IEEE80211_TXQ_NO_AMSDU) ? " NO-AMSDU" : "");
+                              test_bit(IEEE80211_TXQ_STOP, &txqi->flags) ? "STOP" : "RUN",
+                              test_bit(IEEE80211_TXQ_AMPDU, &txqi->flags) ? " AMPDU" : "",
+                              test_bit(IEEE80211_TXQ_NO_AMSDU, &txqi->flags) ? " NO-AMSDU" : "");
        }
 
        rcu_read_unlock();
@@ -195,6 +195,64 @@ static ssize_t sta_aqm_read(struct file *file, char __user *userbuf,
 }
 STA_OPS(aqm);
 
+static ssize_t sta_airtime_read(struct file *file, char __user *userbuf,
+                               size_t count, loff_t *ppos)
+{
+       struct sta_info *sta = file->private_data;
+       struct ieee80211_local *local = sta->sdata->local;
+       size_t bufsz = 200;
+       char *buf = kzalloc(bufsz, GFP_KERNEL), *p = buf;
+       u64 rx_airtime = 0, tx_airtime = 0;
+       s64 deficit[IEEE80211_NUM_ACS];
+       ssize_t rv;
+       int ac;
+
+       if (!buf)
+               return -ENOMEM;
+
+       for (ac = 0; ac < IEEE80211_NUM_ACS; ac++) {
+               spin_lock_bh(&local->active_txq_lock[ac]);
+               rx_airtime += sta->airtime[ac].rx_airtime;
+               tx_airtime += sta->airtime[ac].tx_airtime;
+               deficit[ac] = sta->airtime[ac].deficit;
+               spin_unlock_bh(&local->active_txq_lock[ac]);
+       }
+
+       p += scnprintf(p, bufsz + buf - p,
+               "RX: %llu us\nTX: %llu us\nWeight: %u\n"
+               "Deficit: VO: %lld us VI: %lld us BE: %lld us BK: %lld us\n",
+               rx_airtime,
+               tx_airtime,
+               sta->airtime_weight,
+               deficit[0],
+               deficit[1],
+               deficit[2],
+               deficit[3]);
+
+       rv = simple_read_from_buffer(userbuf, count, ppos, buf, p - buf);
+       kfree(buf);
+       return rv;
+}
+
+static ssize_t sta_airtime_write(struct file *file, const char __user *userbuf,
+                                size_t count, loff_t *ppos)
+{
+       struct sta_info *sta = file->private_data;
+       struct ieee80211_local *local = sta->sdata->local;
+       int ac;
+
+       for (ac = 0; ac < IEEE80211_NUM_ACS; ac++) {
+               spin_lock_bh(&local->active_txq_lock[ac]);
+               sta->airtime[ac].rx_airtime = 0;
+               sta->airtime[ac].tx_airtime = 0;
+               sta->airtime[ac].deficit = sta->airtime_weight;
+               spin_unlock_bh(&local->active_txq_lock[ac]);
+       }
+
+       return count;
+}
+STA_OPS_RW(airtime);
+
 static ssize_t sta_agg_status_read(struct file *file, char __user *userbuf,
                                        size_t count, loff_t *ppos)
 {
@@ -906,6 +964,10 @@ void ieee80211_sta_debugfs_add(struct sta_info *sta)
        if (local->ops->wake_tx_queue)
                DEBUGFS_ADD(aqm);
 
+       if (wiphy_ext_feature_isset(local->hw.wiphy,
+                                   NL80211_EXT_FEATURE_AIRTIME_FAIRNESS))
+               DEBUGFS_ADD(airtime);
+
        if (sizeof(sta->driver_buffered_tids) == sizeof(u32))
                debugfs_create_x32("driver_buffered_tids", 0400,
                                   sta->debugfs_dir,
index d1db27b1e989e5e700daf55a1387265d7e450d2b..056b16bce3b05b4dff316679d9798343a20e82cf 100644 (file)
@@ -1138,6 +1138,8 @@ struct ieee80211_local {
        struct list_head active_txqs[IEEE80211_NUM_ACS];
        u16 schedule_round[IEEE80211_NUM_ACS];
 
+       u16 airtime_flags;
+
        const struct ieee80211_ops *ops;
 
        /*
index 9b9d6cadf56e1180e9f4d7eeae0543599d33c485..896f17d726d0ed5b7a148bb6b09d90f09fc57934 100644 (file)
@@ -667,6 +667,7 @@ struct ieee80211_hw *ieee80211_alloc_hw_nm(size_t priv_data_len,
                INIT_LIST_HEAD(&local->active_txqs[i]);
                spin_lock_init(&local->active_txq_lock[i]);
        }
+       local->airtime_flags = AIRTIME_USE_TX | AIRTIME_USE_RX;
 
        INIT_LIST_HEAD(&local->chanctx_list);
        mutex_init(&local->chanctx_mtx);
@@ -1153,6 +1154,9 @@ int ieee80211_register_hw(struct ieee80211_hw *hw)
        if (!local->hw.max_nan_de_entries)
                local->hw.max_nan_de_entries = IEEE80211_MAX_NAN_INSTANCE_ID;
 
+       if (!local->hw.weight_multiplier)
+               local->hw.weight_multiplier = 1;
+
        result = ieee80211_wep_init(local);
        if (result < 0)
                wiphy_debug(local->hw.wiphy, "Failed to initialize wep: %d\n",
index 83e1c316a29ec3e05474ea7ec4fc4c17871a03ee..11f058987a54393edd87b7e04f88c74084bd70de 100644 (file)
@@ -90,7 +90,6 @@ static void __cleanup_single_sta(struct sta_info *sta)
        struct tid_ampdu_tx *tid_tx;
        struct ieee80211_sub_if_data *sdata = sta->sdata;
        struct ieee80211_local *local = sdata->local;
-       struct fq *fq = &local->fq;
        struct ps_data *ps;
 
        if (test_sta_flag(sta, WLAN_STA_PS_STA) ||
@@ -120,9 +119,7 @@ static void __cleanup_single_sta(struct sta_info *sta)
 
                        txqi = to_txq_info(sta->sta.txq[i]);
 
-                       spin_lock_bh(&fq->lock);
                        ieee80211_txq_purge(local, txqi);
-                       spin_unlock_bh(&fq->lock);
                }
        }
 
@@ -387,9 +384,12 @@ struct sta_info *sta_info_alloc(struct ieee80211_sub_if_data *sdata,
        if (sta_prepare_rate_control(local, sta, gfp))
                goto free_txq;
 
+       sta->airtime_weight = IEEE80211_DEFAULT_AIRTIME_WEIGHT;
+
        for (i = 0; i < IEEE80211_NUM_ACS; i++) {
                skb_queue_head_init(&sta->ps_tx_buf[i]);
                skb_queue_head_init(&sta->tx_filtered[i]);
+               sta->airtime[i].deficit = sta->airtime_weight;
        }
 
        for (i = 0; i < IEEE80211_NUM_TIDS; i++)
@@ -1826,6 +1826,27 @@ void ieee80211_sta_set_buffered(struct ieee80211_sta *pubsta,
 }
 EXPORT_SYMBOL(ieee80211_sta_set_buffered);
 
+void ieee80211_sta_register_airtime(struct ieee80211_sta *pubsta, u8 tid,
+                                   u32 tx_airtime, u32 rx_airtime)
+{
+       struct sta_info *sta = container_of(pubsta, struct sta_info, sta);
+       struct ieee80211_local *local = sta->sdata->local;
+       u8 ac = ieee80211_ac_from_tid(tid);
+       u32 airtime = 0;
+
+       if (sta->local->airtime_flags & AIRTIME_USE_TX)
+               airtime += tx_airtime;
+       if (sta->local->airtime_flags & AIRTIME_USE_RX)
+               airtime += rx_airtime;
+
+       spin_lock_bh(&local->active_txq_lock[ac]);
+       sta->airtime[ac].tx_airtime += tx_airtime;
+       sta->airtime[ac].rx_airtime += rx_airtime;
+       sta->airtime[ac].deficit -= airtime;
+       spin_unlock_bh(&local->active_txq_lock[ac]);
+}
+EXPORT_SYMBOL(ieee80211_sta_register_airtime);
+
 int sta_info_move_state(struct sta_info *sta,
                        enum ieee80211_sta_state new_state)
 {
@@ -2188,6 +2209,23 @@ void sta_set_sinfo(struct sta_info *sta, struct station_info *sinfo,
                sinfo->filled |= BIT_ULL(NL80211_STA_INFO_TX_FAILED);
        }
 
+       if (!(sinfo->filled & BIT_ULL(NL80211_STA_INFO_RX_DURATION))) {
+               for (ac = 0; ac < IEEE80211_NUM_ACS; ac++)
+                       sinfo->rx_duration += sta->airtime[ac].rx_airtime;
+               sinfo->filled |= BIT_ULL(NL80211_STA_INFO_RX_DURATION);
+       }
+
+       if (!(sinfo->filled & BIT_ULL(NL80211_STA_INFO_TX_DURATION))) {
+               for (ac = 0; ac < IEEE80211_NUM_ACS; ac++)
+                       sinfo->tx_duration += sta->airtime[ac].tx_airtime;
+               sinfo->filled |= BIT_ULL(NL80211_STA_INFO_TX_DURATION);
+       }
+
+       if (!(sinfo->filled & BIT_ULL(NL80211_STA_INFO_AIRTIME_WEIGHT))) {
+               sinfo->airtime_weight = sta->airtime_weight;
+               sinfo->filled |= BIT_ULL(NL80211_STA_INFO_AIRTIME_WEIGHT);
+       }
+
        sinfo->rx_dropped_misc = sta->rx_stats.dropped;
        if (sta->pcpu_rx_stats) {
                for_each_possible_cpu(cpu) {
index 8eb29041be54b1af438c50532cd9861740061263..9a380803e59793dcbbc93378015744deb472661a 100644 (file)
@@ -127,6 +127,16 @@ enum ieee80211_agg_stop_reason {
        AGG_STOP_DESTROY_STA,
 };
 
+/* Debugfs flags to enable/disable use of RX/TX airtime in scheduler */
+#define AIRTIME_USE_TX         BIT(0)
+#define AIRTIME_USE_RX         BIT(1)
+
+struct airtime_info {
+       u64 rx_airtime;
+       u64 tx_airtime;
+       s64 deficit;
+};
+
 struct sta_info;
 
 /**
@@ -565,6 +575,9 @@ struct sta_info {
        } tx_stats;
        u16 tid_seq[IEEE80211_QOS_CTL_TID_MASK + 1];
 
+       struct airtime_info airtime[IEEE80211_NUM_ACS];
+       u16 airtime_weight;
+
        /*
         * Aggregation information, locked with lock.
         */
index 3f0b96e1e02fa572bde737a0cc3f98967fe8ac0f..5b9952b1caf30521a9d62b6471ce238f4f90b4eb 100644 (file)
@@ -823,6 +823,12 @@ static void __ieee80211_tx_status(struct ieee80211_hw *hw,
                        ieee80211_sta_tx_notify(sta->sdata, (void *) skb->data,
                                                acked, info->status.tx_time);
 
+               if (info->status.tx_time &&
+                   wiphy_ext_feature_isset(local->hw.wiphy,
+                                           NL80211_EXT_FEATURE_AIRTIME_FAIRNESS))
+                       ieee80211_sta_register_airtime(&sta->sta, tid,
+                                                      info->status.tx_time, 0);
+
                if (ieee80211_hw_check(&local->hw, REPORTS_TX_ACK_STATUS)) {
                        if (info->flags & IEEE80211_TX_STAT_ACK) {
                                if (sta->status_stats.lost_packets)
index 544da6411620f5c190824b7a6a4e245620138029..f46d8d822f86edfaf0ecf34157b56c4031baeed5 100644 (file)
@@ -1488,8 +1488,11 @@ void ieee80211_txq_purge(struct ieee80211_local *local,
        struct fq *fq = &local->fq;
        struct fq_tin *tin = &txqi->tin;
 
+       spin_lock_bh(&fq->lock);
        fq_tin_reset(fq, tin, fq_skb_free_func);
        ieee80211_purge_tx_queue(&local->hw, &txqi->frags);
+       spin_unlock_bh(&fq->lock);
+
        spin_lock_bh(&local->active_txq_lock[txqi->txq.ac]);
        list_del_init(&txqi->schedule_order);
        spin_unlock_bh(&local->active_txq_lock[txqi->txq.ac]);
@@ -3641,11 +3644,28 @@ struct ieee80211_txq *ieee80211_next_txq(struct ieee80211_hw *hw, u8 ac)
 
        lockdep_assert_held(&local->active_txq_lock[ac]);
 
+ begin:
        txqi = list_first_entry_or_null(&local->active_txqs[ac],
                                        struct txq_info,
                                        schedule_order);
+       if (!txqi)
+               return NULL;
+
+       if (txqi->txq.sta) {
+               struct sta_info *sta = container_of(txqi->txq.sta,
+                                               struct sta_info, sta);
+
+               if (sta->airtime[txqi->txq.ac].deficit < 0) {
+                       sta->airtime[txqi->txq.ac].deficit +=
+                               sta->airtime_weight;
+                       list_move_tail(&txqi->schedule_order,
+                                      &local->active_txqs[txqi->txq.ac]);
+                       goto begin;
+               }
+       }
+
 
-       if (!txqi || txqi->schedule_round == local->schedule_round[ac])
+       if (txqi->schedule_round == local->schedule_round[ac])
                return NULL;
 
        list_del_init(&txqi->schedule_order);
@@ -3663,12 +3683,74 @@ void ieee80211_return_txq(struct ieee80211_hw *hw,
        lockdep_assert_held(&local->active_txq_lock[txq->ac]);
 
        if (list_empty(&txqi->schedule_order) &&
-           (!skb_queue_empty(&txqi->frags) || txqi->tin.backlog_packets))
-               list_add_tail(&txqi->schedule_order,
-                             &local->active_txqs[txq->ac]);
+           (!skb_queue_empty(&txqi->frags) || txqi->tin.backlog_packets)) {
+               /* If airtime accounting is active, always enqueue STAs at the
+                * head of the list to ensure that they only get moved to the
+                * back by the airtime DRR scheduler once they have a negative
+                * deficit. A station that already has a negative deficit will
+                * get immediately moved to the back of the list on the next
+                * call to ieee80211_next_txq().
+                */
+               if (txqi->txq.sta &&
+                   wiphy_ext_feature_isset(local->hw.wiphy,
+                                           NL80211_EXT_FEATURE_AIRTIME_FAIRNESS))
+                       list_add(&txqi->schedule_order,
+                                &local->active_txqs[txq->ac]);
+               else
+                       list_add_tail(&txqi->schedule_order,
+                                     &local->active_txqs[txq->ac]);
+       }
 }
 EXPORT_SYMBOL(ieee80211_return_txq);
 
+bool ieee80211_txq_may_transmit(struct ieee80211_hw *hw,
+                               struct ieee80211_txq *txq)
+{
+       struct ieee80211_local *local = hw_to_local(hw);
+       struct txq_info *iter, *tmp, *txqi = to_txq_info(txq);
+       struct sta_info *sta;
+       u8 ac = txq->ac;
+
+       lockdep_assert_held(&local->active_txq_lock[ac]);
+
+       if (!txqi->txq.sta)
+               goto out;
+
+       if (list_empty(&txqi->schedule_order))
+               goto out;
+
+       list_for_each_entry_safe(iter, tmp, &local->active_txqs[ac],
+                                schedule_order) {
+               if (iter == txqi)
+                       break;
+
+               if (!iter->txq.sta) {
+                       list_move_tail(&iter->schedule_order,
+                                      &local->active_txqs[ac]);
+                       continue;
+               }
+               sta = container_of(iter->txq.sta, struct sta_info, sta);
+               if (sta->airtime[ac].deficit < 0)
+                       sta->airtime[ac].deficit += sta->airtime_weight;
+               list_move_tail(&iter->schedule_order, &local->active_txqs[ac]);
+       }
+
+       sta = container_of(txqi->txq.sta, struct sta_info, sta);
+       if (sta->airtime[ac].deficit >= 0)
+               goto out;
+
+       sta->airtime[ac].deficit += sta->airtime_weight;
+       list_move_tail(&txqi->schedule_order, &local->active_txqs[ac]);
+
+       return false;
+out:
+       if (!list_empty(&txqi->schedule_order))
+               list_del_init(&txqi->schedule_order);
+
+       return true;
+}
+EXPORT_SYMBOL(ieee80211_txq_may_transmit);
+
 void ieee80211_txq_schedule_start(struct ieee80211_hw *hw, u8 ac)
        __acquires(txq_lock)
 {