[openwrt/openwrt] mac80211: add airtime fairness improvements

LEDE Commits lede-commits at lists.infradead.org
Mon Jun 27 00:25:22 PDT 2022


nbd pushed a commit to openwrt/openwrt.git, branch openwrt-22.03:
https://git.openwrt.org/aab535d2bbc414aecde5770152e7da6690462b9a

commit aab535d2bbc414aecde5770152e7da6690462b9a
Author: Felix Fietkau <nbd at nbd.name>
AuthorDate: Sat Jun 18 10:52:09 2022 +0200

    mac80211: add airtime fairness improvements
    
    This reverts the airtime scheduler back from the virtual-time based scheduler
    to the deficit round robin scheduler implementation.
    This reduces burstiness and improves fairness by improving interaction with AQL.
    
    Signed-off-by: Felix Fietkau <nbd at nbd.name>
    (cherry-picked from commit 6d49a25804d78d639e08a67c86b26991ce6485d8)
---
 ...itch-airtime-fairness-back-to-deficit-rou.patch | 1249 ++++++++++++++++++++
 ...ke-sta-airtime-deficit-field-s32-instead-.patch |   52 +
 ...nsider-aql_tx_pending-when-checking-airti.patch |   48 +
 ...ep-recently-active-tx-queues-in-schedulin.patch |  118 ++
 ...d-a-per-PHY-AQL-limit-to-improve-fairness.patch |  131 ++
 ...d-debugfs-file-to-display-per-phy-AQL-pen.patch |   58 +
 ...ly-accumulate-airtime-deficit-for-active-.patch |   36 +
 .../500-mac80211_configure_antenna_gain.patch      |    6 +-
 8 files changed, 1695 insertions(+), 3 deletions(-)

diff --git a/package/kernel/mac80211/patches/subsys/330-mac80211-switch-airtime-fairness-back-to-deficit-rou.patch b/package/kernel/mac80211/patches/subsys/330-mac80211-switch-airtime-fairness-back-to-deficit-rou.patch
new file mode 100644
index 0000000000..f7ed81cd9a
--- /dev/null
+++ b/package/kernel/mac80211/patches/subsys/330-mac80211-switch-airtime-fairness-back-to-deficit-rou.patch
@@ -0,0 +1,1249 @@
+From: Felix Fietkau <nbd at nbd.name>
+Date: Sun, 19 Jun 2022 23:13:05 +0200
+Subject: [PATCH] mac80211: switch airtime fairness back to deficit round-robin
+ scheduling
+
+This reverts commits 6a789ba679d652587532cec2a0e0274fda172f3b and
+2433647bc8d983a543e7d31b41ca2de1c7e2c198.
+
+The virtual time scheduler code has a number of issues:
+- queues slowed down by hardware/firmware powersave handling were not properly
+  handled.
+- on ath10k in push-pull mode, tx queues that the driver tries to pull from
+  were starved, causing excessive latency
+- delay between tx enqueue and reported airtime use were causing excessively
+  bursty tx behavior
+
+The bursty behavior may also be present on the round-robin scheduler, but there
+it is much easier to fix without introducing additional regressions
+
+Signed-off-by: Felix Fietkau <nbd at nbd.name>
+---
+
+--- a/include/net/mac80211.h
++++ b/include/net/mac80211.h
+@@ -6666,6 +6666,9 @@ static inline void ieee80211_txq_schedul
+ {
+ }
+ 
++void __ieee80211_schedule_txq(struct ieee80211_hw *hw,
++			      struct ieee80211_txq *txq, bool force);
++
+ /**
+  * ieee80211_schedule_txq - schedule a TXQ for transmission
+  *
+@@ -6678,7 +6681,11 @@ static inline void ieee80211_txq_schedul
+  * The driver may call this function if it has buffered packets for
+  * this TXQ internally.
+  */
+-void ieee80211_schedule_txq(struct ieee80211_hw *hw, struct ieee80211_txq *txq);
++static inline void
++ieee80211_schedule_txq(struct ieee80211_hw *hw, struct ieee80211_txq *txq)
++{
++	__ieee80211_schedule_txq(hw, txq, true);
++}
+ 
+ /**
+  * ieee80211_return_txq - return a TXQ previously acquired by ieee80211_next_txq()
+@@ -6690,8 +6697,12 @@ void ieee80211_schedule_txq(struct ieee8
+  * The driver may set force=true if it has buffered packets for this TXQ
+  * internally.
+  */
+-void ieee80211_return_txq(struct ieee80211_hw *hw, struct ieee80211_txq *txq,
+-			  bool force);
++static inline void
++ieee80211_return_txq(struct ieee80211_hw *hw, struct ieee80211_txq *txq,
++		     bool force)
++{
++	__ieee80211_schedule_txq(hw, txq, force);
++}
+ 
+ /**
+  * ieee80211_txq_may_transmit - check whether TXQ is allowed to transmit
+--- a/net/mac80211/cfg.c
++++ b/net/mac80211/cfg.c
+@@ -1554,38 +1554,6 @@ static void sta_apply_mesh_params(struct
+ #endif
+ }
+ 
+-static void sta_apply_airtime_params(struct ieee80211_local *local,
+-				     struct sta_info *sta,
+-				     struct station_parameters *params)
+-{
+-	u8 ac;
+-
+-	for (ac = 0; ac < IEEE80211_NUM_ACS; ac++) {
+-		struct airtime_sched_info *air_sched = &local->airtime[ac];
+-		struct airtime_info *air_info = &sta->airtime[ac];
+-		struct txq_info *txqi;
+-		u8 tid;
+-
+-		spin_lock_bh(&air_sched->lock);
+-		for (tid = 0; tid < IEEE80211_NUM_TIDS + 1; tid++) {
+-			if (air_info->weight == params->airtime_weight ||
+-			    !sta->sta.txq[tid] ||
+-			    ac != ieee80211_ac_from_tid(tid))
+-				continue;
+-
+-			airtime_weight_set(air_info, params->airtime_weight);
+-
+-			txqi = to_txq_info(sta->sta.txq[tid]);
+-			if (RB_EMPTY_NODE(&txqi->schedule_order))
+-				continue;
+-
+-			ieee80211_update_airtime_weight(local, air_sched,
+-							0, true);
+-		}
+-		spin_unlock_bh(&air_sched->lock);
+-	}
+-}
+-
+ static int sta_apply_parameters(struct ieee80211_local *local,
+ 				struct sta_info *sta,
+ 				struct station_parameters *params)
+@@ -1773,8 +1741,7 @@ static int sta_apply_parameters(struct i
+ 		sta_apply_mesh_params(local, sta, params);
+ 
+ 	if (params->airtime_weight)
+-		sta_apply_airtime_params(local, sta, params);
+-
++		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) ||
+--- a/net/mac80211/debugfs.c
++++ b/net/mac80211/debugfs.c
+@@ -216,14 +216,14 @@ static ssize_t aql_txq_limit_read(struct
+ 			"VI	%u		%u\n"
+ 			"BE	%u		%u\n"
+ 			"BK	%u		%u\n",
+-			local->airtime[IEEE80211_AC_VO].aql_txq_limit_low,
+-			local->airtime[IEEE80211_AC_VO].aql_txq_limit_high,
+-			local->airtime[IEEE80211_AC_VI].aql_txq_limit_low,
+-			local->airtime[IEEE80211_AC_VI].aql_txq_limit_high,
+-			local->airtime[IEEE80211_AC_BE].aql_txq_limit_low,
+-			local->airtime[IEEE80211_AC_BE].aql_txq_limit_high,
+-			local->airtime[IEEE80211_AC_BK].aql_txq_limit_low,
+-			local->airtime[IEEE80211_AC_BK].aql_txq_limit_high);
++			local->aql_txq_limit_low[IEEE80211_AC_VO],
++			local->aql_txq_limit_high[IEEE80211_AC_VO],
++			local->aql_txq_limit_low[IEEE80211_AC_VI],
++			local->aql_txq_limit_high[IEEE80211_AC_VI],
++			local->aql_txq_limit_low[IEEE80211_AC_BE],
++			local->aql_txq_limit_high[IEEE80211_AC_BE],
++			local->aql_txq_limit_low[IEEE80211_AC_BK],
++			local->aql_txq_limit_high[IEEE80211_AC_BK]);
+ 	return simple_read_from_buffer(user_buf, count, ppos,
+ 				       buf, len);
+ }
+@@ -255,11 +255,11 @@ static ssize_t aql_txq_limit_write(struc
+ 	if (ac >= IEEE80211_NUM_ACS)
+ 		return -EINVAL;
+ 
+-	q_limit_low_old = local->airtime[ac].aql_txq_limit_low;
+-	q_limit_high_old = local->airtime[ac].aql_txq_limit_high;
++	q_limit_low_old = local->aql_txq_limit_low[ac];
++	q_limit_high_old = local->aql_txq_limit_high[ac];
+ 
+-	local->airtime[ac].aql_txq_limit_low = q_limit_low;
+-	local->airtime[ac].aql_txq_limit_high = q_limit_high;
++	local->aql_txq_limit_low[ac] = q_limit_low;
++	local->aql_txq_limit_high[ac] = q_limit_high;
+ 
+ 	mutex_lock(&local->sta_mtx);
+ 	list_for_each_entry(sta, &local->sta_list, list) {
+@@ -382,46 +382,6 @@ static const struct file_operations forc
+ 	.llseek = default_llseek,
+ };
+ 
+-static ssize_t airtime_read(struct file *file,
+-			    char __user *user_buf,
+-			    size_t count,
+-			    loff_t *ppos)
+-{
+-	struct ieee80211_local *local = file->private_data;
+-	char buf[200];
+-	u64 v_t[IEEE80211_NUM_ACS];
+-	u64 wt[IEEE80211_NUM_ACS];
+-	int len = 0, ac;
+-
+-	for (ac = 0; ac < IEEE80211_NUM_ACS; ac++) {
+-		spin_lock_bh(&local->airtime[ac].lock);
+-		v_t[ac] = local->airtime[ac].v_t;
+-		wt[ac] = local->airtime[ac].weight_sum;
+-		spin_unlock_bh(&local->airtime[ac].lock);
+-	}
+-	len = scnprintf(buf, sizeof(buf),
+-			"\tVO         VI         BE         BK\n"
+-			"Virt-t\t%-10llu %-10llu %-10llu %-10llu\n"
+-			"Weight\t%-10llu %-10llu %-10llu %-10llu\n",
+-			v_t[0],
+-			v_t[1],
+-			v_t[2],
+-			v_t[3],
+-			wt[0],
+-			wt[1],
+-			wt[2],
+-			wt[3]);
+-
+-	return simple_read_from_buffer(user_buf, count, ppos,
+-				       buf, len);
+-}
+-
+-static const struct file_operations airtime_ops = {
+-	.read = airtime_read,
+-	.open = simple_open,
+-	.llseek = default_llseek,
+-};
+-
+ #ifdef CONFIG_PM
+ static ssize_t reset_write(struct file *file, const char __user *user_buf,
+ 			   size_t count, loff_t *ppos)
+@@ -672,11 +632,7 @@ void debugfs_hw_add(struct ieee80211_loc
+ 	if (local->ops->wake_tx_queue)
+ 		DEBUGFS_ADD_MODE(aqm, 0600);
+ 
+-	if (wiphy_ext_feature_isset(local->hw.wiphy,
+-				    NL80211_EXT_FEATURE_AIRTIME_FAIRNESS)) {
+-		DEBUGFS_ADD_MODE(airtime, 0600);
+-		DEBUGFS_ADD_MODE(airtime_flags, 0600);
+-	}
++	DEBUGFS_ADD_MODE(airtime_flags, 0600);
+ 
+ 	DEBUGFS_ADD(aql_txq_limit);
+ 	debugfs_create_u32("aql_threshold", 0600,
+--- a/net/mac80211/debugfs_netdev.c
++++ b/net/mac80211/debugfs_netdev.c
+@@ -512,34 +512,6 @@ static ssize_t ieee80211_if_fmt_aqm(
+ }
+ IEEE80211_IF_FILE_R(aqm);
+ 
+-static ssize_t ieee80211_if_fmt_airtime(
+-	const struct ieee80211_sub_if_data *sdata, char *buf, int buflen)
+-{
+-	struct ieee80211_local *local = sdata->local;
+-	struct ieee80211_txq *txq = sdata->vif.txq;
+-	struct airtime_info *air_info;
+-	int len;
+-
+-	if (!txq)
+-		return 0;
+-
+-	spin_lock_bh(&local->airtime[txq->ac].lock);
+-	air_info = to_airtime_info(txq);
+-	len = scnprintf(buf,
+-			buflen,
+-			"RX: %llu us\nTX: %llu us\nWeight: %u\n"
+-			"Virt-T: %lld us\n",
+-			air_info->rx_airtime,
+-			air_info->tx_airtime,
+-			air_info->weight,
+-			air_info->v_t);
+-	spin_unlock_bh(&local->airtime[txq->ac].lock);
+-
+-	return len;
+-}
+-
+-IEEE80211_IF_FILE_R(airtime);
+-
+ IEEE80211_IF_FILE(multicast_to_unicast, u.ap.multicast_to_unicast, HEX);
+ 
+ /* IBSS attributes */
+@@ -685,10 +657,8 @@ static void add_common_files(struct ieee
+ 
+ 	if (sdata->local->ops->wake_tx_queue &&
+ 	    sdata->vif.type != NL80211_IFTYPE_P2P_DEVICE &&
+-	    sdata->vif.type != NL80211_IFTYPE_NAN) {
++	    sdata->vif.type != NL80211_IFTYPE_NAN)
+ 		DEBUGFS_ADD(aqm);
+-		DEBUGFS_ADD(airtime);
+-	}
+ }
+ 
+ static void add_sta_files(struct ieee80211_sub_if_data *sdata)
+--- a/net/mac80211/debugfs_sta.c
++++ b/net/mac80211/debugfs_sta.c
+@@ -202,7 +202,7 @@ static ssize_t sta_airtime_read(struct f
+ 	size_t bufsz = 400;
+ 	char *buf = kzalloc(bufsz, GFP_KERNEL), *p = buf;
+ 	u64 rx_airtime = 0, tx_airtime = 0;
+-	u64 v_t[IEEE80211_NUM_ACS];
++	s64 deficit[IEEE80211_NUM_ACS];
+ 	ssize_t rv;
+ 	int ac;
+ 
+@@ -210,18 +210,18 @@ static ssize_t sta_airtime_read(struct f
+ 		return -ENOMEM;
+ 
+ 	for (ac = 0; ac < IEEE80211_NUM_ACS; ac++) {
+-		spin_lock_bh(&local->airtime[ac].lock);
++		spin_lock_bh(&local->active_txq_lock[ac]);
+ 		rx_airtime += sta->airtime[ac].rx_airtime;
+ 		tx_airtime += sta->airtime[ac].tx_airtime;
+-		v_t[ac] = sta->airtime[ac].v_t;
+-		spin_unlock_bh(&local->airtime[ac].lock);
++		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"
+-		"Virt-T: VO: %lld us VI: %lld us BE: %lld us BK: %lld us\n",
+-		rx_airtime, tx_airtime, sta->airtime[0].weight,
+-		v_t[0], v_t[1], v_t[2], v_t[3]);
++		"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);
+@@ -236,11 +236,11 @@ static ssize_t sta_airtime_write(struct
+ 	int ac;
+ 
+ 	for (ac = 0; ac < IEEE80211_NUM_ACS; ac++) {
+-		spin_lock_bh(&local->airtime[ac].lock);
++		spin_lock_bh(&local->active_txq_lock[ac]);
+ 		sta->airtime[ac].rx_airtime = 0;
+ 		sta->airtime[ac].tx_airtime = 0;
+-		sta->airtime[ac].v_t = 0;
+-		spin_unlock_bh(&local->airtime[ac].lock);
++		sta->airtime[ac].deficit = sta->airtime_weight;
++		spin_unlock_bh(&local->active_txq_lock[ac]);
+ 	}
+ 
+ 	return count;
+@@ -263,10 +263,10 @@ static ssize_t sta_aql_read(struct file
+ 		return -ENOMEM;
+ 
+ 	for (ac = 0; ac < IEEE80211_NUM_ACS; ac++) {
+-		spin_lock_bh(&local->airtime[ac].lock);
++		spin_lock_bh(&local->active_txq_lock[ac]);
+ 		q_limit_l[ac] = sta->airtime[ac].aql_limit_low;
+ 		q_limit_h[ac] = sta->airtime[ac].aql_limit_high;
+-		spin_unlock_bh(&local->airtime[ac].lock);
++		spin_unlock_bh(&local->active_txq_lock[ac]);
+ 		q_depth[ac] = atomic_read(&sta->airtime[ac].aql_tx_pending);
+ 	}
+ 
+--- a/net/mac80211/ieee80211_i.h
++++ b/net/mac80211/ieee80211_i.h
+@@ -863,16 +863,20 @@ enum txq_info_flags {
+  * @def_flow: used as a fallback flow when a packet destined to @tin hashes to
+  *	a fq_flow which is already owned by a different tin
+  * @def_cvars: codel vars for @def_flow
+- * @schedule_order: used with ieee80211_local->active_txqs
+  * @frags: used to keep fragments created after dequeue
++ * @schedule_order: used with ieee80211_local->active_txqs
++ * @schedule_round: counter to prevent infinite loops on TXQ scheduling
+  */
+ struct txq_info {
+ 	struct fq_tin tin;
+ 	struct codel_vars def_cvars;
+ 	struct codel_stats cstats;
+-	struct rb_node schedule_order;
++
++	u16 schedule_round;
++	struct list_head schedule_order;
+ 
+ 	struct sk_buff_head frags;
++
+ 	unsigned long flags;
+ 
+ 	/* keep last! */
+@@ -949,8 +953,6 @@ struct ieee80211_sub_if_data {
+ 	struct ieee80211_tx_queue_params tx_conf[IEEE80211_NUM_ACS];
+ 	struct mac80211_qos_map __rcu *qos_map;
+ 
+-	struct airtime_info airtime[IEEE80211_NUM_ACS];
+-
+ 	struct work_struct csa_finalize_work;
+ 	bool csa_block_tx; /* write-protected by sdata_lock and local->mtx */
+ 	struct cfg80211_chan_def csa_chandef;
+@@ -1180,44 +1182,6 @@ enum mac80211_scan_state {
+ 	SCAN_ABORT,
+ };
+ 
+-/**
+- * struct airtime_sched_info - state used for airtime scheduling and AQL
+- *
+- * @lock: spinlock that protects all the fields in this struct
+- * @active_txqs: rbtree of currently backlogged queues, sorted by virtual time
+- * @schedule_pos: the current position maintained while a driver walks the tree
+- *                with ieee80211_next_txq()
+- * @active_list: list of struct airtime_info structs that were active within
+- *               the last AIRTIME_ACTIVE_DURATION (100 ms), used to compute
+- *               weight_sum
+- * @last_weight_update: used for rate limiting walking active_list
+- * @last_schedule_time: tracks the last time a transmission was scheduled; used
+- *                      for catching up v_t if no stations are eligible for
+- *                      transmission.
+- * @v_t: global virtual time; queues with v_t < this are eligible for
+- *       transmission
+- * @weight_sum: total sum of all active stations used for dividing airtime
+- * @weight_sum_reciprocal: reciprocal of weight_sum (to avoid divisions in fast
+- *                         path - see comment above
+- *                         IEEE80211_RECIPROCAL_DIVISOR_64)
+- * @aql_txq_limit_low: AQL limit when total outstanding airtime
+- *                     is < IEEE80211_AQL_THRESHOLD
+- * @aql_txq_limit_high: AQL limit when total outstanding airtime
+- *                      is > IEEE80211_AQL_THRESHOLD
+- */
+-struct airtime_sched_info {
+-	spinlock_t lock;
+-	struct rb_root_cached active_txqs;
+-	struct rb_node *schedule_pos;
+-	struct list_head active_list;
+-	u64 last_weight_update;
+-	u64 last_schedule_activity;
+-	u64 v_t;
+-	u64 weight_sum;
+-	u64 weight_sum_reciprocal;
+-	u32 aql_txq_limit_low;
+-	u32 aql_txq_limit_high;
+-};
+ DECLARE_STATIC_KEY_FALSE(aql_disable);
+ 
+ struct ieee80211_local {
+@@ -1231,8 +1195,13 @@ struct ieee80211_local {
+ 	struct codel_params cparams;
+ 
+ 	/* protects active_txqs and txqi->schedule_order */
+-	struct airtime_sched_info airtime[IEEE80211_NUM_ACS];
++	spinlock_t active_txq_lock[IEEE80211_NUM_ACS];
++	struct list_head active_txqs[IEEE80211_NUM_ACS];
++	u16 schedule_round[IEEE80211_NUM_ACS];
++
+ 	u16 airtime_flags;
++	u32 aql_txq_limit_low[IEEE80211_NUM_ACS];
++	u32 aql_txq_limit_high[IEEE80211_NUM_ACS];
+ 	u32 aql_threshold;
+ 	atomic_t aql_total_pending_airtime;
+ 
+@@ -1649,125 +1618,6 @@ static inline bool txq_has_queue(struct
+ 	return !(skb_queue_empty(&txqi->frags) && !txqi->tin.backlog_packets);
+ }
+ 
+-static inline struct airtime_info *to_airtime_info(struct ieee80211_txq *txq)
+-{
+-	struct ieee80211_sub_if_data *sdata;
+-	struct sta_info *sta;
+-
+-	if (txq->sta) {
+-		sta = container_of(txq->sta, struct sta_info, sta);
+-		return &sta->airtime[txq->ac];
+-	}
+-
+-	sdata = vif_to_sdata(txq->vif);
+-	return &sdata->airtime[txq->ac];
+-}
+-
+-/* To avoid divisions in the fast path, we keep pre-computed reciprocals for
+- * airtime weight calculations. There are two different weights to keep track
+- * of: The per-station weight and the sum of weights per phy.
+- *
+- * For the per-station weights (kept in airtime_info below), we use 32-bit
+- * reciprocals with a devisor of 2^19. This lets us keep the multiplications and
+- * divisions for the station weights as 32-bit operations at the cost of a bit
+- * of rounding error for high weights; but the choice of divisor keeps rounding
+- * errors <10% for weights <2^15, assuming no more than 8ms of airtime is
+- * reported at a time.
+- *
+- * For the per-phy sum of weights the values can get higher, so we use 64-bit
+- * operations for those with a 32-bit divisor, which should avoid any
+- * significant rounding errors.
+- */
+-#define IEEE80211_RECIPROCAL_DIVISOR_64 0x100000000ULL
+-#define IEEE80211_RECIPROCAL_SHIFT_64 32
+-#define IEEE80211_RECIPROCAL_DIVISOR_32 0x80000U
+-#define IEEE80211_RECIPROCAL_SHIFT_32 19
+-
+-static inline void airtime_weight_set(struct airtime_info *air_info, u16 weight)
+-{
+-	if (air_info->weight == weight)
+-		return;
+-
+-	air_info->weight = weight;
+-	if (weight) {
+-		air_info->weight_reciprocal =
+-			IEEE80211_RECIPROCAL_DIVISOR_32 / weight;
+-	} else {
+-		air_info->weight_reciprocal = 0;
+-	}
+-}
+-
+-static inline void airtime_weight_sum_set(struct airtime_sched_info *air_sched,
+-					  int weight_sum)
+-{
+-	if (air_sched->weight_sum == weight_sum)
+-		return;
+-
+-	air_sched->weight_sum = weight_sum;
+-	if (air_sched->weight_sum) {
+-		air_sched->weight_sum_reciprocal = IEEE80211_RECIPROCAL_DIVISOR_64;
+-		do_div(air_sched->weight_sum_reciprocal, air_sched->weight_sum);
+-	} else {
+-		air_sched->weight_sum_reciprocal = 0;
+-	}
+-}
+-
+-/* A problem when trying to enforce airtime fairness is that we want to divide
+- * the airtime between the currently *active* stations. However, basing this on
+- * the instantaneous queue state of stations doesn't work, as queues tend to
+- * oscillate very quickly between empty and occupied, leading to the scheduler
+- * thinking only a single station is active when deciding whether to allow
+- * transmission (and thus not throttling correctly).
+- *
+- * To fix this we use a timer-based notion of activity: a station is considered
+- * active if it has been scheduled within the last 100 ms; we keep a separate
+- * list of all the stations considered active in this manner, and lazily update
+- * the total weight of active stations from this list (filtering the stations in
+- * the list by their 'last active' time).
+- *
+- * We add one additional safeguard to guard against stations that manage to get
+- * scheduled every 100 ms but don't transmit a lot of data, and thus don't use
+- * up any airtime. Such stations would be able to get priority for an extended
+- * period of time if they do start transmitting at full capacity again, and so
+- * we add an explicit maximum for how far behind a station is allowed to fall in
+- * the virtual airtime domain. This limit is set to a relatively high value of
+- * 20 ms because the main mechanism for catching up idle stations is the active
+- * state as described above; i.e., the hard limit should only be hit in
+- * pathological cases.
+- */
+-#define AIRTIME_ACTIVE_DURATION (100 * NSEC_PER_MSEC)
+-#define AIRTIME_MAX_BEHIND 20000 /* 20 ms */
+-
+-static inline bool airtime_is_active(struct airtime_info *air_info, u64 now)
+-{
+-	return air_info->last_scheduled >= now - AIRTIME_ACTIVE_DURATION;
+-}
+-
+-static inline void airtime_set_active(struct airtime_sched_info *air_sched,
+-				      struct airtime_info *air_info, u64 now)
+-{
+-	air_info->last_scheduled = now;
+-	air_sched->last_schedule_activity = now;
+-	list_move_tail(&air_info->list, &air_sched->active_list);
+-}
+-
+-static inline bool airtime_catchup_v_t(struct airtime_sched_info *air_sched,
+-				       u64 v_t, u64 now)
+-{
+-	air_sched->v_t = v_t;
+-	return true;
+-}
+-
+-static inline void init_airtime_info(struct airtime_info *air_info,
+-				     struct airtime_sched_info *air_sched)
+-{
+-	atomic_set(&air_info->aql_tx_pending, 0);
+-	air_info->aql_limit_low = air_sched->aql_txq_limit_low;
+-	air_info->aql_limit_high = air_sched->aql_txq_limit_high;
+-	airtime_weight_set(air_info, IEEE80211_DEFAULT_AIRTIME_WEIGHT);
+-	INIT_LIST_HEAD(&air_info->list);
+-}
+-
+ static inline int ieee80211_bssid_match(const u8 *raddr, const u8 *addr)
+ {
+ 	return ether_addr_equal(raddr, addr) ||
+@@ -2013,14 +1863,6 @@ int ieee80211_tx_control_port(struct wip
+ 			      u64 *cookie);
+ int ieee80211_probe_mesh_link(struct wiphy *wiphy, struct net_device *dev,
+ 			      const u8 *buf, size_t len);
+-void ieee80211_resort_txq(struct ieee80211_hw *hw,
+-			  struct ieee80211_txq *txq);
+-void ieee80211_unschedule_txq(struct ieee80211_hw *hw,
+-			      struct ieee80211_txq *txq,
+-			      bool purge);
+-void ieee80211_update_airtime_weight(struct ieee80211_local *local,
+-				     struct airtime_sched_info *air_sched,
+-				     u64 now, bool force);
+ 
+ /* HT */
+ void ieee80211_apply_htcap_overrides(struct ieee80211_sub_if_data *sdata,
+--- a/net/mac80211/iface.c
++++ b/net/mac80211/iface.c
+@@ -2190,9 +2190,6 @@ int ieee80211_if_add(struct ieee80211_lo
+ 		}
+ 	}
+ 
+-	for (i = 0; i < IEEE80211_NUM_ACS; i++)
+-		init_airtime_info(&sdata->airtime[i], &local->airtime[i]);
+-
+ 	ieee80211_set_default_queues(sdata);
+ 
+ 	sdata->ap_power_level = IEEE80211_UNSET_POWER_LEVEL;
+--- a/net/mac80211/main.c
++++ b/net/mac80211/main.c
+@@ -707,13 +707,10 @@ struct ieee80211_hw *ieee80211_alloc_hw_
+ 	spin_lock_init(&local->queue_stop_reason_lock);
+ 
+ 	for (i = 0; i < IEEE80211_NUM_ACS; i++) {
+-		struct airtime_sched_info *air_sched = &local->airtime[i];
+-
+-		air_sched->active_txqs = RB_ROOT_CACHED;
+-		INIT_LIST_HEAD(&air_sched->active_list);
+-		spin_lock_init(&air_sched->lock);
+-		air_sched->aql_txq_limit_low = IEEE80211_DEFAULT_AQL_TXQ_LIMIT_L;
+-		air_sched->aql_txq_limit_high =
++		INIT_LIST_HEAD(&local->active_txqs[i]);
++		spin_lock_init(&local->active_txq_lock[i]);
++		local->aql_txq_limit_low[i] = IEEE80211_DEFAULT_AQL_TXQ_LIMIT_L;
++		local->aql_txq_limit_high[i] =
+ 			IEEE80211_DEFAULT_AQL_TXQ_LIMIT_H;
+ 	}
+ 
+--- a/net/mac80211/rx.c
++++ b/net/mac80211/rx.c
+@@ -1584,8 +1584,12 @@ static void sta_ps_start(struct sta_info
+ 
+ 	for (tid = 0; tid < IEEE80211_NUM_TIDS; tid++) {
+ 		struct ieee80211_txq *txq = sta->sta.txq[tid];
++		struct txq_info *txqi = to_txq_info(txq);
+ 
+-		ieee80211_unschedule_txq(&local->hw, txq, false);
++		spin_lock(&local->active_txq_lock[txq->ac]);
++		if (!list_empty(&txqi->schedule_order))
++			list_del_init(&txqi->schedule_order);
++		spin_unlock(&local->active_txq_lock[txq->ac]);
+ 
+ 		if (txq_has_queue(txq))
+ 			set_bit(tid, &sta->txq_buffered_tids);
+--- a/net/mac80211/sta_info.c
++++ b/net/mac80211/sta_info.c
+@@ -426,11 +426,15 @@ struct sta_info *sta_info_alloc(struct i
+ 	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]);
+-		init_airtime_info(&sta->airtime[i], &local->airtime[i]);
++		sta->airtime[i].deficit = sta->airtime_weight;
++		atomic_set(&sta->airtime[i].aql_tx_pending, 0);
++		sta->airtime[i].aql_limit_low = local->aql_txq_limit_low[i];
++		sta->airtime[i].aql_limit_high = local->aql_txq_limit_high[i];
+ 	}
+ 
+ 	for (i = 0; i < IEEE80211_NUM_TIDS; i++)
+@@ -1889,59 +1893,24 @@ void ieee80211_sta_set_buffered(struct i
+ }
+ EXPORT_SYMBOL(ieee80211_sta_set_buffered);
+ 
+-void ieee80211_register_airtime(struct ieee80211_txq *txq,
+-				u32 tx_airtime, u32 rx_airtime)
++void ieee80211_sta_register_airtime(struct ieee80211_sta *pubsta, u8 tid,
++				    u32 tx_airtime, u32 rx_airtime)
+ {
+-	struct ieee80211_sub_if_data *sdata = vif_to_sdata(txq->vif);
+-	struct ieee80211_local *local = sdata->local;
+-	u64 weight_sum, weight_sum_reciprocal;
+-	struct airtime_sched_info *air_sched;
+-	struct airtime_info *air_info;
++	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;
+ 
+-	air_sched = &local->airtime[txq->ac];
+-	air_info = to_airtime_info(txq);
+-
+-	if (local->airtime_flags & AIRTIME_USE_TX)
++	if (sta->local->airtime_flags & AIRTIME_USE_TX)
+ 		airtime += tx_airtime;
+-	if (local->airtime_flags & AIRTIME_USE_RX)
++	if (sta->local->airtime_flags & AIRTIME_USE_RX)
+ 		airtime += rx_airtime;
+ 
+-	/* Weights scale so the unit weight is 256 */
+-	airtime <<= 8;
+-
+-	spin_lock_bh(&air_sched->lock);
+-
+-	air_info->tx_airtime += tx_airtime;
+-	air_info->rx_airtime += rx_airtime;
+-
+-	if (air_sched->weight_sum) {
+-		weight_sum = air_sched->weight_sum;
+-		weight_sum_reciprocal = air_sched->weight_sum_reciprocal;
+-	} else {
+-		weight_sum = air_info->weight;
+-		weight_sum_reciprocal = air_info->weight_reciprocal;
+-	}
+-
+-	/* Round the calculation of global vt */
+-	air_sched->v_t += (u64)((airtime + (weight_sum >> 1)) *
+-				weight_sum_reciprocal) >> IEEE80211_RECIPROCAL_SHIFT_64;
+-	air_info->v_t += (u32)((airtime + (air_info->weight >> 1)) *
+-			       air_info->weight_reciprocal) >> IEEE80211_RECIPROCAL_SHIFT_32;
+-	ieee80211_resort_txq(&local->hw, txq);
+-
+-	spin_unlock_bh(&air_sched->lock);
+-}
+-
+-void ieee80211_sta_register_airtime(struct ieee80211_sta *pubsta, u8 tid,
+-				    u32 tx_airtime, u32 rx_airtime)
+-{
+-	struct ieee80211_txq *txq = pubsta->txq[tid];
+-
+-	if (!txq)
+-		return;
+-
+-	ieee80211_register_airtime(txq, tx_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);
+ 
+@@ -2385,7 +2354,7 @@ void sta_set_sinfo(struct sta_info *sta,
+ 	}
+ 
+ 	if (!(sinfo->filled & BIT_ULL(NL80211_STA_INFO_AIRTIME_WEIGHT))) {
+-		sinfo->airtime_weight = sta->airtime[0].weight;
++		sinfo->airtime_weight = sta->airtime_weight;
+ 		sinfo->filled |= BIT_ULL(NL80211_STA_INFO_AIRTIME_WEIGHT);
+ 	}
+ 
+--- a/net/mac80211/sta_info.h
++++ b/net/mac80211/sta_info.h
+@@ -135,25 +135,18 @@ enum ieee80211_agg_stop_reason {
+ #define AIRTIME_USE_TX		BIT(0)
+ #define AIRTIME_USE_RX		BIT(1)
+ 
+-
+ struct airtime_info {
+ 	u64 rx_airtime;
+ 	u64 tx_airtime;
+-	u64 v_t;
+-	u64 last_scheduled;
+-	struct list_head list;
++	s64 deficit;
+ 	atomic_t aql_tx_pending; /* Estimated airtime for frames pending */
+ 	u32 aql_limit_low;
+ 	u32 aql_limit_high;
+-	u32 weight_reciprocal;
+-	u16 weight;
+ };
+ 
+ void ieee80211_sta_update_pending_airtime(struct ieee80211_local *local,
+ 					  struct sta_info *sta, u8 ac,
+ 					  u16 tx_airtime, bool tx_completed);
+-void ieee80211_register_airtime(struct ieee80211_txq *txq,
+-				u32 tx_airtime, u32 rx_airtime);
+ 
+ struct sta_info;
+ 
+@@ -523,6 +516,7 @@ struct ieee80211_fragment_cache {
+  * @tid_seq: per-TID sequence numbers for sending to this STA
+  * @airtime: per-AC struct airtime_info describing airtime statistics for this
+  *	station
++ * @airtime_weight: station weight for airtime fairness calculation purposes
+  * @ampdu_mlme: A-MPDU state machine state
+  * @mesh: mesh STA information
+  * @debugfs_dir: debug filesystem directory dentry
+@@ -653,6 +647,7 @@ struct sta_info {
+ 	u16 tid_seq[IEEE80211_QOS_CTL_TID_MASK + 1];
+ 
+ 	struct airtime_info airtime[IEEE80211_NUM_ACS];
++	u16 airtime_weight;
+ 
+ 	/*
+ 	 * Aggregation information, locked with lock.
+--- a/net/mac80211/status.c
++++ b/net/mac80211/status.c
+@@ -983,25 +983,6 @@ static void __ieee80211_tx_status(struct
+ 		if (!(info->flags & IEEE80211_TX_CTL_INJECTED) && acked)
+ 			ieee80211_frame_acked(sta, skb);
+ 
+-	} else if (wiphy_ext_feature_isset(local->hw.wiphy,
+-					   NL80211_EXT_FEATURE_AIRTIME_FAIRNESS)) {
+-		struct ieee80211_sub_if_data *sdata;
+-		struct ieee80211_txq *txq;
+-		u32 airtime;
+-
+-		/* Account airtime to multicast queue */
+-		sdata = ieee80211_sdata_from_skb(local, skb);
+-
+-		if (sdata && (txq = sdata->vif.txq)) {
+-			airtime = info->status.tx_time ?:
+-				ieee80211_calc_expected_tx_airtime(hw,
+-								   &sdata->vif,
+-								   NULL,
+-								   skb->len,
+-								   false);
+-
+-			ieee80211_register_airtime(txq, airtime, 0);
+-		}
+ 	}
+ 
+ 	/* SNMP counters
+--- a/net/mac80211/tx.c
++++ b/net/mac80211/tx.c
+@@ -18,7 +18,6 @@
+ #include <linux/bitmap.h>
+ #include <linux/rcupdate.h>
+ #include <linux/export.h>
+-#include <linux/timekeeping.h>
+ #include <net/net_namespace.h>
+ #include <net/ieee80211_radiotap.h>
+ #include <net/cfg80211.h>
+@@ -1480,7 +1479,7 @@ void ieee80211_txq_init(struct ieee80211
+ 	codel_vars_init(&txqi->def_cvars);
+ 	codel_stats_init(&txqi->cstats);
+ 	__skb_queue_head_init(&txqi->frags);
+-	RB_CLEAR_NODE(&txqi->schedule_order);
++	INIT_LIST_HEAD(&txqi->schedule_order);
+ 
+ 	txqi->txq.vif = &sdata->vif;
+ 
+@@ -1524,7 +1523,9 @@ void ieee80211_txq_purge(struct ieee8021
+ 	ieee80211_purge_tx_queue(&local->hw, &txqi->frags);
+ 	spin_unlock_bh(&fq->lock);
+ 
+-	ieee80211_unschedule_txq(&local->hw, &txqi->txq, true);
++	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]);
+ }
+ 
+ void ieee80211_txq_set_params(struct ieee80211_local *local)
+@@ -3819,259 +3820,102 @@ EXPORT_SYMBOL(ieee80211_tx_dequeue);
+ struct ieee80211_txq *ieee80211_next_txq(struct ieee80211_hw *hw, u8 ac)
+ {
+ 	struct ieee80211_local *local = hw_to_local(hw);
+-	struct airtime_sched_info *air_sched;
+-	u64 now = ktime_get_coarse_boottime_ns();
+ 	struct ieee80211_txq *ret = NULL;
+-	struct airtime_info *air_info;
+-	struct txq_info *txqi = NULL;
+-	struct rb_node *node;
+-	bool first = false;
++	struct txq_info *txqi = NULL, *head = NULL;
++	bool found_eligible_txq = false;
+ 
+-	air_sched = &local->airtime[ac];
+-	spin_lock_bh(&air_sched->lock);
++	spin_lock_bh(&local->active_txq_lock[ac]);
+ 
+-	node = air_sched->schedule_pos;
+-
+-begin:
+-	if (!node) {
+-		node = rb_first_cached(&air_sched->active_txqs);
+-		first = true;
+-	} else {
+-		node = rb_next(node);
+-	}
+-
+-	if (!node)
+-		goto out;
+-
+-	txqi = container_of(node, struct txq_info, schedule_order);
+-	air_info = to_airtime_info(&txqi->txq);
+-
+-	if (air_info->v_t > air_sched->v_t &&
+-	    (!first || !airtime_catchup_v_t(air_sched, air_info->v_t, now)))
++ begin:
++	txqi = list_first_entry_or_null(&local->active_txqs[ac],
++					struct txq_info,
++					schedule_order);
++	if (!txqi)
+ 		goto out;
+ 
+-	if (!ieee80211_txq_airtime_check(hw, &txqi->txq)) {
+-		first = false;
+-		goto begin;
+-	}
+-
+-	air_sched->schedule_pos = node;
+-	air_sched->last_schedule_activity = now;
+-	ret = &txqi->txq;
+-out:
+-	spin_unlock_bh(&air_sched->lock);
+-	return ret;
+-}
+-EXPORT_SYMBOL(ieee80211_next_txq);
+-
+-static void __ieee80211_insert_txq(struct rb_root_cached *root,
+-				   struct txq_info *txqi)
+-{
+-	struct rb_node **new = &root->rb_root.rb_node;
+-	struct airtime_info *old_air, *new_air;
+-	struct rb_node *parent = NULL;
+-	struct txq_info *__txqi;
+-	bool leftmost = true;
+-
+-	while (*new) {
+-		parent = *new;
+-		__txqi = rb_entry(parent, struct txq_info, schedule_order);
+-		old_air = to_airtime_info(&__txqi->txq);
+-		new_air = to_airtime_info(&txqi->txq);
+-
+-		if (new_air->v_t <= old_air->v_t) {
+-			new = &parent->rb_left;
+-		} else {
+-			new = &parent->rb_right;
+-			leftmost = false;
+-		}
++	if (txqi == head) {
++		if (!found_eligible_txq)
++			goto out;
++		else
++			found_eligible_txq = false;
+ 	}
+ 
+-	rb_link_node(&txqi->schedule_order, parent, new);
+-	rb_insert_color_cached(&txqi->schedule_order, root, leftmost);
+-}
+-
+-void ieee80211_resort_txq(struct ieee80211_hw *hw,
+-			  struct ieee80211_txq *txq)
+-{
+-	struct airtime_info *air_info = to_airtime_info(txq);
+-	struct ieee80211_local *local = hw_to_local(hw);
+-	struct txq_info *txqi = to_txq_info(txq);
+-	struct airtime_sched_info *air_sched;
+-
+-	air_sched = &local->airtime[txq->ac];
++	if (!head)
++		head = txqi;
+ 
+-	lockdep_assert_held(&air_sched->lock);
+-
+-	if (!RB_EMPTY_NODE(&txqi->schedule_order)) {
+-		struct airtime_info *a_prev = NULL, *a_next = NULL;
+-		struct txq_info *t_prev, *t_next;
+-		struct rb_node *n_prev, *n_next;
++	if (txqi->txq.sta) {
++		struct sta_info *sta = container_of(txqi->txq.sta,
++						    struct sta_info, sta);
++		bool aql_check = ieee80211_txq_airtime_check(hw, &txqi->txq);
++		s64 deficit = sta->airtime[txqi->txq.ac].deficit;
+ 
+-		/* Erasing a node can cause an expensive rebalancing operation,
+-		 * so we check the previous and next nodes first and only remove
+-		 * and re-insert if the current node is not already in the
+-		 * correct position.
+-		 */
+-		if ((n_prev = rb_prev(&txqi->schedule_order)) != NULL) {
+-			t_prev = container_of(n_prev, struct txq_info,
+-					      schedule_order);
+-			a_prev = to_airtime_info(&t_prev->txq);
+-		}
++		if (aql_check)
++			found_eligible_txq = true;
+ 
+-		if ((n_next = rb_next(&txqi->schedule_order)) != NULL) {
+-			t_next = container_of(n_next, struct txq_info,
+-					      schedule_order);
+-			a_next = to_airtime_info(&t_next->txq);
++		if (deficit < 0)
++			sta->airtime[txqi->txq.ac].deficit +=
++				sta->airtime_weight;
++
++		if (deficit < 0 || !aql_check) {
++			list_move_tail(&txqi->schedule_order,
++				       &local->active_txqs[txqi->txq.ac]);
++			goto begin;
+ 		}
+-
+-		if ((!a_prev || a_prev->v_t <= air_info->v_t) &&
+-		    (!a_next || a_next->v_t > air_info->v_t))
+-			return;
+-
+-		if (air_sched->schedule_pos == &txqi->schedule_order)
+-			air_sched->schedule_pos = n_prev;
+-
+-		rb_erase_cached(&txqi->schedule_order,
+-				&air_sched->active_txqs);
+-		RB_CLEAR_NODE(&txqi->schedule_order);
+-		__ieee80211_insert_txq(&air_sched->active_txqs, txqi);
+ 	}
+-}
+-
+-void ieee80211_update_airtime_weight(struct ieee80211_local *local,
+-				     struct airtime_sched_info *air_sched,
+-				     u64 now, bool force)
+-{
+-	struct airtime_info *air_info, *tmp;
+-	u64 weight_sum = 0;
+-
+-	if (unlikely(!now))
+-		now = ktime_get_coarse_boottime_ns();
+-
+-	lockdep_assert_held(&air_sched->lock);
+-
+-	if (!force && (air_sched->last_weight_update <
+-		       now - AIRTIME_ACTIVE_DURATION))
+-		return;
+-
+-	list_for_each_entry_safe(air_info, tmp,
+-				 &air_sched->active_list, list) {
+-		if (airtime_is_active(air_info, now))
+-			weight_sum += air_info->weight;
+-		else
+-			list_del_init(&air_info->list);
+-	}
+-	airtime_weight_sum_set(air_sched, weight_sum);
+-	air_sched->last_weight_update = now;
+-}
+ 
+-void ieee80211_schedule_txq(struct ieee80211_hw *hw,
+-			    struct ieee80211_txq *txq)
+-	__acquires(txq_lock) __releases(txq_lock)
+-{
+-	struct ieee80211_local *local = hw_to_local(hw);
+-	struct txq_info *txqi = to_txq_info(txq);
+-	struct airtime_sched_info *air_sched;
+-	u64 now = ktime_get_coarse_boottime_ns();
+-	struct airtime_info *air_info;
+-	u8 ac = txq->ac;
+-	bool was_active;
+ 
+-	air_sched = &local->airtime[ac];
+-	air_info = to_airtime_info(txq);
+-
+-	spin_lock_bh(&air_sched->lock);
+-	was_active = airtime_is_active(air_info, now);
+-	airtime_set_active(air_sched, air_info, now);
+-
+-	if (!RB_EMPTY_NODE(&txqi->schedule_order))
++	if (txqi->schedule_round == local->schedule_round[ac])
+ 		goto out;
+ 
+-	/* If the station has been inactive for a while, catch up its v_t so it
+-	 * doesn't get indefinite priority; see comment above the definition of
+-	 * AIRTIME_MAX_BEHIND.
+-	 */
+-	if ((!was_active && air_info->v_t < air_sched->v_t) ||
+-	    air_info->v_t < air_sched->v_t - AIRTIME_MAX_BEHIND)
+-		air_info->v_t = air_sched->v_t;
+-
+-	ieee80211_update_airtime_weight(local, air_sched, now, !was_active);
+-	__ieee80211_insert_txq(&air_sched->active_txqs, txqi);
++	list_del_init(&txqi->schedule_order);
++	txqi->schedule_round = local->schedule_round[ac];
++	ret = &txqi->txq;
+ 
+ out:
+-	spin_unlock_bh(&air_sched->lock);
+-}
+-EXPORT_SYMBOL(ieee80211_schedule_txq);
+-
+-static void __ieee80211_unschedule_txq(struct ieee80211_hw *hw,
+-				       struct ieee80211_txq *txq,
+-				       bool purge)
+-{
+-	struct ieee80211_local *local = hw_to_local(hw);
+-	struct txq_info *txqi = to_txq_info(txq);
+-	struct airtime_sched_info *air_sched;
+-	struct airtime_info *air_info;
+-
+-	air_sched = &local->airtime[txq->ac];
+-	air_info = to_airtime_info(&txqi->txq);
+-
+-	lockdep_assert_held(&air_sched->lock);
+-
+-	if (purge) {
+-		list_del_init(&air_info->list);
+-		ieee80211_update_airtime_weight(local, air_sched, 0, true);
+-	}
+-
+-	if (RB_EMPTY_NODE(&txqi->schedule_order))
+-		return;
+-
+-	if (air_sched->schedule_pos == &txqi->schedule_order)
+-		air_sched->schedule_pos = rb_prev(&txqi->schedule_order);
+-
+-	if (!purge)
+-		airtime_set_active(air_sched, air_info,
+-				   ktime_get_coarse_boottime_ns());
+-
+-	rb_erase_cached(&txqi->schedule_order,
+-			&air_sched->active_txqs);
+-	RB_CLEAR_NODE(&txqi->schedule_order);
++	spin_unlock_bh(&local->active_txq_lock[ac]);
++	return ret;
+ }
++EXPORT_SYMBOL(ieee80211_next_txq);
+ 
+-void ieee80211_unschedule_txq(struct ieee80211_hw *hw,
++void __ieee80211_schedule_txq(struct ieee80211_hw *hw,
+ 			      struct ieee80211_txq *txq,
+-			      bool purge)
+-	__acquires(txq_lock) __releases(txq_lock)
+-{
+-	struct ieee80211_local *local = hw_to_local(hw);
+-
+-	spin_lock_bh(&local->airtime[txq->ac].lock);
+-	__ieee80211_unschedule_txq(hw, txq, purge);
+-	spin_unlock_bh(&local->airtime[txq->ac].lock);
+-}
+-
+-void ieee80211_return_txq(struct ieee80211_hw *hw,
+-			  struct ieee80211_txq *txq, bool force)
++			      bool force)
+ {
+ 	struct ieee80211_local *local = hw_to_local(hw);
+ 	struct txq_info *txqi = to_txq_info(txq);
+ 
+-	spin_lock_bh(&local->airtime[txq->ac].lock);
++	spin_lock_bh(&local->active_txq_lock[txq->ac]);
+ 
+-	if (!RB_EMPTY_NODE(&txqi->schedule_order) && !force &&
+-	    !txq_has_queue(txq))
+-		__ieee80211_unschedule_txq(hw, txq, false);
++	if (list_empty(&txqi->schedule_order) &&
++	    (force || !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 && local->airtime_flags &&
++		    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]);
++	}
+ 
+-	spin_unlock_bh(&local->airtime[txq->ac].lock);
++	spin_unlock_bh(&local->active_txq_lock[txq->ac]);
+ }
+-EXPORT_SYMBOL(ieee80211_return_txq);
++EXPORT_SYMBOL(__ieee80211_schedule_txq);
+ 
+ DEFINE_STATIC_KEY_FALSE(aql_disable);
+ 
+ bool ieee80211_txq_airtime_check(struct ieee80211_hw *hw,
+ 				 struct ieee80211_txq *txq)
+ {
+-	struct airtime_info *air_info = to_airtime_info(txq);
++	struct sta_info *sta;
+ 	struct ieee80211_local *local = hw_to_local(hw);
+ 
+ 	if (!wiphy_ext_feature_isset(local->hw.wiphy, NL80211_EXT_FEATURE_AQL))
+@@ -4086,12 +3930,15 @@ bool ieee80211_txq_airtime_check(struct
+ 	if (unlikely(txq->tid == IEEE80211_NUM_TIDS))
+ 		return true;
+ 
+-	if (atomic_read(&air_info->aql_tx_pending) < air_info->aql_limit_low)
++	sta = container_of(txq->sta, struct sta_info, sta);
++	if (atomic_read(&sta->airtime[txq->ac].aql_tx_pending) <
++	    sta->airtime[txq->ac].aql_limit_low)
+ 		return true;
+ 
+ 	if (atomic_read(&local->aql_total_pending_airtime) <
+ 	    local->aql_threshold &&
+-	    atomic_read(&air_info->aql_tx_pending) < air_info->aql_limit_high)
++	    atomic_read(&sta->airtime[txq->ac].aql_tx_pending) <
++	    sta->airtime[txq->ac].aql_limit_high)
+ 		return true;
+ 
+ 	return false;
+@@ -4101,59 +3948,60 @@ EXPORT_SYMBOL(ieee80211_txq_airtime_chec
+ bool ieee80211_txq_may_transmit(struct ieee80211_hw *hw,
+ 				struct ieee80211_txq *txq)
+ {
+-	struct txq_info *first_txqi = NULL, *txqi = to_txq_info(txq);
+ 	struct ieee80211_local *local = hw_to_local(hw);
+-	struct airtime_sched_info *air_sched;
+-	struct airtime_info *air_info;
+-	struct rb_node *node = NULL;
+-	bool ret = false;
+-	u64 now;
+-
++	struct txq_info *iter, *tmp, *txqi = to_txq_info(txq);
++	struct sta_info *sta;
++	u8 ac = txq->ac;
+ 
+-	if (!ieee80211_txq_airtime_check(hw, txq))
+-		return false;
++	spin_lock_bh(&local->active_txq_lock[ac]);
+ 
+-	air_sched = &local->airtime[txq->ac];
+-	spin_lock_bh(&air_sched->lock);
++	if (!txqi->txq.sta)
++		goto out;
+ 
+-	if (RB_EMPTY_NODE(&txqi->schedule_order))
++	if (list_empty(&txqi->schedule_order))
+ 		goto out;
+ 
+-	now = ktime_get_coarse_boottime_ns();
++	list_for_each_entry_safe(iter, tmp, &local->active_txqs[ac],
++				 schedule_order) {
++		if (iter == txqi)
++			break;
+ 
+-	/* Like in ieee80211_next_txq(), make sure the first station in the
+-	 * scheduling order is eligible for transmission to avoid starvation.
+-	 */
+-	node = rb_first_cached(&air_sched->active_txqs);
+-	if (node) {
+-		first_txqi = container_of(node, struct txq_info,
+-					  schedule_order);
+-		air_info = to_airtime_info(&first_txqi->txq);
+-
+-		if (air_sched->v_t < air_info->v_t)
+-			airtime_catchup_v_t(air_sched, air_info->v_t, now);
++		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]);
+ 	}
+ 
+-	air_info = to_airtime_info(&txqi->txq);
+-	if (air_info->v_t <= air_sched->v_t) {
+-		air_sched->last_schedule_activity = now;
+-		ret = true;
+-	}
++	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]);
++	spin_unlock_bh(&local->active_txq_lock[ac]);
+ 
++	return false;
+ out:
+-	spin_unlock_bh(&air_sched->lock);
+-	return ret;
++	if (!list_empty(&txqi->schedule_order))
++		list_del_init(&txqi->schedule_order);
++	spin_unlock_bh(&local->active_txq_lock[ac]);
++
++	return true;
+ }
+ EXPORT_SYMBOL(ieee80211_txq_may_transmit);
+ 
+ void ieee80211_txq_schedule_start(struct ieee80211_hw *hw, u8 ac)
+ {
+ 	struct ieee80211_local *local = hw_to_local(hw);
+-	struct airtime_sched_info *air_sched = &local->airtime[ac];
+ 
+-	spin_lock_bh(&air_sched->lock);
+-	air_sched->schedule_pos = NULL;
+-	spin_unlock_bh(&air_sched->lock);
++	spin_lock_bh(&local->active_txq_lock[ac]);
++	local->schedule_round[ac]++;
++	spin_unlock_bh(&local->active_txq_lock[ac]);
+ }
+ EXPORT_SYMBOL(ieee80211_txq_schedule_start);
+ 
diff --git a/package/kernel/mac80211/patches/subsys/331-mac80211-make-sta-airtime-deficit-field-s32-instead-.patch b/package/kernel/mac80211/patches/subsys/331-mac80211-make-sta-airtime-deficit-field-s32-instead-.patch
new file mode 100644
index 0000000000..c006d3762a
--- /dev/null
+++ b/package/kernel/mac80211/patches/subsys/331-mac80211-make-sta-airtime-deficit-field-s32-instead-.patch
@@ -0,0 +1,52 @@
+From: Felix Fietkau <nbd at nbd.name>
+Date: Mon, 20 Jun 2022 14:53:04 +0200
+Subject: [PATCH] mac80211: make sta airtime deficit field s32 instead of
+ s64
+
+32 bit is more than enough range for the airtime deficit
+
+Signed-off-by: Felix Fietkau <nbd at nbd.name>
+---
+
+--- a/net/mac80211/debugfs_sta.c
++++ b/net/mac80211/debugfs_sta.c
+@@ -202,7 +202,7 @@ static ssize_t sta_airtime_read(struct f
+ 	size_t bufsz = 400;
+ 	char *buf = kzalloc(bufsz, GFP_KERNEL), *p = buf;
+ 	u64 rx_airtime = 0, tx_airtime = 0;
+-	s64 deficit[IEEE80211_NUM_ACS];
++	s32 deficit[IEEE80211_NUM_ACS];
+ 	ssize_t rv;
+ 	int ac;
+ 
+@@ -219,7 +219,7 @@ static ssize_t sta_airtime_read(struct f
+ 
+ 	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",
++		"Deficit: VO: %d us VI: %d us BE: %d us BK: %d us\n",
+ 		rx_airtime, tx_airtime, sta->airtime_weight,
+ 		deficit[0], deficit[1], deficit[2], deficit[3]);
+ 
+--- a/net/mac80211/sta_info.h
++++ b/net/mac80211/sta_info.h
+@@ -138,7 +138,7 @@ enum ieee80211_agg_stop_reason {
+ struct airtime_info {
+ 	u64 rx_airtime;
+ 	u64 tx_airtime;
+-	s64 deficit;
++	s32 deficit;
+ 	atomic_t aql_tx_pending; /* Estimated airtime for frames pending */
+ 	u32 aql_limit_low;
+ 	u32 aql_limit_high;
+--- a/net/mac80211/tx.c
++++ b/net/mac80211/tx.c
+@@ -3847,7 +3847,7 @@ struct ieee80211_txq *ieee80211_next_txq
+ 		struct sta_info *sta = container_of(txqi->txq.sta,
+ 						    struct sta_info, sta);
+ 		bool aql_check = ieee80211_txq_airtime_check(hw, &txqi->txq);
+-		s64 deficit = sta->airtime[txqi->txq.ac].deficit;
++		s32 deficit = sta->airtime[txqi->txq.ac].deficit;
+ 
+ 		if (aql_check)
+ 			found_eligible_txq = true;
diff --git a/package/kernel/mac80211/patches/subsys/332-mac80211-consider-aql_tx_pending-when-checking-airti.patch b/package/kernel/mac80211/patches/subsys/332-mac80211-consider-aql_tx_pending-when-checking-airti.patch
new file mode 100644
index 0000000000..c214294603
--- /dev/null
+++ b/package/kernel/mac80211/patches/subsys/332-mac80211-consider-aql_tx_pending-when-checking-airti.patch
@@ -0,0 +1,48 @@
+From: Felix Fietkau <nbd at nbd.name>
+Date: Mon, 20 Jun 2022 14:59:09 +0200
+Subject: [PATCH] mac80211: consider aql_tx_pending when checking airtime
+ deficit
+
+When queueing packets for a station, deficit only gets added once the packets
+have been transmitted, which could be much later. During that time, a lot of
+temporary unfairness could happen, which could lead to bursty behavior.
+Fix this by subtracting the aql_tx_pending when checking the deficit in tx
+scheduling.
+
+Signed-off-by: Felix Fietkau <nbd at nbd.name>
+---
+
+--- a/net/mac80211/tx.c
++++ b/net/mac80211/tx.c
+@@ -3817,6 +3817,13 @@ out:
+ }
+ EXPORT_SYMBOL(ieee80211_tx_dequeue);
+ 
++static inline s32 ieee80211_sta_deficit(struct sta_info *sta, u8 ac)
++{
++	struct airtime_info *air_info = &sta->airtime[ac];
++
++	return air_info->deficit - atomic_read(&air_info->aql_tx_pending);
++}
++
+ struct ieee80211_txq *ieee80211_next_txq(struct ieee80211_hw *hw, u8 ac)
+ {
+ 	struct ieee80211_local *local = hw_to_local(hw);
+@@ -3847,7 +3854,7 @@ struct ieee80211_txq *ieee80211_next_txq
+ 		struct sta_info *sta = container_of(txqi->txq.sta,
+ 						    struct sta_info, sta);
+ 		bool aql_check = ieee80211_txq_airtime_check(hw, &txqi->txq);
+-		s32 deficit = sta->airtime[txqi->txq.ac].deficit;
++		s32 deficit = ieee80211_sta_deficit(sta, txqi->txq.ac);
+ 
+ 		if (aql_check)
+ 			found_eligible_txq = true;
+@@ -3972,7 +3979,7 @@ bool ieee80211_txq_may_transmit(struct i
+ 			continue;
+ 		}
+ 		sta = container_of(iter->txq.sta, struct sta_info, sta);
+-		if (sta->airtime[ac].deficit < 0)
++		if (ieee80211_sta_deficit(sta, ac) < 0)
+ 			sta->airtime[ac].deficit += sta->airtime_weight;
+ 		list_move_tail(&iter->schedule_order, &local->active_txqs[ac]);
+ 	}
diff --git a/package/kernel/mac80211/patches/subsys/333-mac80211-keep-recently-active-tx-queues-in-schedulin.patch b/package/kernel/mac80211/patches/subsys/333-mac80211-keep-recently-active-tx-queues-in-schedulin.patch
new file mode 100644
index 0000000000..317e4f0653
--- /dev/null
+++ b/package/kernel/mac80211/patches/subsys/333-mac80211-keep-recently-active-tx-queues-in-schedulin.patch
@@ -0,0 +1,118 @@
+From: Felix Fietkau <nbd at nbd.name>
+Date: Mon, 20 Jun 2022 20:52:50 +0200
+Subject: [PATCH] mac80211: keep recently active tx queues in scheduling
+ list
+
+This allows proper deficit accounting to ensure that they don't carry their
+deficit until the next time they become active
+
+Signed-off-by: Felix Fietkau <nbd at nbd.name>
+---
+
+--- a/net/mac80211/ieee80211_i.h
++++ b/net/mac80211/ieee80211_i.h
+@@ -83,6 +83,13 @@ extern const u8 ieee80211_ac_to_qos_mask
+ 
+ #define IEEE80211_MAX_NAN_INSTANCE_ID 255
+ 
++
++/*
++ * Keep a station's queues on the active list for deficit accounting purposes
++ * if it was active or queued during the last 100ms
++ */
++#define AIRTIME_ACTIVE_DURATION (HZ / 10)
++
+ struct ieee80211_bss {
+ 	u32 device_ts_beacon, device_ts_presp;
+ 
+--- a/net/mac80211/sta_info.h
++++ b/net/mac80211/sta_info.h
+@@ -138,6 +138,7 @@ enum ieee80211_agg_stop_reason {
+ struct airtime_info {
+ 	u64 rx_airtime;
+ 	u64 tx_airtime;
++	u32 last_active;
+ 	s32 deficit;
+ 	atomic_t aql_tx_pending; /* Estimated airtime for frames pending */
+ 	u32 aql_limit_low;
+--- a/net/mac80211/tx.c
++++ b/net/mac80211/tx.c
+@@ -3824,6 +3824,36 @@ static inline s32 ieee80211_sta_deficit(
+ 	return air_info->deficit - atomic_read(&air_info->aql_tx_pending);
+ }
+ 
++static void
++ieee80211_txq_set_active(struct txq_info *txqi)
++{
++	struct sta_info *sta;
++
++	if (!txqi->txq.sta)
++		return;
++
++	sta = container_of(txqi->txq.sta, struct sta_info, sta);
++	sta->airtime[txqi->txq.ac].last_active = (u32)jiffies;
++}
++
++static bool
++ieee80211_txq_keep_active(struct txq_info *txqi)
++{
++	struct sta_info *sta;
++	u32 diff;
++
++	if (!txqi->txq.sta)
++		return false;
++
++	sta = container_of(txqi->txq.sta, struct sta_info, sta);
++	if (ieee80211_sta_deficit(sta, txqi->txq.ac) >= 0)
++		return false;
++
++	diff = (u32)jiffies - sta->airtime[txqi->txq.ac].last_active;
++
++	return diff <= AIRTIME_ACTIVE_DURATION;
++}
++
+ struct ieee80211_txq *ieee80211_next_txq(struct ieee80211_hw *hw, u8 ac)
+ {
+ 	struct ieee80211_local *local = hw_to_local(hw);
+@@ -3870,7 +3900,6 @@ struct ieee80211_txq *ieee80211_next_txq
+ 		}
+ 	}
+ 
+-
+ 	if (txqi->schedule_round == local->schedule_round[ac])
+ 		goto out;
+ 
+@@ -3890,12 +3919,13 @@ void __ieee80211_schedule_txq(struct iee
+ {
+ 	struct ieee80211_local *local = hw_to_local(hw);
+ 	struct txq_info *txqi = to_txq_info(txq);
++	bool has_queue;
+ 
+ 	spin_lock_bh(&local->active_txq_lock[txq->ac]);
+ 
++	has_queue = force || txq_has_queue(txq);
+ 	if (list_empty(&txqi->schedule_order) &&
+-	    (force || !skb_queue_empty(&txqi->frags) ||
+-	     txqi->tin.backlog_packets)) {
++	    (has_queue || ieee80211_txq_keep_active(txqi))) {
+ 		/* 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
+@@ -3903,7 +3933,7 @@ void __ieee80211_schedule_txq(struct iee
+ 		 * get immediately moved to the back of the list on the next
+ 		 * call to ieee80211_next_txq().
+ 		 */
+-		if (txqi->txq.sta && local->airtime_flags &&
++		if (txqi->txq.sta && local->airtime_flags && has_queue &&
+ 		    wiphy_ext_feature_isset(local->hw.wiphy,
+ 					    NL80211_EXT_FEATURE_AIRTIME_FAIRNESS))
+ 			list_add(&txqi->schedule_order,
+@@ -3911,6 +3941,8 @@ void __ieee80211_schedule_txq(struct iee
+ 		else
+ 			list_add_tail(&txqi->schedule_order,
+ 				      &local->active_txqs[txq->ac]);
++		if (has_queue)
++			ieee80211_txq_set_active(txqi);
+ 	}
+ 
+ 	spin_unlock_bh(&local->active_txq_lock[txq->ac]);
diff --git a/package/kernel/mac80211/patches/subsys/334-mac80211-add-a-per-PHY-AQL-limit-to-improve-fairness.patch b/package/kernel/mac80211/patches/subsys/334-mac80211-add-a-per-PHY-AQL-limit-to-improve-fairness.patch
new file mode 100644
index 0000000000..89718a8273
--- /dev/null
+++ b/package/kernel/mac80211/patches/subsys/334-mac80211-add-a-per-PHY-AQL-limit-to-improve-fairness.patch
@@ -0,0 +1,131 @@
+From: Felix Fietkau <nbd at nbd.name>
+Date: Mon, 20 Jun 2022 21:26:34 +0200
+Subject: [PATCH] mac80211: add a per-PHY AQL limit to improve fairness
+
+In order to maintain fairness, the amount of queueing needs to be limited
+beyond the simple per-station AQL budget, otherwise the driver can simply
+repeatedly do scheduling rounds until all queues that have not used their
+AQL budget become eligble.
+
+To be conservative, use the high AQL limit for the first txq and add half
+of the low AQL for each subsequent queue.
+
+Signed-off-by: Felix Fietkau <nbd at nbd.name>
+---
+
+--- a/net/mac80211/ieee80211_i.h
++++ b/net/mac80211/ieee80211_i.h
+@@ -1211,6 +1211,7 @@ struct ieee80211_local {
+ 	u32 aql_txq_limit_high[IEEE80211_NUM_ACS];
+ 	u32 aql_threshold;
+ 	atomic_t aql_total_pending_airtime;
++	atomic_t aql_ac_pending_airtime[IEEE80211_NUM_ACS];
+ 
+ 	const struct ieee80211_ops *ops;
+ 
+--- a/net/mac80211/main.c
++++ b/net/mac80211/main.c
+@@ -712,6 +712,7 @@ struct ieee80211_hw *ieee80211_alloc_hw_
+ 		local->aql_txq_limit_low[i] = IEEE80211_DEFAULT_AQL_TXQ_LIMIT_L;
+ 		local->aql_txq_limit_high[i] =
+ 			IEEE80211_DEFAULT_AQL_TXQ_LIMIT_H;
++		atomic_set(&local->aql_ac_pending_airtime[i], 0);
+ 	}
+ 
+ 	local->airtime_flags = AIRTIME_USE_TX | AIRTIME_USE_RX;
+--- a/net/mac80211/sta_info.c
++++ b/net/mac80211/sta_info.c
+@@ -1929,6 +1929,7 @@ void ieee80211_sta_update_pending_airtim
+ 				   &sta->airtime[ac].aql_tx_pending);
+ 
+ 		atomic_add(tx_airtime, &local->aql_total_pending_airtime);
++		atomic_add(tx_airtime, &local->aql_ac_pending_airtime[ac]);
+ 		return;
+ 	}
+ 
+@@ -1940,14 +1941,17 @@ void ieee80211_sta_update_pending_airtim
+ 				       tx_pending, 0);
+ 	}
+ 
++	atomic_sub(tx_airtime, &local->aql_total_pending_airtime);
+ 	tx_pending = atomic_sub_return(tx_airtime,
+-				       &local->aql_total_pending_airtime);
++				       &local->aql_ac_pending_airtime[ac]);
+ 	if (WARN_ONCE(tx_pending < 0,
+ 		      "Device %s AC %d pending airtime underflow: %u, %u",
+ 		      wiphy_name(local->hw.wiphy), ac, tx_pending,
+-		      tx_airtime))
+-		atomic_cmpxchg(&local->aql_total_pending_airtime,
++		      tx_airtime)) {
++		atomic_cmpxchg(&local->aql_ac_pending_airtime[ac],
+ 			       tx_pending, 0);
++		atomic_sub(tx_pending, &local->aql_total_pending_airtime);
++	}
+ }
+ 
+ int sta_info_move_state(struct sta_info *sta,
+--- a/net/mac80211/tx.c
++++ b/net/mac80211/tx.c
+@@ -3863,6 +3863,9 @@ struct ieee80211_txq *ieee80211_next_txq
+ 
+ 	spin_lock_bh(&local->active_txq_lock[ac]);
+ 
++	if (!local->schedule_round[ac])
++		goto out;
++
+  begin:
+ 	txqi = list_first_entry_or_null(&local->active_txqs[ac],
+ 					struct txq_info,
+@@ -3984,6 +3987,25 @@ bool ieee80211_txq_airtime_check(struct
+ }
+ EXPORT_SYMBOL(ieee80211_txq_airtime_check);
+ 
++static bool
++ieee80211_txq_schedule_airtime_check(struct ieee80211_local *local, u8 ac)
++{
++	unsigned int num_txq = 0;
++	struct txq_info *txq;
++	u32 aql_limit;
++
++	if (!wiphy_ext_feature_isset(local->hw.wiphy, NL80211_EXT_FEATURE_AQL))
++		return true;
++
++	list_for_each_entry(txq, &local->active_txqs[ac], schedule_order)
++		num_txq++;
++
++	aql_limit = (num_txq - 1) * local->aql_txq_limit_low[ac] / 2 +
++		    local->aql_txq_limit_high[ac];
++
++	return atomic_read(&local->aql_ac_pending_airtime[ac]) < aql_limit;
++}
++
+ bool ieee80211_txq_may_transmit(struct ieee80211_hw *hw,
+ 				struct ieee80211_txq *txq)
+ {
+@@ -4000,6 +4022,9 @@ bool ieee80211_txq_may_transmit(struct i
+ 	if (list_empty(&txqi->schedule_order))
+ 		goto out;
+ 
++	if (!ieee80211_txq_schedule_airtime_check(local, ac))
++		goto out;
++
+ 	list_for_each_entry_safe(iter, tmp, &local->active_txqs[ac],
+ 				 schedule_order) {
+ 		if (iter == txqi)
+@@ -4039,7 +4064,15 @@ void ieee80211_txq_schedule_start(struct
+ 	struct ieee80211_local *local = hw_to_local(hw);
+ 
+ 	spin_lock_bh(&local->active_txq_lock[ac]);
+-	local->schedule_round[ac]++;
++
++	if (ieee80211_txq_schedule_airtime_check(local, ac)) {
++		local->schedule_round[ac]++;
++		if (!local->schedule_round[ac])
++			local->schedule_round[ac]++;
++	} else {
++		local->schedule_round[ac] = 0;
++	}
++
+ 	spin_unlock_bh(&local->active_txq_lock[ac]);
+ }
+ EXPORT_SYMBOL(ieee80211_txq_schedule_start);
diff --git a/package/kernel/mac80211/patches/subsys/335-mac80211-add-debugfs-file-to-display-per-phy-AQL-pen.patch b/package/kernel/mac80211/patches/subsys/335-mac80211-add-debugfs-file-to-display-per-phy-AQL-pen.patch
new file mode 100644
index 0000000000..df45a520fa
--- /dev/null
+++ b/package/kernel/mac80211/patches/subsys/335-mac80211-add-debugfs-file-to-display-per-phy-AQL-pen.patch
@@ -0,0 +1,58 @@
+From: Felix Fietkau <nbd at nbd.name>
+Date: Sat, 25 Jun 2022 21:25:40 +0200
+Subject: [PATCH] mac80211: add debugfs file to display per-phy AQL pending
+ airtime
+
+Now that the global pending airtime is more relevant for airtime fairness,
+it makes sense to make it accessible via debugfs for debugging
+
+Signed-off-by: Felix Fietkau <nbd at nbd.name>
+---
+
+--- a/net/mac80211/debugfs.c
++++ b/net/mac80211/debugfs.c
+@@ -201,6 +201,36 @@ static const struct file_operations airt
+ 	.llseek = default_llseek,
+ };
+ 
++static ssize_t aql_pending_read(struct file *file,
++				char __user *user_buf,
++				size_t count, loff_t *ppos)
++{
++	struct ieee80211_local *local = file->private_data;
++	char buf[400];
++	int len = 0;
++
++	len = scnprintf(buf, sizeof(buf),
++			"AC     AQL pending\n"
++			"VO     %u us\n"
++			"VI     %u us\n"
++			"BE     %u us\n"
++			"BK     %u us\n"
++			"total  %u us\n",
++			atomic_read(&local->aql_ac_pending_airtime[IEEE80211_AC_VO]),
++			atomic_read(&local->aql_ac_pending_airtime[IEEE80211_AC_VI]),
++			atomic_read(&local->aql_ac_pending_airtime[IEEE80211_AC_BE]),
++			atomic_read(&local->aql_ac_pending_airtime[IEEE80211_AC_BK]),
++			atomic_read(&local->aql_total_pending_airtime));
++	return simple_read_from_buffer(user_buf, count, ppos,
++				       buf, len);
++}
++
++static const struct file_operations aql_pending_ops = {
++	.read = aql_pending_read,
++	.open = simple_open,
++	.llseek = default_llseek,
++};
++
+ static ssize_t aql_txq_limit_read(struct file *file,
+ 				  char __user *user_buf,
+ 				  size_t count,
+@@ -628,6 +658,7 @@ void debugfs_hw_add(struct ieee80211_loc
+ 	DEBUGFS_ADD(hw_conf);
+ 	DEBUGFS_ADD_MODE(force_tx_status, 0600);
+ 	DEBUGFS_ADD_MODE(aql_enable, 0600);
++	DEBUGFS_ADD(aql_pending);
+ 
+ 	if (local->ops->wake_tx_queue)
+ 		DEBUGFS_ADD_MODE(aqm, 0600);
diff --git a/package/kernel/mac80211/patches/subsys/336-mac80211-only-accumulate-airtime-deficit-for-active-.patch b/package/kernel/mac80211/patches/subsys/336-mac80211-only-accumulate-airtime-deficit-for-active-.patch
new file mode 100644
index 0000000000..35f07c1a97
--- /dev/null
+++ b/package/kernel/mac80211/patches/subsys/336-mac80211-only-accumulate-airtime-deficit-for-active-.patch
@@ -0,0 +1,36 @@
+From: Felix Fietkau <nbd at nbd.name>
+Date: Sat, 25 Jun 2022 23:10:19 +0200
+Subject: [PATCH] mac80211: only accumulate airtime deficit for active
+ clients
+
+When a client does not generate any local tx activity, accumulating airtime
+deficit for the round-robin scheduler can be harmful. If this goes on for too
+long, the deficit could grow quite large, which might cause unreasonable
+initial latency once the client becomes active
+
+Signed-off-by: Felix Fietkau <nbd at nbd.name>
+---
+
+--- a/net/mac80211/sta_info.c
++++ b/net/mac80211/sta_info.c
+@@ -1900,6 +1900,7 @@ void ieee80211_sta_register_airtime(stru
+ 	struct ieee80211_local *local = sta->sdata->local;
+ 	u8 ac = ieee80211_ac_from_tid(tid);
+ 	u32 airtime = 0;
++	u32 diff;
+ 
+ 	if (sta->local->airtime_flags & AIRTIME_USE_TX)
+ 		airtime += tx_airtime;
+@@ -1909,7 +1910,11 @@ void ieee80211_sta_register_airtime(stru
+ 	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;
++
++	diff = (u32)jiffies - sta->airtime[ac].last_active;
++	if (diff <= AIRTIME_ACTIVE_DURATION)
++		sta->airtime[ac].deficit -= airtime;
++
+ 	spin_unlock_bh(&local->active_txq_lock[ac]);
+ }
+ EXPORT_SYMBOL(ieee80211_sta_register_airtime);
diff --git a/package/kernel/mac80211/patches/subsys/500-mac80211_configure_antenna_gain.patch b/package/kernel/mac80211/patches/subsys/500-mac80211_configure_antenna_gain.patch
index 612b9d66ee..7c598ba3ff 100644
--- a/package/kernel/mac80211/patches/subsys/500-mac80211_configure_antenna_gain.patch
+++ b/package/kernel/mac80211/patches/subsys/500-mac80211_configure_antenna_gain.patch
@@ -57,7 +57,7 @@
  	__NL80211_ATTR_AFTER_LAST,
 --- a/net/mac80211/cfg.c
 +++ b/net/mac80211/cfg.c
-@@ -2845,6 +2845,19 @@ static int ieee80211_get_tx_power(struct
+@@ -2812,6 +2812,19 @@ static int ieee80211_get_tx_power(struct
  	return 0;
  }
  
@@ -77,7 +77,7 @@
  static void ieee80211_rfkill_poll(struct wiphy *wiphy)
  {
  	struct ieee80211_local *local = wiphy_priv(wiphy);
-@@ -4549,6 +4562,7 @@ const struct cfg80211_ops mac80211_confi
+@@ -4516,6 +4529,7 @@ const struct cfg80211_ops mac80211_confi
  	.set_wiphy_params = ieee80211_set_wiphy_params,
  	.set_tx_power = ieee80211_set_tx_power,
  	.get_tx_power = ieee80211_get_tx_power,
@@ -87,7 +87,7 @@
  	CFG80211_TESTMODE_DUMP(ieee80211_testmode_dump)
 --- a/net/mac80211/ieee80211_i.h
 +++ b/net/mac80211/ieee80211_i.h
-@@ -1464,6 +1464,7 @@ struct ieee80211_local {
+@@ -1443,6 +1443,7 @@ struct ieee80211_local {
  	int dynamic_ps_forced_timeout;
  
  	int user_power_level; /* in dBm, for all interfaces */




More information about the lede-commits mailing list