Commit 2433647b authored by Toke Høiland-Jørgensen's avatar Toke Høiland-Jørgensen Committed by Johannes Berg
Browse files

mac80211: Switch to a virtual time-based airtime scheduler



This switches the airtime scheduler in mac80211 to use a virtual
time-based scheduler instead of the round-robin scheduler used before.
This has a couple of advantages:

- No need to sync up the round-robin scheduler in firmware/hardware with
  the round-robin airtime scheduler.

- If several stations are eligible for transmission we can schedule both
  of them; no need to hard-block the scheduling rotation until the head
  of the queue has used up its quantum.

- The check of whether a station is eligible for transmission becomes
  simpler (in ieee80211_txq_may_transmit()).

The drawback is that scheduling becomes slightly more expensive, as we
need to maintain an rbtree of TXQs sorted by virtual time. This means
that ieee80211_register_airtime() becomes O(logN) in the number of
currently scheduled TXQs because it can change the order of the
scheduled stations. We mitigate this overhead by only resorting when a
station changes position in the tree, and hopefully N rarely grows too
big (it's only TXQs currently backlogged, not all associated stations),
so it shouldn't be too big of an issue.

To prevent divisions in the fast path, we maintain both station sums and
pre-computed reciprocals of the sums. This turns the fast-path operation
into a multiplication, with divisions only happening as the number of
active stations change (to re-compute the current sum of all active
station weights). To prevent this re-computation of the reciprocal from
happening too frequently, we use a time-based notion of station
activity, instead of updating the weight every time a station gets
scheduled or de-scheduled. As queues can oscillate between empty and
occupied quite frequently, this can significantly cut down on the number
of re-computations. It also has the added benefit of making the station
airtime calculation independent on whether the queue happened to have
drained at the time an airtime value was accounted.

Co-developed-by: default avatarYibo Zhao <yiboz@codeaurora.org>
Signed-off-by: default avatarYibo Zhao <yiboz@codeaurora.org>
Signed-off-by: default avatarToke Høiland-Jørgensen <toke@redhat.com>
Link: https://lore.kernel.org/r/20210623134755.235545-1-toke@redhat.com


Signed-off-by: default avatarJohannes Berg <johannes.berg@intel.com>
parent 2832943c
Loading
Loading
Loading
Loading
+3 −14
Original line number Diff line number Diff line
@@ -6605,9 +6605,6 @@ static inline void ieee80211_txq_schedule_end(struct ieee80211_hw *hw, u8 ac)
{
}

void __ieee80211_schedule_txq(struct ieee80211_hw *hw,
			      struct ieee80211_txq *txq, bool force);

/**
 * ieee80211_schedule_txq - schedule a TXQ for transmission
 *
@@ -6620,11 +6617,7 @@ void __ieee80211_schedule_txq(struct ieee80211_hw *hw,
 * The driver may call this function if it has buffered packets for
 * this TXQ internally.
 */
static inline void
ieee80211_schedule_txq(struct ieee80211_hw *hw, struct ieee80211_txq *txq)
{
	__ieee80211_schedule_txq(hw, txq, true);
}
void ieee80211_schedule_txq(struct ieee80211_hw *hw, struct ieee80211_txq *txq);

/**
 * ieee80211_return_txq - return a TXQ previously acquired by ieee80211_next_txq()
@@ -6636,12 +6629,8 @@ ieee80211_schedule_txq(struct ieee80211_hw *hw, struct ieee80211_txq *txq)
 * The driver may set force=true if it has buffered packets for this TXQ
 * internally.
 */
static inline void
ieee80211_return_txq(struct ieee80211_hw *hw, struct ieee80211_txq *txq,
		     bool force)
{
	__ieee80211_schedule_txq(hw, txq, force);
}
void ieee80211_return_txq(struct ieee80211_hw *hw, struct ieee80211_txq *txq,
			  bool force);

/**
 * ieee80211_txq_may_transmit - check whether TXQ is allowed to transmit
+34 −1
Original line number Diff line number Diff line
@@ -1442,6 +1442,38 @@ static void sta_apply_mesh_params(struct ieee80211_local *local,
#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)
@@ -1629,7 +1661,8 @@ static int sta_apply_parameters(struct ieee80211_local *local,
		sta_apply_mesh_params(local, sta, params);

	if (params->airtime_weight)
		sta->airtime_weight = params->airtime_weight;
		sta_apply_airtime_params(local, sta, params);


	/* set the STA state after all sta info from usermode has been set */
	if (test_sta_flag(sta, WLAN_STA_TDLS_PEER) ||
+57 −13
Original line number Diff line number Diff line
@@ -216,14 +216,14 @@ static ssize_t aql_txq_limit_read(struct file *file,
			"VI	%u		%u\n"
			"BE	%u		%u\n"
			"BK	%u		%u\n",
			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]);
			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);
	return simple_read_from_buffer(user_buf, count, ppos,
				       buf, len);
}
@@ -255,11 +255,11 @@ static ssize_t aql_txq_limit_write(struct file *file,
	if (ac >= IEEE80211_NUM_ACS)
		return -EINVAL;

	q_limit_low_old = local->aql_txq_limit_low[ac];
	q_limit_high_old = local->aql_txq_limit_high[ac];
	q_limit_low_old = local->airtime[ac].aql_txq_limit_low;
	q_limit_high_old = local->airtime[ac].aql_txq_limit_high;

	local->aql_txq_limit_low[ac] = q_limit_low;
	local->aql_txq_limit_high[ac] = q_limit_high;
	local->airtime[ac].aql_txq_limit_low = q_limit_low;
	local->airtime[ac].aql_txq_limit_high = q_limit_high;

	mutex_lock(&local->sta_mtx);
	list_for_each_entry(sta, &local->sta_list, list) {
@@ -382,6 +382,46 @@ static const struct file_operations force_tx_status_ops = {
	.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)
@@ -632,7 +672,11 @@ void debugfs_hw_add(struct ieee80211_local *local)
	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(aql_txq_limit);
	debugfs_create_u32("aql_threshold", 0600,
+31 −1
Original line number Diff line number Diff line
@@ -512,6 +512,34 @@ 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 */
@@ -657,8 +685,10 @@ static void add_common_files(struct ieee80211_sub_if_data *sdata)

	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)
+12 −12
Original line number Diff line number Diff line
@@ -202,7 +202,7 @@ static ssize_t sta_airtime_read(struct file *file, char __user *userbuf,
	size_t bufsz = 400;
	char *buf = kzalloc(bufsz, GFP_KERNEL), *p = buf;
	u64 rx_airtime = 0, tx_airtime = 0;
	s64 deficit[IEEE80211_NUM_ACS];
	u64 v_t[IEEE80211_NUM_ACS];
	ssize_t rv;
	int ac;

@@ -210,18 +210,18 @@ static ssize_t sta_airtime_read(struct file *file, char __user *userbuf,
		return -ENOMEM;

	for (ac = 0; ac < IEEE80211_NUM_ACS; ac++) {
		spin_lock_bh(&local->active_txq_lock[ac]);
		spin_lock_bh(&local->airtime[ac].lock);
		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]);
		v_t[ac] = sta->airtime[ac].v_t;
		spin_unlock_bh(&local->airtime[ac].lock);
	}

	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]);
		"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]);

	rv = simple_read_from_buffer(userbuf, count, ppos, buf, p - buf);
	kfree(buf);
@@ -236,11 +236,11 @@ static ssize_t sta_airtime_write(struct file *file, const char __user *userbuf,
	int ac;

	for (ac = 0; ac < IEEE80211_NUM_ACS; ac++) {
		spin_lock_bh(&local->active_txq_lock[ac]);
		spin_lock_bh(&local->airtime[ac].lock);
		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]);
		sta->airtime[ac].v_t = 0;
		spin_unlock_bh(&local->airtime[ac].lock);
	}

	return count;
@@ -263,10 +263,10 @@ static ssize_t sta_aql_read(struct file *file, char __user *userbuf,
		return -ENOMEM;

	for (ac = 0; ac < IEEE80211_NUM_ACS; ac++) {
		spin_lock_bh(&local->active_txq_lock[ac]);
		spin_lock_bh(&local->airtime[ac].lock);
		q_limit_l[ac] = sta->airtime[ac].aql_limit_low;
		q_limit_h[ac] = sta->airtime[ac].aql_limit_high;
		spin_unlock_bh(&local->active_txq_lock[ac]);
		spin_unlock_bh(&local->airtime[ac].lock);
		q_depth[ac] = atomic_read(&sta->airtime[ac].aql_tx_pending);
	}

Loading