mac80211: backport airtime queue limits support
Signed-off-by: Felix Fietkau <nbd@nbd.name>
This commit is contained in:
		| @@ -0,0 +1,78 @@ | ||||
| From: John Crispin <john@phrozen.org> | ||||
| Date: Tue, 29 Oct 2019 10:13:02 +0100 | ||||
| Subject: [PATCH] mac80211: move store skb ack code to its own function | ||||
|  | ||||
| This patch moves the code handling SKBTX_WIFI_STATUS inside the TX path | ||||
| into an extra function. This allows us to reuse it inside the 802.11 encap | ||||
| offloading datapath. | ||||
|  | ||||
| Signed-off-by: John Crispin <john@phrozen.org> | ||||
| Link: https://lore.kernel.org/r/20191029091304.7330-2-john@phrozen.org | ||||
| Signed-off-by: Johannes Berg <johannes.berg@intel.com> | ||||
| --- | ||||
|  | ||||
| --- a/net/mac80211/tx.c | ||||
| +++ b/net/mac80211/tx.c | ||||
| @@ -2430,6 +2430,33 @@ static int ieee80211_lookup_ra_sta(struc | ||||
|  	return 0; | ||||
|  } | ||||
|   | ||||
| +static int ieee80211_store_ack_skb(struct ieee80211_local *local, | ||||
| +				   struct sk_buff *skb, | ||||
| +				   u32 *info_flags) | ||||
| +{ | ||||
| +	struct sk_buff *ack_skb = skb_clone_sk(skb); | ||||
| +	u16 info_id = 0; | ||||
| + | ||||
| +	if (ack_skb) { | ||||
| +		unsigned long flags; | ||||
| +		int id; | ||||
| + | ||||
| +		spin_lock_irqsave(&local->ack_status_lock, flags); | ||||
| +		id = idr_alloc(&local->ack_status_frames, ack_skb, | ||||
| +			       1, 0x10000, GFP_ATOMIC); | ||||
| +		spin_unlock_irqrestore(&local->ack_status_lock, flags); | ||||
| + | ||||
| +		if (id >= 0) { | ||||
| +			info_id = id; | ||||
| +			*info_flags |= IEEE80211_TX_CTL_REQ_TX_STATUS; | ||||
| +		} else { | ||||
| +			kfree_skb(ack_skb); | ||||
| +		} | ||||
| +	} | ||||
| + | ||||
| +	return info_id; | ||||
| +} | ||||
| + | ||||
|  /** | ||||
|   * ieee80211_build_hdr - build 802.11 header in the given frame | ||||
|   * @sdata: virtual interface to build the header for | ||||
| @@ -2723,26 +2750,8 @@ static struct sk_buff *ieee80211_build_h | ||||
|  	} | ||||
|   | ||||
|  	if (unlikely(!multicast && skb->sk && | ||||
| -		     skb_shinfo(skb)->tx_flags & SKBTX_WIFI_STATUS)) { | ||||
| -		struct sk_buff *ack_skb = skb_clone_sk(skb); | ||||
| - | ||||
| -		if (ack_skb) { | ||||
| -			unsigned long flags; | ||||
| -			int id; | ||||
| - | ||||
| -			spin_lock_irqsave(&local->ack_status_lock, flags); | ||||
| -			id = idr_alloc(&local->ack_status_frames, ack_skb, | ||||
| -				       1, 0x10000, GFP_ATOMIC); | ||||
| -			spin_unlock_irqrestore(&local->ack_status_lock, flags); | ||||
| - | ||||
| -			if (id >= 0) { | ||||
| -				info_id = id; | ||||
| -				info_flags |= IEEE80211_TX_CTL_REQ_TX_STATUS; | ||||
| -			} else { | ||||
| -				kfree_skb(ack_skb); | ||||
| -			} | ||||
| -		} | ||||
| -	} | ||||
| +		     skb_shinfo(skb)->tx_flags & SKBTX_WIFI_STATUS)) | ||||
| +		info_id = ieee80211_store_ack_skb(local, skb, &info_flags); | ||||
|   | ||||
|  	/* | ||||
|  	 * If the skb is shared we need to obtain our own copy. | ||||
| @@ -0,0 +1,67 @@ | ||||
| From: =?UTF-8?q?Toke=20H=C3=B8iland-J=C3=B8rgensen?= <toke@redhat.com> | ||||
| Date: Wed, 23 Oct 2019 11:59:00 +0200 | ||||
| Subject: [PATCH] mac80211: Shrink the size of ack_frame_id to make room for | ||||
|  tx_time_est | ||||
| MIME-Version: 1.0 | ||||
| Content-Type: text/plain; charset=UTF-8 | ||||
| Content-Transfer-Encoding: 8bit | ||||
|  | ||||
| To implement airtime queue limiting, we need to keep a running account of | ||||
| the estimated airtime of all skbs queued into the device. Do to this | ||||
| correctly, we need to store the airtime estimate into the skb so we can | ||||
| decrease the outstanding balance when the skb is freed. This means that the | ||||
| time estimate must be stored somewhere that will survive for the lifetime | ||||
| of the skb. | ||||
|  | ||||
| To get this, decrease the size of the ack_frame_id field to 6 bits, and | ||||
| lower the size of the ID space accordingly. This leaves 10 bits for use for | ||||
| tx_time_est, which is enough to store a maximum of 4096 us, if we shift the | ||||
| values so they become units of 4us. | ||||
|  | ||||
| Signed-off-by: Toke Høiland-Jørgensen <toke@redhat.com> | ||||
| Link: https://lore.kernel.org/r/157182474063.150713.16132669599100802716.stgit@toke.dk | ||||
| Signed-off-by: Johannes Berg <johannes.berg@intel.com> | ||||
| --- | ||||
|  | ||||
| --- a/include/net/mac80211.h | ||||
| +++ b/include/net/mac80211.h | ||||
| @@ -967,6 +967,7 @@ ieee80211_rate_get_vht_nss(const struct | ||||
|   * @band: the band to transmit on (use for checking for races) | ||||
|   * @hw_queue: HW queue to put the frame on, skb_get_queue_mapping() gives the AC | ||||
|   * @ack_frame_id: internal frame ID for TX status, used internally | ||||
| + * @tx_time_est: TX time estimate in units of 4us, used internally | ||||
|   * @control: union part for control data | ||||
|   * @control.rates: TX rates array to try | ||||
|   * @control.rts_cts_rate_idx: rate for RTS or CTS | ||||
| @@ -1007,7 +1008,8 @@ struct ieee80211_tx_info { | ||||
|   | ||||
|  	u8 hw_queue; | ||||
|   | ||||
| -	u16 ack_frame_id; | ||||
| +	u16 ack_frame_id:6; | ||||
| +	u16 tx_time_est:10; | ||||
|   | ||||
|  	union { | ||||
|  		struct { | ||||
| --- a/net/mac80211/cfg.c | ||||
| +++ b/net/mac80211/cfg.c | ||||
| @@ -3427,7 +3427,7 @@ int ieee80211_attach_ack_skb(struct ieee | ||||
|   | ||||
|  	spin_lock_irqsave(&local->ack_status_lock, spin_flags); | ||||
|  	id = idr_alloc(&local->ack_status_frames, ack_skb, | ||||
| -		       1, 0x10000, GFP_ATOMIC); | ||||
| +		       1, 0x40, GFP_ATOMIC); | ||||
|  	spin_unlock_irqrestore(&local->ack_status_lock, spin_flags); | ||||
|   | ||||
|  	if (id < 0) { | ||||
| --- a/net/mac80211/tx.c | ||||
| +++ b/net/mac80211/tx.c | ||||
| @@ -2443,7 +2443,7 @@ static int ieee80211_store_ack_skb(struc | ||||
|   | ||||
|  		spin_lock_irqsave(&local->ack_status_lock, flags); | ||||
|  		id = idr_alloc(&local->ack_status_frames, ack_skb, | ||||
| -			       1, 0x10000, GFP_ATOMIC); | ||||
| +			       1, 0x40, GFP_ATOMIC); | ||||
|  		spin_unlock_irqrestore(&local->ack_status_lock, flags); | ||||
|   | ||||
|  		if (id >= 0) { | ||||
| @@ -0,0 +1,78 @@ | ||||
| From: =?UTF-8?q?Toke=20H=C3=B8iland-J=C3=B8rgensen?= <toke@redhat.com> | ||||
| Date: Tue, 12 Nov 2019 14:08:35 +0100 | ||||
| Subject: [PATCH] mac80211: Add new sta_info getter by sta/vif addrs | ||||
| MIME-Version: 1.0 | ||||
| Content-Type: text/plain; charset=UTF-8 | ||||
| Content-Transfer-Encoding: 8bit | ||||
|  | ||||
| In ieee80211_tx_status() we don't have an sdata struct when looking up the | ||||
| destination sta. Instead, we just do a lookup by the vif addr that is the | ||||
| source of the packet being completed. Factor this out into a new sta_info | ||||
| getter helper, since we need to use it for accounting AQL as well. | ||||
|  | ||||
| Signed-off-by: Toke Høiland-Jørgensen <toke@redhat.com> | ||||
| Link: https://lore.kernel.org/r/20191112130835.382062-1-toke@redhat.com | ||||
| [remove internal rcu_read_lock(), document instead] | ||||
| Signed-off-by: Johannes Berg <johannes.berg@intel.com> | ||||
| --- | ||||
|  | ||||
| --- a/net/mac80211/sta_info.c | ||||
| +++ b/net/mac80211/sta_info.c | ||||
| @@ -210,6 +210,20 @@ struct sta_info *sta_info_get_bss(struct | ||||
|  	return NULL; | ||||
|  } | ||||
|   | ||||
| +struct sta_info *sta_info_get_by_addrs(struct ieee80211_local *local, | ||||
| +				       const u8 *sta_addr, const u8 *vif_addr) | ||||
| +{ | ||||
| +	struct rhlist_head *tmp; | ||||
| +	struct sta_info *sta; | ||||
| + | ||||
| +	for_each_sta_info(local, sta_addr, sta, tmp) { | ||||
| +		if (ether_addr_equal(vif_addr, sta->sdata->vif.addr)) | ||||
| +			return sta; | ||||
| +	} | ||||
| + | ||||
| +	return NULL; | ||||
| +} | ||||
| + | ||||
|  struct sta_info *sta_info_get_by_idx(struct ieee80211_sub_if_data *sdata, | ||||
|  				     int idx) | ||||
|  { | ||||
| --- a/net/mac80211/sta_info.h | ||||
| +++ b/net/mac80211/sta_info.h | ||||
| @@ -725,6 +725,10 @@ struct sta_info *sta_info_get(struct iee | ||||
|  struct sta_info *sta_info_get_bss(struct ieee80211_sub_if_data *sdata, | ||||
|  				  const u8 *addr); | ||||
|   | ||||
| +/* user must hold sta_mtx or be in RCU critical section */ | ||||
| +struct sta_info *sta_info_get_by_addrs(struct ieee80211_local *local, | ||||
| +				       const u8 *sta_addr, const u8 *vif_addr); | ||||
| + | ||||
|  #define for_each_sta_info(local, _addr, _sta, _tmp)			\ | ||||
|  	rhl_for_each_entry_rcu(_sta, _tmp,				\ | ||||
|  			       sta_info_hash_lookup(local, _addr), hash_node) | ||||
| --- a/net/mac80211/status.c | ||||
| +++ b/net/mac80211/status.c | ||||
| @@ -1085,19 +1085,13 @@ void ieee80211_tx_status(struct ieee8021 | ||||
|  		.skb = skb, | ||||
|  		.info = IEEE80211_SKB_CB(skb), | ||||
|  	}; | ||||
| -	struct rhlist_head *tmp; | ||||
|  	struct sta_info *sta; | ||||
|   | ||||
|  	rcu_read_lock(); | ||||
|   | ||||
| -	for_each_sta_info(local, hdr->addr1, sta, tmp) { | ||||
| -		/* skip wrong virtual interface */ | ||||
| -		if (!ether_addr_equal(hdr->addr2, sta->sdata->vif.addr)) | ||||
| -			continue; | ||||
| - | ||||
| +	sta = sta_info_get_by_addrs(local, hdr->addr1, hdr->addr2); | ||||
| +	if (sta) | ||||
|  		status.sta = &sta->sta; | ||||
| -		break; | ||||
| -	} | ||||
|   | ||||
|  	__ieee80211_tx_status(hw, &status); | ||||
|  	rcu_read_unlock(); | ||||
| @@ -0,0 +1,690 @@ | ||||
| From: =?UTF-8?q?Toke=20H=C3=B8iland-J=C3=B8rgensen?= <toke@redhat.com> | ||||
| Date: Mon, 18 Nov 2019 22:06:08 -0800 | ||||
| Subject: [PATCH] mac80211: Import airtime calculation code from mt76 | ||||
| MIME-Version: 1.0 | ||||
| Content-Type: text/plain; charset=UTF-8 | ||||
| Content-Transfer-Encoding: 8bit | ||||
|  | ||||
| Felix recently added code to calculate airtime of packets to the mt76 | ||||
| driver. Import this into mac80211 so we can use it for airtime queue limit | ||||
| calculations. | ||||
|  | ||||
| The airtime.c file is copied verbatim from the mt76 driver, and adjusted to | ||||
| be usable in mac80211. This involves: | ||||
|  | ||||
| - Switching to mac80211 data structures. | ||||
| - Adding support for 160 MHz channels and HE mode. | ||||
| - Moving the symbol and duration calculations around a bit to avoid | ||||
|   rounding with the higher rates and longer symbol times used for HE rates. | ||||
|  | ||||
| The per-rate TX rate calculation is also split out to its own function so | ||||
| it can be used directly for the AQL calculations later. | ||||
|  | ||||
| Signed-off-by: Toke Høiland-Jørgensen <toke@redhat.com> | ||||
| Link: https://lore.kernel.org/r/20191119060610.76681-3-kyan@google.com | ||||
| [fix HE_GROUP_IDX() to use 3 * bw, since there are 3 _gi values] | ||||
| Signed-off-by: Johannes Berg <johannes.berg@intel.com> | ||||
| --- | ||||
|  create mode 100644 net/mac80211/airtime.c | ||||
|  | ||||
| --- a/include/net/mac80211.h | ||||
| +++ b/include/net/mac80211.h | ||||
| @@ -6417,4 +6417,33 @@ void ieee80211_nan_func_match(struct iee | ||||
|  			      struct cfg80211_nan_match_params *match, | ||||
|  			      gfp_t gfp); | ||||
|   | ||||
| +/** | ||||
| + * ieee80211_calc_rx_airtime - calculate estimated transmission airtime for RX. | ||||
| + * | ||||
| + * This function calculates the estimated airtime usage of a frame based on the | ||||
| + * rate information in the RX status struct and the frame length. | ||||
| + * | ||||
| + * @hw: pointer as obtained from ieee80211_alloc_hw() | ||||
| + * @status: &struct ieee80211_rx_status containing the transmission rate | ||||
| + *          information. | ||||
| + * @len: frame length in bytes | ||||
| + */ | ||||
| +u32 ieee80211_calc_rx_airtime(struct ieee80211_hw *hw, | ||||
| +			      struct ieee80211_rx_status *status, | ||||
| +			      int len); | ||||
| + | ||||
| +/** | ||||
| + * ieee80211_calc_tx_airtime - calculate estimated transmission airtime for TX. | ||||
| + * | ||||
| + * This function calculates the estimated airtime usage of a frame based on the | ||||
| + * rate information in the TX info struct and the frame length. | ||||
| + * | ||||
| + * @hw: pointer as obtained from ieee80211_alloc_hw() | ||||
| + * @info: &struct ieee80211_tx_info of the frame. | ||||
| + * @len: frame length in bytes | ||||
| + */ | ||||
| +u32 ieee80211_calc_tx_airtime(struct ieee80211_hw *hw, | ||||
| +			      struct ieee80211_tx_info *info, | ||||
| +			      int len); | ||||
| + | ||||
|  #endif /* MAC80211_H */ | ||||
| --- a/net/mac80211/Makefile | ||||
| +++ b/net/mac80211/Makefile | ||||
| @@ -31,7 +31,8 @@ mac80211-y := \ | ||||
|  	chan.o \ | ||||
|  	trace.o mlme.o \ | ||||
|  	tdls.o \ | ||||
| -	ocb.o | ||||
| +	ocb.o \ | ||||
| +	airtime.o | ||||
|   | ||||
|  mac80211-$(CPTCFG_MAC80211_LEDS) += led.o | ||||
|  mac80211-$(CPTCFG_MAC80211_DEBUGFS) += \ | ||||
| --- /dev/null | ||||
| +++ b/net/mac80211/airtime.c | ||||
| @@ -0,0 +1,597 @@ | ||||
| +// SPDX-License-Identifier: ISC | ||||
| +/* | ||||
| + * Copyright (C) 2019 Felix Fietkau <nbd@nbd.name> | ||||
| + */ | ||||
| + | ||||
| +#include <net/mac80211.h> | ||||
| +#include "ieee80211_i.h" | ||||
| +#include "sta_info.h" | ||||
| + | ||||
| +#define AVG_PKT_SIZE	1024 | ||||
| + | ||||
| +/* Number of bits for an average sized packet */ | ||||
| +#define MCS_NBITS (AVG_PKT_SIZE << 3) | ||||
| + | ||||
| +/* Number of kilo-symbols (symbols * 1024) for a packet with (bps) bits per | ||||
| + * symbol. We use k-symbols to avoid rounding in the _TIME macros below. | ||||
| + */ | ||||
| +#define MCS_N_KSYMS(bps) DIV_ROUND_UP(MCS_NBITS << 10, (bps)) | ||||
| + | ||||
| +/* Transmission time (in 1024 * usec) for a packet containing (ksyms) * 1024 | ||||
| + * symbols. | ||||
| + */ | ||||
| +#define MCS_SYMBOL_TIME(sgi, ksyms)					\ | ||||
| +	(sgi ?								\ | ||||
| +	  ((ksyms) * 4 * 18) / 20 :		/* 3.6 us per sym */	\ | ||||
| +	  ((ksyms) * 4)			/* 4.0 us per sym */	\ | ||||
| +	) | ||||
| + | ||||
| +/* Transmit duration for the raw data part of an average sized packet */ | ||||
| +#define MCS_DURATION(streams, sgi, bps) \ | ||||
| +	((u32)MCS_SYMBOL_TIME(sgi, MCS_N_KSYMS((streams) * (bps)))) | ||||
| + | ||||
| +#define MCS_DURATION_S(shift, streams, sgi, bps)		\ | ||||
| +	((u16)((MCS_DURATION(streams, sgi, bps) >> shift))) | ||||
| + | ||||
| +/* These should match the values in enum nl80211_he_gi */ | ||||
| +#define HE_GI_08 0 | ||||
| +#define HE_GI_16 1 | ||||
| +#define HE_GI_32 2 | ||||
| + | ||||
| +/* Transmission time (1024 usec) for a packet containing (ksyms) * k-symbols */ | ||||
| +#define HE_SYMBOL_TIME(gi, ksyms)					\ | ||||
| +	(gi == HE_GI_08 ?						\ | ||||
| +	 ((ksyms) * 16 * 17) / 20 :		/* 13.6 us per sym */	\ | ||||
| +	 (gi == HE_GI_16 ?						\ | ||||
| +	  ((ksyms) * 16 * 18) / 20 :		/* 14.4 us per sym */	\ | ||||
| +	  ((ksyms) * 16)			/* 16.0 us per sym */	\ | ||||
| +	 )) | ||||
| + | ||||
| +/* Transmit duration for the raw data part of an average sized packet */ | ||||
| +#define HE_DURATION(streams, gi, bps) \ | ||||
| +	((u32)HE_SYMBOL_TIME(gi, MCS_N_KSYMS((streams) * (bps)))) | ||||
| + | ||||
| +#define HE_DURATION_S(shift, streams, gi, bps)		\ | ||||
| +	(HE_DURATION(streams, gi, bps) >> shift) | ||||
| + | ||||
| +#define BW_20			0 | ||||
| +#define BW_40			1 | ||||
| +#define BW_80			2 | ||||
| +#define BW_160			3 | ||||
| + | ||||
| +/* | ||||
| + * Define group sort order: HT40 -> SGI -> #streams | ||||
| + */ | ||||
| +#define IEEE80211_MAX_STREAMS		4 | ||||
| +#define IEEE80211_HT_STREAM_GROUPS	4 /* BW(=2) * SGI(=2) */ | ||||
| +#define IEEE80211_VHT_STREAM_GROUPS	8 /* BW(=4) * SGI(=2) */ | ||||
| + | ||||
| +#define IEEE80211_HE_MAX_STREAMS	8 | ||||
| +#define IEEE80211_HE_STREAM_GROUPS	12 /* BW(=4) * GI(=3) */ | ||||
| + | ||||
| +#define IEEE80211_HT_GROUPS_NB	(IEEE80211_MAX_STREAMS *	\ | ||||
| +				 IEEE80211_HT_STREAM_GROUPS) | ||||
| +#define IEEE80211_VHT_GROUPS_NB	(IEEE80211_MAX_STREAMS *	\ | ||||
| +					 IEEE80211_VHT_STREAM_GROUPS) | ||||
| +#define IEEE80211_HE_GROUPS_NB	(IEEE80211_HE_MAX_STREAMS *	\ | ||||
| +				 IEEE80211_HE_STREAM_GROUPS) | ||||
| +#define IEEE80211_GROUPS_NB	(IEEE80211_HT_GROUPS_NB +	\ | ||||
| +				 IEEE80211_VHT_GROUPS_NB +	\ | ||||
| +				 IEEE80211_HE_GROUPS_NB) | ||||
| + | ||||
| +#define IEEE80211_HT_GROUP_0	0 | ||||
| +#define IEEE80211_VHT_GROUP_0	(IEEE80211_HT_GROUP_0 + IEEE80211_HT_GROUPS_NB) | ||||
| +#define IEEE80211_HE_GROUP_0	(IEEE80211_VHT_GROUP_0 + IEEE80211_VHT_GROUPS_NB) | ||||
| + | ||||
| +#define MCS_GROUP_RATES		12 | ||||
| + | ||||
| +#define HT_GROUP_IDX(_streams, _sgi, _ht40)	\ | ||||
| +	IEEE80211_HT_GROUP_0 +			\ | ||||
| +	IEEE80211_MAX_STREAMS * 2 * _ht40 +	\ | ||||
| +	IEEE80211_MAX_STREAMS * _sgi +		\ | ||||
| +	_streams - 1 | ||||
| + | ||||
| +#define _MAX(a, b) (((a)>(b))?(a):(b)) | ||||
| + | ||||
| +#define GROUP_SHIFT(duration)						\ | ||||
| +	_MAX(0, 16 - __builtin_clz(duration)) | ||||
| + | ||||
| +/* MCS rate information for an MCS group */ | ||||
| +#define __MCS_GROUP(_streams, _sgi, _ht40, _s)				\ | ||||
| +	[HT_GROUP_IDX(_streams, _sgi, _ht40)] = {			\ | ||||
| +	.shift = _s,							\ | ||||
| +	.duration = {							\ | ||||
| +		MCS_DURATION_S(_s, _streams, _sgi, _ht40 ? 54 : 26),	\ | ||||
| +		MCS_DURATION_S(_s, _streams, _sgi, _ht40 ? 108 : 52),	\ | ||||
| +		MCS_DURATION_S(_s, _streams, _sgi, _ht40 ? 162 : 78),	\ | ||||
| +		MCS_DURATION_S(_s, _streams, _sgi, _ht40 ? 216 : 104),	\ | ||||
| +		MCS_DURATION_S(_s, _streams, _sgi, _ht40 ? 324 : 156),	\ | ||||
| +		MCS_DURATION_S(_s, _streams, _sgi, _ht40 ? 432 : 208),	\ | ||||
| +		MCS_DURATION_S(_s, _streams, _sgi, _ht40 ? 486 : 234),	\ | ||||
| +		MCS_DURATION_S(_s, _streams, _sgi, _ht40 ? 540 : 260)	\ | ||||
| +	}								\ | ||||
| +} | ||||
| + | ||||
| +#define MCS_GROUP_SHIFT(_streams, _sgi, _ht40)				\ | ||||
| +	GROUP_SHIFT(MCS_DURATION(_streams, _sgi, _ht40 ? 54 : 26)) | ||||
| + | ||||
| +#define MCS_GROUP(_streams, _sgi, _ht40)				\ | ||||
| +	__MCS_GROUP(_streams, _sgi, _ht40,				\ | ||||
| +		    MCS_GROUP_SHIFT(_streams, _sgi, _ht40)) | ||||
| + | ||||
| +#define VHT_GROUP_IDX(_streams, _sgi, _bw)				\ | ||||
| +	(IEEE80211_VHT_GROUP_0 +					\ | ||||
| +	 IEEE80211_MAX_STREAMS * 2 * (_bw) +				\ | ||||
| +	 IEEE80211_MAX_STREAMS * (_sgi) +				\ | ||||
| +	 (_streams) - 1) | ||||
| + | ||||
| +#define BW2VBPS(_bw, r4, r3, r2, r1)					\ | ||||
| +	(_bw == BW_160 ? r4 : _bw == BW_80 ? r3 : _bw == BW_40 ? r2 : r1) | ||||
| + | ||||
| +#define __VHT_GROUP(_streams, _sgi, _bw, _s)				\ | ||||
| +	[VHT_GROUP_IDX(_streams, _sgi, _bw)] = {			\ | ||||
| +	.shift = _s,							\ | ||||
| +	.duration = {							\ | ||||
| +		MCS_DURATION_S(_s, _streams, _sgi,			\ | ||||
| +			       BW2VBPS(_bw,  234,  117,  54,  26)),	\ | ||||
| +		MCS_DURATION_S(_s, _streams, _sgi,			\ | ||||
| +			       BW2VBPS(_bw,  468,  234, 108,  52)),	\ | ||||
| +		MCS_DURATION_S(_s, _streams, _sgi,			\ | ||||
| +			       BW2VBPS(_bw,  702,  351, 162,  78)),	\ | ||||
| +		MCS_DURATION_S(_s, _streams, _sgi,			\ | ||||
| +			       BW2VBPS(_bw,  936,  468, 216, 104)),	\ | ||||
| +		MCS_DURATION_S(_s, _streams, _sgi,			\ | ||||
| +			       BW2VBPS(_bw, 1404,  702, 324, 156)),	\ | ||||
| +		MCS_DURATION_S(_s, _streams, _sgi,			\ | ||||
| +			       BW2VBPS(_bw, 1872,  936, 432, 208)),	\ | ||||
| +		MCS_DURATION_S(_s, _streams, _sgi,			\ | ||||
| +			       BW2VBPS(_bw, 2106, 1053, 486, 234)),	\ | ||||
| +		MCS_DURATION_S(_s, _streams, _sgi,			\ | ||||
| +			       BW2VBPS(_bw, 2340, 1170, 540, 260)),	\ | ||||
| +		MCS_DURATION_S(_s, _streams, _sgi,			\ | ||||
| +			       BW2VBPS(_bw, 2808, 1404, 648, 312)),	\ | ||||
| +		MCS_DURATION_S(_s, _streams, _sgi,			\ | ||||
| +			       BW2VBPS(_bw, 3120, 1560, 720, 346))	\ | ||||
| +        }								\ | ||||
| +} | ||||
| + | ||||
| +#define VHT_GROUP_SHIFT(_streams, _sgi, _bw)				\ | ||||
| +	GROUP_SHIFT(MCS_DURATION(_streams, _sgi,			\ | ||||
| +				 BW2VBPS(_bw, 243, 117,  54,  26))) | ||||
| + | ||||
| +#define VHT_GROUP(_streams, _sgi, _bw)					\ | ||||
| +	__VHT_GROUP(_streams, _sgi, _bw,				\ | ||||
| +		    VHT_GROUP_SHIFT(_streams, _sgi, _bw)) | ||||
| + | ||||
| + | ||||
| +#define HE_GROUP_IDX(_streams, _gi, _bw)				\ | ||||
| +	(IEEE80211_HE_GROUP_0 +					\ | ||||
| +	 IEEE80211_HE_MAX_STREAMS * 3 * (_bw) +			\ | ||||
| +	 IEEE80211_HE_MAX_STREAMS * (_gi) +				\ | ||||
| +	 (_streams) - 1) | ||||
| + | ||||
| +#define __HE_GROUP(_streams, _gi, _bw, _s)				\ | ||||
| +	[HE_GROUP_IDX(_streams, _gi, _bw)] = {			\ | ||||
| +	.shift = _s,							\ | ||||
| +	.duration = {							\ | ||||
| +		HE_DURATION_S(_s, _streams, _gi,			\ | ||||
| +			      BW2VBPS(_bw,   979,  489,  230,  115)),	\ | ||||
| +		HE_DURATION_S(_s, _streams, _gi,			\ | ||||
| +			      BW2VBPS(_bw,  1958,  979,  475,  230)),	\ | ||||
| +		HE_DURATION_S(_s, _streams, _gi,			\ | ||||
| +			      BW2VBPS(_bw,  2937, 1468,  705,  345)),	\ | ||||
| +		HE_DURATION_S(_s, _streams, _gi,			\ | ||||
| +			      BW2VBPS(_bw,  3916, 1958,  936,  475)),	\ | ||||
| +		HE_DURATION_S(_s, _streams, _gi,			\ | ||||
| +			      BW2VBPS(_bw,  5875, 2937, 1411,  705)),	\ | ||||
| +		HE_DURATION_S(_s, _streams, _gi,			\ | ||||
| +			      BW2VBPS(_bw,  7833, 3916, 1872,  936)),	\ | ||||
| +		HE_DURATION_S(_s, _streams, _gi,			\ | ||||
| +			      BW2VBPS(_bw,  8827, 4406, 2102, 1051)),	\ | ||||
| +		HE_DURATION_S(_s, _streams, _gi,			\ | ||||
| +			      BW2VBPS(_bw,  9806, 4896, 2347, 1166)),	\ | ||||
| +		HE_DURATION_S(_s, _streams, _gi,			\ | ||||
| +			      BW2VBPS(_bw, 11764, 5875, 2808, 1411)),	\ | ||||
| +		HE_DURATION_S(_s, _streams, _gi,			\ | ||||
| +			      BW2VBPS(_bw, 13060, 6523, 3124, 1555)),	\ | ||||
| +		HE_DURATION_S(_s, _streams, _gi,			\ | ||||
| +			      BW2VBPS(_bw, 14702, 7344, 3513, 1756)),	\ | ||||
| +		HE_DURATION_S(_s, _streams, _gi,			\ | ||||
| +			      BW2VBPS(_bw, 16329, 8164, 3902, 1944))	\ | ||||
| +        }								\ | ||||
| +} | ||||
| + | ||||
| +#define HE_GROUP_SHIFT(_streams, _gi, _bw)				\ | ||||
| +	GROUP_SHIFT(HE_DURATION(_streams, _gi,			\ | ||||
| +				BW2VBPS(_bw,   979,  489,  230,  115))) | ||||
| + | ||||
| +#define HE_GROUP(_streams, _gi, _bw)					\ | ||||
| +	__HE_GROUP(_streams, _gi, _bw,				\ | ||||
| +		   HE_GROUP_SHIFT(_streams, _gi, _bw)) | ||||
| +struct mcs_group { | ||||
| +	u8 shift; | ||||
| +	u16 duration[MCS_GROUP_RATES]; | ||||
| +}; | ||||
| + | ||||
| +static const struct mcs_group airtime_mcs_groups[] = { | ||||
| +	MCS_GROUP(1, 0, BW_20), | ||||
| +	MCS_GROUP(2, 0, BW_20), | ||||
| +	MCS_GROUP(3, 0, BW_20), | ||||
| +	MCS_GROUP(4, 0, BW_20), | ||||
| + | ||||
| +	MCS_GROUP(1, 1, BW_20), | ||||
| +	MCS_GROUP(2, 1, BW_20), | ||||
| +	MCS_GROUP(3, 1, BW_20), | ||||
| +	MCS_GROUP(4, 1, BW_20), | ||||
| + | ||||
| +	MCS_GROUP(1, 0, BW_40), | ||||
| +	MCS_GROUP(2, 0, BW_40), | ||||
| +	MCS_GROUP(3, 0, BW_40), | ||||
| +	MCS_GROUP(4, 0, BW_40), | ||||
| + | ||||
| +	MCS_GROUP(1, 1, BW_40), | ||||
| +	MCS_GROUP(2, 1, BW_40), | ||||
| +	MCS_GROUP(3, 1, BW_40), | ||||
| +	MCS_GROUP(4, 1, BW_40), | ||||
| + | ||||
| +	VHT_GROUP(1, 0, BW_20), | ||||
| +	VHT_GROUP(2, 0, BW_20), | ||||
| +	VHT_GROUP(3, 0, BW_20), | ||||
| +	VHT_GROUP(4, 0, BW_20), | ||||
| + | ||||
| +	VHT_GROUP(1, 1, BW_20), | ||||
| +	VHT_GROUP(2, 1, BW_20), | ||||
| +	VHT_GROUP(3, 1, BW_20), | ||||
| +	VHT_GROUP(4, 1, BW_20), | ||||
| + | ||||
| +	VHT_GROUP(1, 0, BW_40), | ||||
| +	VHT_GROUP(2, 0, BW_40), | ||||
| +	VHT_GROUP(3, 0, BW_40), | ||||
| +	VHT_GROUP(4, 0, BW_40), | ||||
| + | ||||
| +	VHT_GROUP(1, 1, BW_40), | ||||
| +	VHT_GROUP(2, 1, BW_40), | ||||
| +	VHT_GROUP(3, 1, BW_40), | ||||
| +	VHT_GROUP(4, 1, BW_40), | ||||
| + | ||||
| +	VHT_GROUP(1, 0, BW_80), | ||||
| +	VHT_GROUP(2, 0, BW_80), | ||||
| +	VHT_GROUP(3, 0, BW_80), | ||||
| +	VHT_GROUP(4, 0, BW_80), | ||||
| + | ||||
| +	VHT_GROUP(1, 1, BW_80), | ||||
| +	VHT_GROUP(2, 1, BW_80), | ||||
| +	VHT_GROUP(3, 1, BW_80), | ||||
| +	VHT_GROUP(4, 1, BW_80), | ||||
| + | ||||
| +	VHT_GROUP(1, 0, BW_160), | ||||
| +	VHT_GROUP(2, 0, BW_160), | ||||
| +	VHT_GROUP(3, 0, BW_160), | ||||
| +	VHT_GROUP(4, 0, BW_160), | ||||
| + | ||||
| +	VHT_GROUP(1, 1, BW_160), | ||||
| +	VHT_GROUP(2, 1, BW_160), | ||||
| +	VHT_GROUP(3, 1, BW_160), | ||||
| +	VHT_GROUP(4, 1, BW_160), | ||||
| + | ||||
| +	HE_GROUP(1, HE_GI_08, BW_20), | ||||
| +	HE_GROUP(2, HE_GI_08, BW_20), | ||||
| +	HE_GROUP(3, HE_GI_08, BW_20), | ||||
| +	HE_GROUP(4, HE_GI_08, BW_20), | ||||
| +	HE_GROUP(5, HE_GI_08, BW_20), | ||||
| +	HE_GROUP(6, HE_GI_08, BW_20), | ||||
| +	HE_GROUP(7, HE_GI_08, BW_20), | ||||
| +	HE_GROUP(8, HE_GI_08, BW_20), | ||||
| + | ||||
| +	HE_GROUP(1, HE_GI_16, BW_20), | ||||
| +	HE_GROUP(2, HE_GI_16, BW_20), | ||||
| +	HE_GROUP(3, HE_GI_16, BW_20), | ||||
| +	HE_GROUP(4, HE_GI_16, BW_20), | ||||
| +	HE_GROUP(5, HE_GI_16, BW_20), | ||||
| +	HE_GROUP(6, HE_GI_16, BW_20), | ||||
| +	HE_GROUP(7, HE_GI_16, BW_20), | ||||
| +	HE_GROUP(8, HE_GI_16, BW_20), | ||||
| + | ||||
| +	HE_GROUP(1, HE_GI_32, BW_20), | ||||
| +	HE_GROUP(2, HE_GI_32, BW_20), | ||||
| +	HE_GROUP(3, HE_GI_32, BW_20), | ||||
| +	HE_GROUP(4, HE_GI_32, BW_20), | ||||
| +	HE_GROUP(5, HE_GI_32, BW_20), | ||||
| +	HE_GROUP(6, HE_GI_32, BW_20), | ||||
| +	HE_GROUP(7, HE_GI_32, BW_20), | ||||
| +	HE_GROUP(8, HE_GI_32, BW_20), | ||||
| + | ||||
| +	HE_GROUP(1, HE_GI_08, BW_40), | ||||
| +	HE_GROUP(2, HE_GI_08, BW_40), | ||||
| +	HE_GROUP(3, HE_GI_08, BW_40), | ||||
| +	HE_GROUP(4, HE_GI_08, BW_40), | ||||
| +	HE_GROUP(5, HE_GI_08, BW_40), | ||||
| +	HE_GROUP(6, HE_GI_08, BW_40), | ||||
| +	HE_GROUP(7, HE_GI_08, BW_40), | ||||
| +	HE_GROUP(8, HE_GI_08, BW_40), | ||||
| + | ||||
| +	HE_GROUP(1, HE_GI_16, BW_40), | ||||
| +	HE_GROUP(2, HE_GI_16, BW_40), | ||||
| +	HE_GROUP(3, HE_GI_16, BW_40), | ||||
| +	HE_GROUP(4, HE_GI_16, BW_40), | ||||
| +	HE_GROUP(5, HE_GI_16, BW_40), | ||||
| +	HE_GROUP(6, HE_GI_16, BW_40), | ||||
| +	HE_GROUP(7, HE_GI_16, BW_40), | ||||
| +	HE_GROUP(8, HE_GI_16, BW_40), | ||||
| + | ||||
| +	HE_GROUP(1, HE_GI_32, BW_40), | ||||
| +	HE_GROUP(2, HE_GI_32, BW_40), | ||||
| +	HE_GROUP(3, HE_GI_32, BW_40), | ||||
| +	HE_GROUP(4, HE_GI_32, BW_40), | ||||
| +	HE_GROUP(5, HE_GI_32, BW_40), | ||||
| +	HE_GROUP(6, HE_GI_32, BW_40), | ||||
| +	HE_GROUP(7, HE_GI_32, BW_40), | ||||
| +	HE_GROUP(8, HE_GI_32, BW_40), | ||||
| + | ||||
| +	HE_GROUP(1, HE_GI_08, BW_80), | ||||
| +	HE_GROUP(2, HE_GI_08, BW_80), | ||||
| +	HE_GROUP(3, HE_GI_08, BW_80), | ||||
| +	HE_GROUP(4, HE_GI_08, BW_80), | ||||
| +	HE_GROUP(5, HE_GI_08, BW_80), | ||||
| +	HE_GROUP(6, HE_GI_08, BW_80), | ||||
| +	HE_GROUP(7, HE_GI_08, BW_80), | ||||
| +	HE_GROUP(8, HE_GI_08, BW_80), | ||||
| + | ||||
| +	HE_GROUP(1, HE_GI_16, BW_80), | ||||
| +	HE_GROUP(2, HE_GI_16, BW_80), | ||||
| +	HE_GROUP(3, HE_GI_16, BW_80), | ||||
| +	HE_GROUP(4, HE_GI_16, BW_80), | ||||
| +	HE_GROUP(5, HE_GI_16, BW_80), | ||||
| +	HE_GROUP(6, HE_GI_16, BW_80), | ||||
| +	HE_GROUP(7, HE_GI_16, BW_80), | ||||
| +	HE_GROUP(8, HE_GI_16, BW_80), | ||||
| + | ||||
| +	HE_GROUP(1, HE_GI_32, BW_80), | ||||
| +	HE_GROUP(2, HE_GI_32, BW_80), | ||||
| +	HE_GROUP(3, HE_GI_32, BW_80), | ||||
| +	HE_GROUP(4, HE_GI_32, BW_80), | ||||
| +	HE_GROUP(5, HE_GI_32, BW_80), | ||||
| +	HE_GROUP(6, HE_GI_32, BW_80), | ||||
| +	HE_GROUP(7, HE_GI_32, BW_80), | ||||
| +	HE_GROUP(8, HE_GI_32, BW_80), | ||||
| + | ||||
| +	HE_GROUP(1, HE_GI_08, BW_160), | ||||
| +	HE_GROUP(2, HE_GI_08, BW_160), | ||||
| +	HE_GROUP(3, HE_GI_08, BW_160), | ||||
| +	HE_GROUP(4, HE_GI_08, BW_160), | ||||
| +	HE_GROUP(5, HE_GI_08, BW_160), | ||||
| +	HE_GROUP(6, HE_GI_08, BW_160), | ||||
| +	HE_GROUP(7, HE_GI_08, BW_160), | ||||
| +	HE_GROUP(8, HE_GI_08, BW_160), | ||||
| + | ||||
| +	HE_GROUP(1, HE_GI_16, BW_160), | ||||
| +	HE_GROUP(2, HE_GI_16, BW_160), | ||||
| +	HE_GROUP(3, HE_GI_16, BW_160), | ||||
| +	HE_GROUP(4, HE_GI_16, BW_160), | ||||
| +	HE_GROUP(5, HE_GI_16, BW_160), | ||||
| +	HE_GROUP(6, HE_GI_16, BW_160), | ||||
| +	HE_GROUP(7, HE_GI_16, BW_160), | ||||
| +	HE_GROUP(8, HE_GI_16, BW_160), | ||||
| + | ||||
| +	HE_GROUP(1, HE_GI_32, BW_160), | ||||
| +	HE_GROUP(2, HE_GI_32, BW_160), | ||||
| +	HE_GROUP(3, HE_GI_32, BW_160), | ||||
| +	HE_GROUP(4, HE_GI_32, BW_160), | ||||
| +	HE_GROUP(5, HE_GI_32, BW_160), | ||||
| +	HE_GROUP(6, HE_GI_32, BW_160), | ||||
| +	HE_GROUP(7, HE_GI_32, BW_160), | ||||
| +	HE_GROUP(8, HE_GI_32, BW_160), | ||||
| +}; | ||||
| + | ||||
| +static u32 | ||||
| +ieee80211_calc_legacy_rate_duration(u16 bitrate, bool short_pre, | ||||
| +				    bool cck, int len) | ||||
| +{ | ||||
| +	u32 duration; | ||||
| + | ||||
| +	if (cck) { | ||||
| +		duration = 144 + 48; /* preamble + PLCP */ | ||||
| +		if (short_pre) | ||||
| +			duration >>= 1; | ||||
| + | ||||
| +		duration += 10; /* SIFS */ | ||||
| +	} else { | ||||
| +		duration = 20 + 16; /* premable + SIFS */ | ||||
| +	} | ||||
| + | ||||
| +	len <<= 3; | ||||
| +	duration += (len * 10) / bitrate; | ||||
| + | ||||
| +	return duration; | ||||
| +} | ||||
| + | ||||
| +u32 ieee80211_calc_rx_airtime(struct ieee80211_hw *hw, | ||||
| +			      struct ieee80211_rx_status *status, | ||||
| +			      int len) | ||||
| +{ | ||||
| +	struct ieee80211_supported_band *sband; | ||||
| +	const struct ieee80211_rate *rate; | ||||
| +	bool sgi = status->enc_flags & RX_ENC_FLAG_SHORT_GI; | ||||
| +	bool sp = status->enc_flags & RX_ENC_FLAG_SHORTPRE; | ||||
| +	int bw, streams; | ||||
| +	int group, idx; | ||||
| +	u32 duration; | ||||
| +	bool cck; | ||||
| + | ||||
| +	switch (status->bw) { | ||||
| +	case RATE_INFO_BW_20: | ||||
| +		bw = BW_20; | ||||
| +		break; | ||||
| +	case RATE_INFO_BW_40: | ||||
| +		bw = BW_40; | ||||
| +		break; | ||||
| +	case RATE_INFO_BW_80: | ||||
| +		bw = BW_80; | ||||
| +		break; | ||||
| +	case RATE_INFO_BW_160: | ||||
| +		bw = BW_160; | ||||
| +		break; | ||||
| +	default: | ||||
| +		WARN_ON_ONCE(1); | ||||
| +		return 0; | ||||
| +	} | ||||
| + | ||||
| +	switch (status->encoding) { | ||||
| +	case RX_ENC_LEGACY: | ||||
| +		if (WARN_ON_ONCE(status->band > NL80211_BAND_5GHZ)) | ||||
| +			return 0; | ||||
| + | ||||
| +		sband = hw->wiphy->bands[status->band]; | ||||
| +		if (!sband || status->rate_idx > sband->n_bitrates) | ||||
| +			return 0; | ||||
| + | ||||
| +		rate = &sband->bitrates[status->rate_idx]; | ||||
| +		cck = rate->flags & IEEE80211_RATE_MANDATORY_B; | ||||
| + | ||||
| +		return ieee80211_calc_legacy_rate_duration(rate->bitrate, sp, | ||||
| +							   cck, len); | ||||
| + | ||||
| +	case RX_ENC_VHT: | ||||
| +		streams = status->nss; | ||||
| +		idx = status->rate_idx; | ||||
| +		group = VHT_GROUP_IDX(streams, sgi, bw); | ||||
| +		break; | ||||
| +	case RX_ENC_HT: | ||||
| +		streams = ((status->rate_idx >> 3) & 3) + 1; | ||||
| +		idx = status->rate_idx & 7; | ||||
| +		group = HT_GROUP_IDX(streams, sgi, bw); | ||||
| +		break; | ||||
| +	case RX_ENC_HE: | ||||
| +		streams = status->nss; | ||||
| +		idx = status->rate_idx; | ||||
| +		group = HE_GROUP_IDX(streams, status->he_gi, bw); | ||||
| +		break; | ||||
| +	default: | ||||
| +		WARN_ON_ONCE(1); | ||||
| +		return 0; | ||||
| +	} | ||||
| + | ||||
| +	if (WARN_ON_ONCE((status->encoding != RX_ENC_HE && streams > 4) || | ||||
| +			 (status->encoding == RX_ENC_HE && streams > 8))) | ||||
| +		return 0; | ||||
| + | ||||
| +	duration = airtime_mcs_groups[group].duration[idx]; | ||||
| +	duration <<= airtime_mcs_groups[group].shift; | ||||
| +	duration *= len; | ||||
| +	duration /= AVG_PKT_SIZE; | ||||
| +	duration /= 1024; | ||||
| + | ||||
| +	duration += 36 + (streams << 2); | ||||
| + | ||||
| +	return duration; | ||||
| +} | ||||
| +EXPORT_SYMBOL_GPL(ieee80211_calc_rx_airtime); | ||||
| + | ||||
| +static u32 ieee80211_calc_tx_airtime_rate(struct ieee80211_hw *hw, | ||||
| +					  struct ieee80211_tx_rate *rate, | ||||
| +					  u8 band, int len) | ||||
| +{ | ||||
| +	struct ieee80211_rx_status stat = { | ||||
| +		.band = band, | ||||
| +	}; | ||||
| + | ||||
| +	if (rate->idx < 0 || !rate->count) | ||||
| +		return 0; | ||||
| + | ||||
| +	if (rate->flags & IEEE80211_TX_RC_80_MHZ_WIDTH) | ||||
| +		stat.bw = RATE_INFO_BW_80; | ||||
| +	else if (rate->flags & IEEE80211_TX_RC_40_MHZ_WIDTH) | ||||
| +		stat.bw = RATE_INFO_BW_40; | ||||
| +	else | ||||
| +		stat.bw = RATE_INFO_BW_20; | ||||
| + | ||||
| +	stat.enc_flags = 0; | ||||
| +	if (rate->flags & IEEE80211_TX_RC_USE_SHORT_PREAMBLE) | ||||
| +		stat.enc_flags |= RX_ENC_FLAG_SHORTPRE; | ||||
| +	if (rate->flags & IEEE80211_TX_RC_SHORT_GI) | ||||
| +		stat.enc_flags |= RX_ENC_FLAG_SHORT_GI; | ||||
| + | ||||
| +	stat.rate_idx = rate->idx; | ||||
| +	if (rate->flags & IEEE80211_TX_RC_VHT_MCS) { | ||||
| +		stat.encoding = RX_ENC_VHT; | ||||
| +		stat.rate_idx = ieee80211_rate_get_vht_mcs(rate); | ||||
| +		stat.nss = ieee80211_rate_get_vht_nss(rate); | ||||
| +	} else if (rate->flags & IEEE80211_TX_RC_MCS) { | ||||
| +		stat.encoding = RX_ENC_HT; | ||||
| +	} else { | ||||
| +		stat.encoding = RX_ENC_LEGACY; | ||||
| +	} | ||||
| + | ||||
| +	return ieee80211_calc_rx_airtime(hw, &stat, len); | ||||
| +} | ||||
| + | ||||
| +u32 ieee80211_calc_tx_airtime(struct ieee80211_hw *hw, | ||||
| +			      struct ieee80211_tx_info *info, | ||||
| +			      int len) | ||||
| +{ | ||||
| +	u32 duration = 0; | ||||
| +	int i; | ||||
| + | ||||
| +	for (i = 0; i < ARRAY_SIZE(info->status.rates); i++) { | ||||
| +		struct ieee80211_tx_rate *rate = &info->status.rates[i]; | ||||
| +		u32 cur_duration; | ||||
| + | ||||
| +		cur_duration = ieee80211_calc_tx_airtime_rate(hw, rate, | ||||
| +							      info->band, len); | ||||
| +		if (!cur_duration) | ||||
| +			break; | ||||
| + | ||||
| +		duration += cur_duration * rate->count; | ||||
| +	} | ||||
| + | ||||
| +	return duration; | ||||
| +} | ||||
| +EXPORT_SYMBOL_GPL(ieee80211_calc_tx_airtime); | ||||
| + | ||||
| +u32 ieee80211_calc_expected_tx_airtime(struct ieee80211_hw *hw, | ||||
| +				       struct ieee80211_vif *vif, | ||||
| +				       struct ieee80211_sta *pubsta, | ||||
| +				       int len) | ||||
| +{ | ||||
| +	struct ieee80211_supported_band *sband; | ||||
| +	struct ieee80211_chanctx_conf *conf; | ||||
| +	int rateidx, shift = 0; | ||||
| +	bool cck, short_pream; | ||||
| +	u32 basic_rates; | ||||
| +	u8 band = 0; | ||||
| +	u16 rate; | ||||
| + | ||||
| +	len += 38; /* Ethernet header length */ | ||||
| + | ||||
| +	conf = rcu_dereference(vif->chanctx_conf); | ||||
| +	if (conf) { | ||||
| +		band = conf->def.chan->band; | ||||
| +		shift = ieee80211_chandef_get_shift(&conf->def); | ||||
| +	} | ||||
| + | ||||
| +	if (pubsta) { | ||||
| +		struct sta_info *sta = container_of(pubsta, struct sta_info, | ||||
| +						    sta); | ||||
| + | ||||
| +		return ieee80211_calc_tx_airtime_rate(hw, | ||||
| +						      &sta->tx_stats.last_rate, | ||||
| +						      band, len); | ||||
| +	} | ||||
| + | ||||
| +	if (!conf) | ||||
| +		return 0; | ||||
| + | ||||
| +	/* No station to get latest rate from, so calculate the worst-case | ||||
| +	 * duration using the lowest configured basic rate. | ||||
| +	 */ | ||||
| +	sband = hw->wiphy->bands[band]; | ||||
| + | ||||
| +	basic_rates = vif->bss_conf.basic_rates; | ||||
| +	short_pream = vif->bss_conf.use_short_preamble; | ||||
| + | ||||
| +	rateidx = basic_rates ? ffs(basic_rates) - 1 : 0; | ||||
| +	rate = sband->bitrates[rateidx].bitrate << shift; | ||||
| +	cck = sband->bitrates[rateidx].flags & IEEE80211_RATE_MANDATORY_B; | ||||
| + | ||||
| +	return ieee80211_calc_legacy_rate_duration(rate, short_pream, cck, len); | ||||
| +} | ||||
| --- a/net/mac80211/ieee80211_i.h | ||||
| +++ b/net/mac80211/ieee80211_i.h | ||||
| @@ -2253,6 +2253,10 @@ const char *ieee80211_get_reason_code_st | ||||
|   | ||||
|  extern const struct ethtool_ops ieee80211_ethtool_ops; | ||||
|   | ||||
| +u32 ieee80211_calc_expected_tx_airtime(struct ieee80211_hw *hw, | ||||
| +				       struct ieee80211_vif *vif, | ||||
| +				       struct ieee80211_sta *pubsta, | ||||
| +				       int len); | ||||
|  #ifdef CPTCFG_MAC80211_NOINLINE | ||||
|  #define debug_noinline noinline | ||||
|  #else | ||||
| @@ -0,0 +1,446 @@ | ||||
| From: Kan Yan <kyan@google.com> | ||||
| Date: Mon, 18 Nov 2019 22:06:09 -0800 | ||||
| Subject: [PATCH] mac80211: Implement Airtime-based Queue Limit (AQL) | ||||
| MIME-Version: 1.0 | ||||
| Content-Type: text/plain; charset=UTF-8 | ||||
| Content-Transfer-Encoding: 8bit | ||||
|  | ||||
| In order for the Fq_CoDel algorithm integrated in mac80211 layer to operate | ||||
| effectively to control excessive queueing latency, the CoDel algorithm | ||||
| requires an accurate measure of how long packets stays in the queue, AKA | ||||
| sojourn time. The sojourn time measured at the mac80211 layer doesn't | ||||
| include queueing latency in the lower layer (firmware/hardware) and CoDel | ||||
| expects lower layer to have a short queue. However, most 802.11ac chipsets | ||||
| offload tasks such TX aggregation to firmware or hardware, thus have a deep | ||||
| lower layer queue. | ||||
|  | ||||
| Without a mechanism to control the lower layer queue size, packets only | ||||
| stay in mac80211 layer transiently before being sent to firmware queue. | ||||
| As a result, the sojourn time measured by CoDel in the mac80211 layer is | ||||
| almost always lower than the CoDel latency target, hence CoDel does little | ||||
| to control the latency, even when the lower layer queue causes excessive | ||||
| latency. | ||||
|  | ||||
| The Byte Queue Limits (BQL) mechanism is commonly used to address the | ||||
| similar issue with wired network interface. However, this method cannot be | ||||
| applied directly to the wireless network interface. "Bytes" is not a | ||||
| suitable measure of queue depth in the wireless network, as the data rate | ||||
| can vary dramatically from station to station in the same network, from a | ||||
| few Mbps to over Gbps. | ||||
|  | ||||
| This patch implements an Airtime-based Queue Limit (AQL) to make CoDel work | ||||
| effectively with wireless drivers that utilized firmware/hardware | ||||
| offloading. AQL allows each txq to release just enough packets to the lower | ||||
| layer to form 1-2 large aggregations to keep hardware fully utilized and | ||||
| retains the rest of the frames in mac80211 layer to be controlled by the | ||||
| CoDel algorithm. | ||||
|  | ||||
| Signed-off-by: Kan Yan <kyan@google.com> | ||||
| [ Toke: Keep API to set pending airtime internal, fix nits in commit msg ] | ||||
| Signed-off-by: Toke Høiland-Jørgensen <toke@redhat.com> | ||||
| Link: https://lore.kernel.org/r/20191119060610.76681-4-kyan@google.com | ||||
| Signed-off-by: Johannes Berg <johannes.berg@intel.com> | ||||
| --- | ||||
|  | ||||
| --- a/include/net/cfg80211.h | ||||
| +++ b/include/net/cfg80211.h | ||||
| @@ -2603,6 +2603,13 @@ enum wiphy_params_flags { | ||||
|   | ||||
|  #define IEEE80211_DEFAULT_AIRTIME_WEIGHT	256 | ||||
|   | ||||
| +/* The per TXQ device queue limit in airtime */ | ||||
| +#define IEEE80211_DEFAULT_AQL_TXQ_LIMIT_L	5000 | ||||
| +#define IEEE80211_DEFAULT_AQL_TXQ_LIMIT_H	12000 | ||||
| + | ||||
| +/* The per interface airtime threshold to switch to lower queue limit */ | ||||
| +#define IEEE80211_AQL_THRESHOLD			24000 | ||||
| + | ||||
|  /** | ||||
|   * struct cfg80211_pmksa - PMK Security Association | ||||
|   * | ||||
| --- a/include/net/mac80211.h | ||||
| +++ b/include/net/mac80211.h | ||||
| @@ -5559,6 +5559,18 @@ void ieee80211_sta_register_airtime(stru | ||||
|  				    u32 tx_airtime, u32 rx_airtime); | ||||
|   | ||||
|  /** | ||||
| + * ieee80211_txq_airtime_check - check if a txq can send frame to device | ||||
| + * | ||||
| + * @hw: pointer obtained from ieee80211_alloc_hw() | ||||
| + * @txq: pointer obtained from station or virtual interface | ||||
| + * | ||||
| + * Return true if the AQL's airtime limit has not been reached and the txq can | ||||
| + * continue to send more packets to the device. Otherwise return false. | ||||
| + */ | ||||
| +bool | ||||
| +ieee80211_txq_airtime_check(struct ieee80211_hw *hw, struct ieee80211_txq *txq); | ||||
| + | ||||
| +/** | ||||
|   * ieee80211_iter_keys - iterate keys programmed into the device | ||||
|   * @hw: pointer obtained from ieee80211_alloc_hw() | ||||
|   * @vif: virtual interface to iterate, may be %NULL for all | ||||
| --- a/net/mac80211/debugfs.c | ||||
| +++ b/net/mac80211/debugfs.c | ||||
| @@ -148,6 +148,87 @@ static const struct file_operations aqm_ | ||||
|  	.llseek = default_llseek, | ||||
|  }; | ||||
|   | ||||
| +static ssize_t aql_txq_limit_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 limit low	AQL limit high\n" | ||||
| +			"VO	%u		%u\n" | ||||
| +			"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]); | ||||
| +	return simple_read_from_buffer(user_buf, count, ppos, | ||||
| +				       buf, len); | ||||
| +} | ||||
| + | ||||
| +static ssize_t aql_txq_limit_write(struct file *file, | ||||
| +				   const char __user *user_buf, | ||||
| +				   size_t count, | ||||
| +				   loff_t *ppos) | ||||
| +{ | ||||
| +	struct ieee80211_local *local = file->private_data; | ||||
| +	char buf[100]; | ||||
| +	size_t len; | ||||
| +	u32 ac, q_limit_low, q_limit_high, q_limit_low_old, q_limit_high_old; | ||||
| +	struct sta_info *sta; | ||||
| + | ||||
| +	if (count > sizeof(buf)) | ||||
| +		return -EINVAL; | ||||
| + | ||||
| +	if (copy_from_user(buf, user_buf, count)) | ||||
| +		return -EFAULT; | ||||
| + | ||||
| +	buf[sizeof(buf) - 1] = 0; | ||||
| +	len = strlen(buf); | ||||
| +	if (len > 0 && buf[len - 1] == '\n') | ||||
| +		buf[len - 1] = 0; | ||||
| + | ||||
| +	if (sscanf(buf, "%u %u %u", &ac, &q_limit_low, &q_limit_high) != 3) | ||||
| +		return -EINVAL; | ||||
| + | ||||
| +	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]; | ||||
| + | ||||
| +	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) { | ||||
| +		/* If a sta has customized queue limits, keep it */ | ||||
| +		if (sta->airtime[ac].aql_limit_low == q_limit_low_old && | ||||
| +		    sta->airtime[ac].aql_limit_high == q_limit_high_old) { | ||||
| +			sta->airtime[ac].aql_limit_low = q_limit_low; | ||||
| +			sta->airtime[ac].aql_limit_high = q_limit_high; | ||||
| +		} | ||||
| +	} | ||||
| +	mutex_unlock(&local->sta_mtx); | ||||
| +	return count; | ||||
| +} | ||||
| + | ||||
| +static const struct file_operations aql_txq_limit_ops = { | ||||
| +	.write = aql_txq_limit_write, | ||||
| +	.read = aql_txq_limit_read, | ||||
| +	.open = simple_open, | ||||
| +	.llseek = default_llseek, | ||||
| +}; | ||||
| + | ||||
|  static ssize_t force_tx_status_read(struct file *file, | ||||
|  				    char __user *user_buf, | ||||
|  				    size_t count, | ||||
| @@ -441,6 +522,10 @@ void debugfs_hw_add(struct ieee80211_loc | ||||
|  	debugfs_create_u16("airtime_flags", 0600, | ||||
|  			   phyd, &local->airtime_flags); | ||||
|   | ||||
| +	DEBUGFS_ADD(aql_txq_limit); | ||||
| +	debugfs_create_u32("aql_threshold", 0600, | ||||
| +			   phyd, &local->aql_threshold); | ||||
| + | ||||
|  	statsd = debugfs_create_dir("statistics", phyd); | ||||
|   | ||||
|  	/* if the dir failed, don't put all the other things into the root! */ | ||||
| --- a/net/mac80211/debugfs_sta.c | ||||
| +++ b/net/mac80211/debugfs_sta.c | ||||
| @@ -197,10 +197,12 @@ static ssize_t sta_airtime_read(struct f | ||||
|  { | ||||
|  	struct sta_info *sta = file->private_data; | ||||
|  	struct ieee80211_local *local = sta->sdata->local; | ||||
| -	size_t bufsz = 200; | ||||
| +	size_t bufsz = 400; | ||||
|  	char *buf = kzalloc(bufsz, GFP_KERNEL), *p = buf; | ||||
|  	u64 rx_airtime = 0, tx_airtime = 0; | ||||
|  	s64 deficit[IEEE80211_NUM_ACS]; | ||||
| +	u32 q_depth[IEEE80211_NUM_ACS]; | ||||
| +	u32 q_limit_l[IEEE80211_NUM_ACS], q_limit_h[IEEE80211_NUM_ACS]; | ||||
|  	ssize_t rv; | ||||
|  	int ac; | ||||
|   | ||||
| @@ -212,19 +214,22 @@ static ssize_t sta_airtime_read(struct f | ||||
|  		rx_airtime += sta->airtime[ac].rx_airtime; | ||||
|  		tx_airtime += sta->airtime[ac].tx_airtime; | ||||
|  		deficit[ac] = sta->airtime[ac].deficit; | ||||
| +		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]); | ||||
| +		q_depth[ac] = atomic_read(&sta->airtime[ac].aql_tx_pending); | ||||
|  	} | ||||
|   | ||||
|  	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]); | ||||
| +		"Deficit: VO: %lld us VI: %lld us BE: %lld us BK: %lld us\n" | ||||
| +		"Q depth: VO: %u us VI: %u us BE: %u us BK: %u us\n" | ||||
| +		"Q limit[low/high]: VO: %u/%u VI: %u/%u BE: %u/%u BK: %u/%u\n", | ||||
| +		rx_airtime, tx_airtime, sta->airtime_weight, | ||||
| +		deficit[0], deficit[1], deficit[2], deficit[3], | ||||
| +		q_depth[0], q_depth[1], q_depth[2], q_depth[3], | ||||
| +		q_limit_l[0], q_limit_h[0], q_limit_l[1], q_limit_h[1], | ||||
| +		q_limit_l[2], q_limit_h[2], q_limit_l[3], q_limit_h[3]), | ||||
|   | ||||
|  	rv = simple_read_from_buffer(userbuf, count, ppos, buf, p - buf); | ||||
|  	kfree(buf); | ||||
| @@ -236,7 +241,25 @@ static ssize_t sta_airtime_write(struct | ||||
|  { | ||||
|  	struct sta_info *sta = file->private_data; | ||||
|  	struct ieee80211_local *local = sta->sdata->local; | ||||
| -	int ac; | ||||
| +	u32 ac, q_limit_l, q_limit_h; | ||||
| +	char _buf[100] = {}, *buf = _buf; | ||||
| + | ||||
| +	if (count > sizeof(_buf)) | ||||
| +		return -EINVAL; | ||||
| + | ||||
| +	if (copy_from_user(buf, userbuf, count)) | ||||
| +		return -EFAULT; | ||||
| + | ||||
| +	buf[sizeof(_buf) - 1] = '\0'; | ||||
| +	if (sscanf(buf, "queue limit %u %u %u", &ac, &q_limit_l, &q_limit_h) | ||||
| +	    != 3) | ||||
| +		return -EINVAL; | ||||
| + | ||||
| +	if (ac >= IEEE80211_NUM_ACS) | ||||
| +		return -EINVAL; | ||||
| + | ||||
| +	sta->airtime[ac].aql_limit_low = q_limit_l; | ||||
| +	sta->airtime[ac].aql_limit_high = q_limit_h; | ||||
|   | ||||
|  	for (ac = 0; ac < IEEE80211_NUM_ACS; ac++) { | ||||
|  		spin_lock_bh(&local->active_txq_lock[ac]); | ||||
| --- a/net/mac80211/ieee80211_i.h | ||||
| +++ b/net/mac80211/ieee80211_i.h | ||||
| @@ -1142,6 +1142,10 @@ struct ieee80211_local { | ||||
|  	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; | ||||
|   | ||||
|  	const struct ieee80211_ops *ops; | ||||
|   | ||||
| --- a/net/mac80211/main.c | ||||
| +++ b/net/mac80211/main.c | ||||
| @@ -669,8 +669,16 @@ struct ieee80211_hw *ieee80211_alloc_hw_ | ||||
|  	for (i = 0; i < IEEE80211_NUM_ACS; i++) { | ||||
|  		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; | ||||
|  	} | ||||
| -	local->airtime_flags = AIRTIME_USE_TX | AIRTIME_USE_RX; | ||||
| + | ||||
| +	local->airtime_flags = AIRTIME_USE_TX | | ||||
| +			       AIRTIME_USE_RX | | ||||
| +			       AIRTIME_USE_AQL; | ||||
| +	local->aql_threshold = IEEE80211_AQL_THRESHOLD; | ||||
| +	atomic_set(&local->aql_total_pending_airtime, 0); | ||||
|   | ||||
|  	INIT_LIST_HEAD(&local->chanctx_list); | ||||
|  	mutex_init(&local->chanctx_mtx); | ||||
| --- a/net/mac80211/sta_info.c | ||||
| +++ b/net/mac80211/sta_info.c | ||||
| @@ -411,6 +411,9 @@ struct sta_info *sta_info_alloc(struct 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; | ||||
| +		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++) | ||||
| @@ -1908,6 +1911,41 @@ void ieee80211_sta_register_airtime(stru | ||||
|  } | ||||
|  EXPORT_SYMBOL(ieee80211_sta_register_airtime); | ||||
|   | ||||
| +void ieee80211_sta_update_pending_airtime(struct ieee80211_local *local, | ||||
| +					  struct sta_info *sta, u8 ac, | ||||
| +					  u16 tx_airtime, bool tx_completed) | ||||
| +{ | ||||
| +	int tx_pending; | ||||
| + | ||||
| +	if (!tx_completed) { | ||||
| +		if (sta) | ||||
| +			atomic_add(tx_airtime, | ||||
| +				   &sta->airtime[ac].aql_tx_pending); | ||||
| + | ||||
| +		atomic_add(tx_airtime, &local->aql_total_pending_airtime); | ||||
| +		return; | ||||
| +	} | ||||
| + | ||||
| +	if (sta) { | ||||
| +		tx_pending = atomic_sub_return(tx_airtime, | ||||
| +					       &sta->airtime[ac].aql_tx_pending); | ||||
| +		if (WARN_ONCE(tx_pending < 0, | ||||
| +			      "STA %pM AC %d txq pending airtime underflow: %u, %u", | ||||
| +			      sta->addr, ac, tx_pending, tx_airtime)) | ||||
| +			atomic_cmpxchg(&sta->airtime[ac].aql_tx_pending, | ||||
| +				       tx_pending, 0); | ||||
| +	} | ||||
| + | ||||
| +	tx_pending = atomic_sub_return(tx_airtime, | ||||
| +				       &local->aql_total_pending_airtime); | ||||
| +	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_pending, 0); | ||||
| +} | ||||
| + | ||||
|  int sta_info_move_state(struct sta_info *sta, | ||||
|  			enum ieee80211_sta_state new_state) | ||||
|  { | ||||
| --- a/net/mac80211/sta_info.h | ||||
| +++ b/net/mac80211/sta_info.h | ||||
| @@ -127,13 +127,21 @@ enum ieee80211_agg_stop_reason { | ||||
|  /* Debugfs flags to enable/disable use of RX/TX airtime in scheduler */ | ||||
|  #define AIRTIME_USE_TX		BIT(0) | ||||
|  #define AIRTIME_USE_RX		BIT(1) | ||||
| +#define AIRTIME_USE_AQL		BIT(2) | ||||
|   | ||||
|  struct airtime_info { | ||||
|  	u64 rx_airtime; | ||||
|  	u64 tx_airtime; | ||||
|  	s64 deficit; | ||||
| +	atomic_t aql_tx_pending; /* Estimated airtime for frames pending */ | ||||
| +	u32 aql_limit_low; | ||||
| +	u32 aql_limit_high; | ||||
|  }; | ||||
|   | ||||
| +void ieee80211_sta_update_pending_airtime(struct ieee80211_local *local, | ||||
| +					  struct sta_info *sta, u8 ac, | ||||
| +					  u16 tx_airtime, bool tx_completed); | ||||
| + | ||||
|  struct sta_info; | ||||
|   | ||||
|  /** | ||||
| --- a/net/mac80211/tx.c | ||||
| +++ b/net/mac80211/tx.c | ||||
| @@ -3667,7 +3667,8 @@ struct ieee80211_txq *ieee80211_next_txq | ||||
|  { | ||||
|  	struct ieee80211_local *local = hw_to_local(hw); | ||||
|  	struct ieee80211_txq *ret = NULL; | ||||
| -	struct txq_info *txqi = NULL; | ||||
| +	struct txq_info *txqi = NULL, *head = NULL; | ||||
| +	bool found_eligible_txq = false; | ||||
|   | ||||
|  	spin_lock_bh(&local->active_txq_lock[ac]); | ||||
|   | ||||
| @@ -3678,13 +3679,30 @@ struct ieee80211_txq *ieee80211_next_txq | ||||
|  	if (!txqi) | ||||
|  		goto out; | ||||
|   | ||||
| +	if (txqi == head) { | ||||
| +		if (!found_eligible_txq) | ||||
| +			goto out; | ||||
| +		else | ||||
| +			found_eligible_txq = false; | ||||
| +	} | ||||
| + | ||||
| +	if (!head) | ||||
| +		head = txqi; | ||||
| + | ||||
|  	if (txqi->txq.sta) { | ||||
|  		struct sta_info *sta = container_of(txqi->txq.sta, | ||||
| -						struct sta_info, sta); | ||||
| +						    struct sta_info, sta); | ||||
| +		bool aql_check = ieee80211_txq_airtime_check(hw, &txqi->txq); | ||||
| +		s64 deficit = sta->airtime[txqi->txq.ac].deficit; | ||||
|   | ||||
| -		if (sta->airtime[txqi->txq.ac].deficit < 0) { | ||||
| +		if (aql_check) | ||||
| +			found_eligible_txq = true; | ||||
| + | ||||
| +		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; | ||||
| @@ -3738,6 +3756,33 @@ void __ieee80211_schedule_txq(struct iee | ||||
|  } | ||||
|  EXPORT_SYMBOL(__ieee80211_schedule_txq); | ||||
|   | ||||
| +bool ieee80211_txq_airtime_check(struct ieee80211_hw *hw, | ||||
| +				 struct ieee80211_txq *txq) | ||||
| +{ | ||||
| +	struct sta_info *sta; | ||||
| +	struct ieee80211_local *local = hw_to_local(hw); | ||||
| + | ||||
| +	if (!(local->airtime_flags & AIRTIME_USE_AQL)) | ||||
| +		return true; | ||||
| + | ||||
| +	if (!txq->sta) | ||||
| +		return true; | ||||
| + | ||||
| +	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(&sta->airtime[txq->ac].aql_tx_pending) < | ||||
| +	    sta->airtime[txq->ac].aql_limit_high) | ||||
| +		return true; | ||||
| + | ||||
| +	return false; | ||||
| +} | ||||
| +EXPORT_SYMBOL(ieee80211_txq_airtime_check); | ||||
| + | ||||
|  bool ieee80211_txq_may_transmit(struct ieee80211_hw *hw, | ||||
|  				struct ieee80211_txq *txq) | ||||
|  { | ||||
| @@ -0,0 +1,146 @@ | ||||
| From: =?UTF-8?q?Toke=20H=C3=B8iland-J=C3=B8rgensen?= <toke@redhat.com> | ||||
| Date: Mon, 18 Nov 2019 22:06:10 -0800 | ||||
| Subject: [PATCH] mac80211: Use Airtime-based Queue Limits (AQL) on packet | ||||
|  dequeue | ||||
| MIME-Version: 1.0 | ||||
| Content-Type: text/plain; charset=UTF-8 | ||||
| Content-Transfer-Encoding: 8bit | ||||
|  | ||||
| The previous commit added the ability to throttle stations when they queue | ||||
| too much airtime in the hardware. This commit enables the functionality by | ||||
| calculating the expected airtime usage of each packet that is dequeued from | ||||
| the TXQs in mac80211, and accounting that as pending airtime. | ||||
|  | ||||
| The estimated airtime for each skb is stored in the tx_info, so we can | ||||
| subtract the same amount from the running total when the skb is freed or | ||||
| recycled. The throttling mechanism relies on this accounting to be | ||||
| accurate (i.e., that we are not freeing skbs without subtracting any | ||||
| airtime they were accounted for), so we put the subtraction into | ||||
| ieee80211_report_used_skb(). As an optimisation, we also subtract the | ||||
| airtime on regular TX completion, zeroing out the value stored in the | ||||
| packet afterwards, to avoid having to do an expensive lookup of the station | ||||
| from the packet data on every packet. | ||||
|  | ||||
| This patch does *not* include any mechanism to wake a throttled TXQ again, | ||||
| on the assumption that this will happen anyway as a side effect of whatever | ||||
| freed the skb (most commonly a TX completion). | ||||
|  | ||||
| Signed-off-by: Toke Høiland-Jørgensen <toke@redhat.com> | ||||
| Link: https://lore.kernel.org/r/20191119060610.76681-5-kyan@google.com | ||||
| Signed-off-by: Johannes Berg <johannes.berg@intel.com> | ||||
| --- | ||||
|  | ||||
| --- a/include/net/mac80211.h | ||||
| +++ b/include/net/mac80211.h | ||||
| @@ -1060,6 +1060,22 @@ struct ieee80211_tx_info { | ||||
|  	}; | ||||
|  }; | ||||
|   | ||||
| +static inline u16 | ||||
| +ieee80211_info_set_tx_time_est(struct ieee80211_tx_info *info, u16 tx_time_est) | ||||
| +{ | ||||
| +	/* We only have 10 bits in tx_time_est, so store airtime | ||||
| +	 * in increments of 4us and clamp the maximum to 2**12-1 | ||||
| +	 */ | ||||
| +	info->tx_time_est = min_t(u16, tx_time_est, 4095) >> 2; | ||||
| +	return info->tx_time_est << 2; | ||||
| +} | ||||
| + | ||||
| +static inline u16 | ||||
| +ieee80211_info_get_tx_time_est(struct ieee80211_tx_info *info) | ||||
| +{ | ||||
| +	return info->tx_time_est << 2; | ||||
| +} | ||||
| + | ||||
|  /** | ||||
|   * struct ieee80211_tx_status - extended tx staus info for rate control | ||||
|   * | ||||
| --- a/net/mac80211/status.c | ||||
| +++ b/net/mac80211/status.c | ||||
| @@ -670,12 +670,26 @@ static void ieee80211_report_used_skb(st | ||||
|  				      struct sk_buff *skb, bool dropped) | ||||
|  { | ||||
|  	struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); | ||||
| +	u16 tx_time_est = ieee80211_info_get_tx_time_est(info); | ||||
|  	struct ieee80211_hdr *hdr = (void *)skb->data; | ||||
|  	bool acked = info->flags & IEEE80211_TX_STAT_ACK; | ||||
|   | ||||
|  	if (dropped) | ||||
|  		acked = false; | ||||
|   | ||||
| +	if (tx_time_est) { | ||||
| +		struct sta_info *sta; | ||||
| + | ||||
| +		rcu_read_lock(); | ||||
| + | ||||
| +		sta = sta_info_get_by_addrs(local, hdr->addr1, hdr->addr2); | ||||
| +		ieee80211_sta_update_pending_airtime(local, sta, | ||||
| +						     skb_get_queue_mapping(skb), | ||||
| +						     tx_time_est, | ||||
| +						     true); | ||||
| +		rcu_read_unlock(); | ||||
| +	} | ||||
| + | ||||
|  	if (info->flags & IEEE80211_TX_INTFL_MLME_CONN_TX) { | ||||
|  		struct ieee80211_sub_if_data *sdata; | ||||
|   | ||||
| @@ -885,6 +899,7 @@ static void __ieee80211_tx_status(struct | ||||
|  	struct ieee80211_bar *bar; | ||||
|  	int shift = 0; | ||||
|  	int tid = IEEE80211_NUM_TIDS; | ||||
| +	u16 tx_time_est; | ||||
|   | ||||
|  	rates_idx = ieee80211_tx_get_rates(hw, info, &retry_count); | ||||
|   | ||||
| @@ -996,6 +1011,17 @@ static void __ieee80211_tx_status(struct | ||||
|  			ieee80211_sta_register_airtime(&sta->sta, tid, | ||||
|  						       info->status.tx_time, 0); | ||||
|   | ||||
| +		if ((tx_time_est = ieee80211_info_get_tx_time_est(info)) > 0) { | ||||
| +			/* Do this here to avoid the expensive lookup of the sta | ||||
| +			 * in ieee80211_report_used_skb(). | ||||
| +			 */ | ||||
| +			ieee80211_sta_update_pending_airtime(local, sta, | ||||
| +							     skb_get_queue_mapping(skb), | ||||
| +							     tx_time_est, | ||||
| +							     true); | ||||
| +			ieee80211_info_set_tx_time_est(info, 0); | ||||
| +		} | ||||
| + | ||||
|  		if (ieee80211_hw_check(&local->hw, REPORTS_TX_ACK_STATUS)) { | ||||
|  			if (acked) { | ||||
|  				if (sta->status_stats.lost_packets) | ||||
| --- a/net/mac80211/tx.c | ||||
| +++ b/net/mac80211/tx.c | ||||
| @@ -3544,6 +3544,9 @@ struct sk_buff *ieee80211_tx_dequeue(str | ||||
|   | ||||
|  	WARN_ON_ONCE(softirq_count() == 0); | ||||
|   | ||||
| +	if (!ieee80211_txq_airtime_check(hw, txq)) | ||||
| +		return NULL; | ||||
| + | ||||
|  begin: | ||||
|  	spin_lock_bh(&fq->lock); | ||||
|   | ||||
| @@ -3654,6 +3657,21 @@ begin: | ||||
|  	} | ||||
|   | ||||
|  	IEEE80211_SKB_CB(skb)->control.vif = vif; | ||||
| + | ||||
| +	if (local->airtime_flags & AIRTIME_USE_AQL) { | ||||
| +		u32 airtime; | ||||
| + | ||||
| +		airtime = ieee80211_calc_expected_tx_airtime(hw, vif, txq->sta, | ||||
| +							     skb->len); | ||||
| +		if (airtime) { | ||||
| +			airtime = ieee80211_info_set_tx_time_est(info, airtime); | ||||
| +			ieee80211_sta_update_pending_airtime(local, tx.sta, | ||||
| +							     txq->ac, | ||||
| +							     airtime, | ||||
| +							     false); | ||||
| +		} | ||||
| +	} | ||||
| + | ||||
|  	return skb; | ||||
|   | ||||
|  out: | ||||
| @@ -0,0 +1,31 @@ | ||||
| From: Dan Carpenter <dan.carpenter@oracle.com> | ||||
| Date: Tue, 26 Nov 2019 15:09:39 +0300 | ||||
| Subject: [PATCH] mac80211: airtime: Fix an off by one in | ||||
|  ieee80211_calc_rx_airtime() | ||||
| MIME-Version: 1.0 | ||||
| Content-Type: text/plain; charset=UTF-8 | ||||
| Content-Transfer-Encoding: 8bit | ||||
|  | ||||
| This code was copied from mt76 and inherited an off by one bug from | ||||
| there.  The > should be >= so that we don't read one element beyond | ||||
| the end of the array. | ||||
|  | ||||
| Fixes: db3e1c40cf2f ("mac80211: Import airtime calculation code from mt76") | ||||
| Reported-by: Toke Høiland-Jørgensen <toke@redhat.com> | ||||
| Signed-off-by: Dan Carpenter <dan.carpenter@oracle.com> | ||||
| Acked-by: Toke Høiland-Jørgensen <toke@redhat.com> | ||||
| Link: https://lore.kernel.org/r/20191126120910.ftr4t7me3by32aiz@kili.mountain | ||||
| Signed-off-by: Johannes Berg <johannes.berg@intel.com> | ||||
| --- | ||||
|  | ||||
| --- a/net/mac80211/airtime.c | ||||
| +++ b/net/mac80211/airtime.c | ||||
| @@ -442,7 +442,7 @@ u32 ieee80211_calc_rx_airtime(struct iee | ||||
|  			return 0; | ||||
|   | ||||
|  		sband = hw->wiphy->bands[status->band]; | ||||
| -		if (!sband || status->rate_idx > sband->n_bitrates) | ||||
| +		if (!sband || status->rate_idx >= sband->n_bitrates) | ||||
|  			return 0; | ||||
|   | ||||
|  		rate = &sband->bitrates[status->rate_idx]; | ||||
| @@ -0,0 +1,253 @@ | ||||
| From: =?UTF-8?q?Toke=20H=C3=B8iland-J=C3=B8rgensen?= <toke@redhat.com> | ||||
| Date: Thu, 12 Dec 2019 12:14:37 +0100 | ||||
| Subject: [PATCH] mac80211: Turn AQL into an NL80211_EXT_FEATURE | ||||
| MIME-Version: 1.0 | ||||
| Content-Type: text/plain; charset=UTF-8 | ||||
| Content-Transfer-Encoding: 8bit | ||||
|  | ||||
| Instead of just having an airtime flag in debugfs, turn AQL into a proper | ||||
| NL80211_EXT_FEATURE, so drivers can turn it on when they are ready, and so | ||||
| we also expose the presence of the feature to userspace. | ||||
|  | ||||
| This also has the effect of flipping the default, so drivers have to opt in | ||||
| to using AQL instead of getting it by default with TXQs. To keep | ||||
| functionality the same as pre-patch, we set this feature for ath10k (which | ||||
| is where it is needed the most). | ||||
|  | ||||
| While we're at it, split out the debugfs interface so AQL gets its own | ||||
| per-station debugfs file instead of using the 'airtime' file. | ||||
|  | ||||
| [Johannes:] | ||||
| This effectively disables AQL for iwlwifi, where it fixes a number of | ||||
| issues: | ||||
|  * TSO in iwlwifi is causing underflows and associated warnings in AQL | ||||
|  * HE (802.11ax) rates aren't reported properly so at HE rates, AQL could | ||||
|    never have a valid estimate (it'd use 6 Mbps instead of up to 2400!) | ||||
|  | ||||
| Signed-off-by: Toke Høiland-Jørgensen <toke@redhat.com> | ||||
| Link: https://lore.kernel.org/r/20191212111437.224294-1-toke@redhat.com | ||||
| Fixes: 3ace10f5b5ad ("mac80211: Implement Airtime-based Queue Limit (AQL)") | ||||
| Signed-off-by: Johannes Berg <johannes.berg@intel.com> | ||||
| --- | ||||
|  | ||||
| --- a/drivers/net/wireless/ath/ath10k/mac.c | ||||
| +++ b/drivers/net/wireless/ath/ath10k/mac.c | ||||
| @@ -8868,6 +8868,7 @@ int ath10k_mac_register(struct ath10k *a | ||||
|  	wiphy_ext_feature_set(ar->hw->wiphy, NL80211_EXT_FEATURE_VHT_IBSS); | ||||
|  	wiphy_ext_feature_set(ar->hw->wiphy, | ||||
|  			      NL80211_EXT_FEATURE_SET_SCAN_DWELL); | ||||
| +	wiphy_ext_feature_set(ar->hw->wiphy, NL80211_EXT_FEATURE_AQL); | ||||
|   | ||||
|  	if (test_bit(WMI_SERVICE_TX_DATA_ACK_RSSI, ar->wmi.svc_map) || | ||||
|  	    test_bit(WMI_SERVICE_HTT_MGMT_TX_COMP_VALID_FLAGS, ar->wmi.svc_map)) | ||||
| --- a/include/uapi/linux/nl80211.h | ||||
| +++ b/include/uapi/linux/nl80211.h | ||||
| @@ -5484,6 +5484,10 @@ enum nl80211_feature_flags { | ||||
|   * @NL80211_EXT_FEATURE_SAE_OFFLOAD: Device wants to do SAE authentication in | ||||
|   *	station mode (SAE password is passed as part of the connect command). | ||||
|   * | ||||
| + * @NL80211_EXT_FEATURE_AQL: The driver supports the Airtime Queue Limit (AQL) | ||||
| + *	feature, which prevents bufferbloat by using the expected transmission | ||||
| + *	time to limit the amount of data buffered in the hardware. | ||||
| + * | ||||
|   * @NUM_NL80211_EXT_FEATURES: number of extended features. | ||||
|   * @MAX_NL80211_EXT_FEATURES: highest extended feature index. | ||||
|   */ | ||||
| @@ -5529,6 +5533,8 @@ enum nl80211_ext_feature_index { | ||||
|  	NL80211_EXT_FEATURE_EXT_KEY_ID, | ||||
|  	NL80211_EXT_FEATURE_STA_TX_PWR, | ||||
|  	NL80211_EXT_FEATURE_SAE_OFFLOAD, | ||||
| +	NL80211_EXT_FEATURE_VLAN_OFFLOAD, | ||||
| +	NL80211_EXT_FEATURE_AQL, | ||||
|   | ||||
|  	/* add new features before the definition below */ | ||||
|  	NUM_NL80211_EXT_FEATURES, | ||||
| --- a/net/mac80211/debugfs_sta.c | ||||
| +++ b/net/mac80211/debugfs_sta.c | ||||
| @@ -201,8 +201,6 @@ static ssize_t sta_airtime_read(struct f | ||||
|  	char *buf = kzalloc(bufsz, GFP_KERNEL), *p = buf; | ||||
|  	u64 rx_airtime = 0, tx_airtime = 0; | ||||
|  	s64 deficit[IEEE80211_NUM_ACS]; | ||||
| -	u32 q_depth[IEEE80211_NUM_ACS]; | ||||
| -	u32 q_limit_l[IEEE80211_NUM_ACS], q_limit_h[IEEE80211_NUM_ACS]; | ||||
|  	ssize_t rv; | ||||
|  	int ac; | ||||
|   | ||||
| @@ -214,6 +212,56 @@ static ssize_t sta_airtime_read(struct f | ||||
|  		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_aql_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 = 400; | ||||
| +	char *buf = kzalloc(bufsz, GFP_KERNEL), *p = buf; | ||||
| +	u32 q_depth[IEEE80211_NUM_ACS]; | ||||
| +	u32 q_limit_l[IEEE80211_NUM_ACS], q_limit_h[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]); | ||||
|  		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]); | ||||
| @@ -221,12 +269,8 @@ 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" | ||||
|  		"Q depth: VO: %u us VI: %u us BE: %u us BK: %u us\n" | ||||
|  		"Q limit[low/high]: VO: %u/%u VI: %u/%u BE: %u/%u BK: %u/%u\n", | ||||
| -		rx_airtime, tx_airtime, sta->airtime_weight, | ||||
| -		deficit[0], deficit[1], deficit[2], deficit[3], | ||||
|  		q_depth[0], q_depth[1], q_depth[2], q_depth[3], | ||||
|  		q_limit_l[0], q_limit_h[0], q_limit_l[1], q_limit_h[1], | ||||
|  		q_limit_l[2], q_limit_h[2], q_limit_l[3], q_limit_h[3]), | ||||
| @@ -236,11 +280,10 @@ static ssize_t sta_airtime_read(struct f | ||||
|  	return rv; | ||||
|  } | ||||
|   | ||||
| -static ssize_t sta_airtime_write(struct file *file, const char __user *userbuf, | ||||
| +static ssize_t sta_aql_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; | ||||
|  	u32 ac, q_limit_l, q_limit_h; | ||||
|  	char _buf[100] = {}, *buf = _buf; | ||||
|   | ||||
| @@ -251,7 +294,7 @@ static ssize_t sta_airtime_write(struct | ||||
|  		return -EFAULT; | ||||
|   | ||||
|  	buf[sizeof(_buf) - 1] = '\0'; | ||||
| -	if (sscanf(buf, "queue limit %u %u %u", &ac, &q_limit_l, &q_limit_h) | ||||
| +	if (sscanf(buf, "limit %u %u %u", &ac, &q_limit_l, &q_limit_h) | ||||
|  	    != 3) | ||||
|  		return -EINVAL; | ||||
|   | ||||
| @@ -261,17 +304,10 @@ static ssize_t sta_airtime_write(struct | ||||
|  	sta->airtime[ac].aql_limit_low = q_limit_l; | ||||
|  	sta->airtime[ac].aql_limit_high = q_limit_h; | ||||
|   | ||||
| -	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); | ||||
| +STA_OPS_RW(aql); | ||||
| + | ||||
|   | ||||
|  static ssize_t sta_agg_status_read(struct file *file, char __user *userbuf, | ||||
|  					size_t count, loff_t *ppos) | ||||
| @@ -1001,6 +1037,10 @@ void ieee80211_sta_debugfs_add(struct st | ||||
|  				    NL80211_EXT_FEATURE_AIRTIME_FAIRNESS)) | ||||
|  		DEBUGFS_ADD(airtime); | ||||
|   | ||||
| +	if (wiphy_ext_feature_isset(local->hw.wiphy, | ||||
| +				    NL80211_EXT_FEATURE_AQL)) | ||||
| +		DEBUGFS_ADD(aql); | ||||
| + | ||||
|  	if (sizeof(sta->driver_buffered_tids) == sizeof(u32)) | ||||
|  		debugfs_create_x32("driver_buffered_tids", 0400, | ||||
|  				   sta->debugfs_dir, | ||||
| --- a/net/mac80211/main.c | ||||
| +++ b/net/mac80211/main.c | ||||
| @@ -674,9 +674,7 @@ struct ieee80211_hw *ieee80211_alloc_hw_ | ||||
|  			IEEE80211_DEFAULT_AQL_TXQ_LIMIT_H; | ||||
|  	} | ||||
|   | ||||
| -	local->airtime_flags = AIRTIME_USE_TX | | ||||
| -			       AIRTIME_USE_RX | | ||||
| -			       AIRTIME_USE_AQL; | ||||
| +	local->airtime_flags = AIRTIME_USE_TX | AIRTIME_USE_RX; | ||||
|  	local->aql_threshold = IEEE80211_AQL_THRESHOLD; | ||||
|  	atomic_set(&local->aql_total_pending_airtime, 0); | ||||
|   | ||||
| --- a/net/mac80211/sta_info.c | ||||
| +++ b/net/mac80211/sta_info.c | ||||
| @@ -1917,6 +1917,9 @@ void ieee80211_sta_update_pending_airtim | ||||
|  { | ||||
|  	int tx_pending; | ||||
|   | ||||
| +	if (!wiphy_ext_feature_isset(local->hw.wiphy, NL80211_EXT_FEATURE_AQL)) | ||||
| +		return; | ||||
| + | ||||
|  	if (!tx_completed) { | ||||
|  		if (sta) | ||||
|  			atomic_add(tx_airtime, | ||||
| --- a/net/mac80211/sta_info.h | ||||
| +++ b/net/mac80211/sta_info.h | ||||
| @@ -127,7 +127,6 @@ enum ieee80211_agg_stop_reason { | ||||
|  /* Debugfs flags to enable/disable use of RX/TX airtime in scheduler */ | ||||
|  #define AIRTIME_USE_TX		BIT(0) | ||||
|  #define AIRTIME_USE_RX		BIT(1) | ||||
| -#define AIRTIME_USE_AQL		BIT(2) | ||||
|   | ||||
|  struct airtime_info { | ||||
|  	u64 rx_airtime; | ||||
| --- a/net/mac80211/tx.c | ||||
| +++ b/net/mac80211/tx.c | ||||
| @@ -3658,7 +3658,7 @@ begin: | ||||
|   | ||||
|  	IEEE80211_SKB_CB(skb)->control.vif = vif; | ||||
|   | ||||
| -	if (local->airtime_flags & AIRTIME_USE_AQL) { | ||||
| +	if (wiphy_ext_feature_isset(local->hw.wiphy, NL80211_EXT_FEATURE_AQL)) { | ||||
|  		u32 airtime; | ||||
|   | ||||
|  		airtime = ieee80211_calc_expected_tx_airtime(hw, vif, txq->sta, | ||||
| @@ -3780,7 +3780,7 @@ bool ieee80211_txq_airtime_check(struct | ||||
|  	struct sta_info *sta; | ||||
|  	struct ieee80211_local *local = hw_to_local(hw); | ||||
|   | ||||
| -	if (!(local->airtime_flags & AIRTIME_USE_AQL)) | ||||
| +	if (!wiphy_ext_feature_isset(local->hw.wiphy, NL80211_EXT_FEATURE_AQL)) | ||||
|  		return true; | ||||
|   | ||||
|  	if (!txq->sta) | ||||
| @@ -0,0 +1,70 @@ | ||||
| From: Johannes Berg <johannes.berg@intel.com> | ||||
| Date: Wed, 15 Jan 2020 12:25:50 +0100 | ||||
| Subject: [PATCH] mac80211: use more bits for ack_frame_id | ||||
| MIME-Version: 1.0 | ||||
| Content-Type: text/plain; charset=UTF-8 | ||||
| Content-Transfer-Encoding: 8bit | ||||
|  | ||||
| It turns out that this wasn't a good idea, I hit a test failure in | ||||
| hwsim due to this. That particular failure was easily worked around, | ||||
| but it raised questions: if an AP needs to, for example, send action | ||||
| frames to each connected station, the current limit is nowhere near | ||||
| enough (especially if those stations are sleeping and the frames are | ||||
| queued for a while.) | ||||
|  | ||||
| Shuffle around some bits to make more room for ack_frame_id to allow | ||||
| up to 8192 queued up frames, that's enough for queueing 4 frames to | ||||
| each connected station, even at the maximum of 2007 stations on a | ||||
| single AP. | ||||
|  | ||||
| We take the bits from band (which currently only 2 but I leave 3 in | ||||
| case we add another band) and from the hw_queue, which can only need | ||||
| 4 since it has a limit of 16 queues. | ||||
|  | ||||
| Fixes: 6912daed05e1 ("mac80211: Shrink the size of ack_frame_id to make room for tx_time_est") | ||||
| Signed-off-by: Johannes Berg <johannes.berg@intel.com> | ||||
| Acked-by: Toke Høiland-Jørgensen <toke@redhat.com> | ||||
| --- | ||||
|  | ||||
| --- a/include/net/mac80211.h | ||||
| +++ b/include/net/mac80211.h | ||||
| @@ -1004,12 +1004,11 @@ ieee80211_rate_get_vht_nss(const struct | ||||
|  struct ieee80211_tx_info { | ||||
|  	/* common information */ | ||||
|  	u32 flags; | ||||
| -	u8 band; | ||||
| - | ||||
| -	u8 hw_queue; | ||||
| - | ||||
| -	u16 ack_frame_id:6; | ||||
| -	u16 tx_time_est:10; | ||||
| +	u32 band:3, | ||||
| +	    ack_frame_id:13, | ||||
| +	    hw_queue:4, | ||||
| +	    tx_time_est:10; | ||||
| +	/* 2 free bits */ | ||||
|   | ||||
|  	union { | ||||
|  		struct { | ||||
| --- a/net/mac80211/cfg.c | ||||
| +++ b/net/mac80211/cfg.c | ||||
| @@ -3427,7 +3427,7 @@ int ieee80211_attach_ack_skb(struct ieee | ||||
|   | ||||
|  	spin_lock_irqsave(&local->ack_status_lock, spin_flags); | ||||
|  	id = idr_alloc(&local->ack_status_frames, ack_skb, | ||||
| -		       1, 0x40, GFP_ATOMIC); | ||||
| +		       1, 0x2000, GFP_ATOMIC); | ||||
|  	spin_unlock_irqrestore(&local->ack_status_lock, spin_flags); | ||||
|   | ||||
|  	if (id < 0) { | ||||
| --- a/net/mac80211/tx.c | ||||
| +++ b/net/mac80211/tx.c | ||||
| @@ -2443,7 +2443,7 @@ static int ieee80211_store_ack_skb(struc | ||||
|   | ||||
|  		spin_lock_irqsave(&local->ack_status_lock, flags); | ||||
|  		id = idr_alloc(&local->ack_status_frames, ack_skb, | ||||
| -			       1, 0x40, GFP_ATOMIC); | ||||
| +			       1, 0x2000, GFP_ATOMIC); | ||||
|  		spin_unlock_irqrestore(&local->ack_status_lock, flags); | ||||
|   | ||||
|  		if (id >= 0) { | ||||
| @@ -1,6 +1,6 @@ | ||||
| --- a/include/net/cfg80211.h | ||||
| +++ b/include/net/cfg80211.h | ||||
| @@ -3447,6 +3447,7 @@ struct cfg80211_update_owe_info { | ||||
| @@ -3454,6 +3454,7 @@ struct cfg80211_update_owe_info { | ||||
|   *	(as advertised by the nl80211 feature flag.) | ||||
|   * @get_tx_power: store the current TX power into the dbm variable; | ||||
|   *	return 0 if successful | ||||
| @@ -8,7 +8,7 @@ | ||||
|   * | ||||
|   * @set_wds_peer: set the WDS peer for a WDS interface | ||||
|   * | ||||
| @@ -3759,6 +3760,7 @@ struct cfg80211_ops { | ||||
| @@ -3766,6 +3767,7 @@ struct cfg80211_ops { | ||||
|  				enum nl80211_tx_power_setting type, int mbm); | ||||
|  	int	(*get_tx_power)(struct wiphy *wiphy, struct wireless_dev *wdev, | ||||
|  				int *dbm); | ||||
| @@ -18,7 +18,7 @@ | ||||
|  				const u8 *addr); | ||||
| --- a/include/net/mac80211.h | ||||
| +++ b/include/net/mac80211.h | ||||
| @@ -1484,6 +1484,7 @@ enum ieee80211_smps_mode { | ||||
| @@ -1501,6 +1501,7 @@ enum ieee80211_smps_mode { | ||||
|   * | ||||
|   * @power_level: requested transmit power (in dBm), backward compatibility | ||||
|   *	value only that is set to the minimum of all interfaces | ||||
| @@ -26,7 +26,7 @@ | ||||
|   * | ||||
|   * @chandef: the channel definition to tune to | ||||
|   * @radar_enabled: whether radar detection is enabled | ||||
| @@ -1504,6 +1505,7 @@ enum ieee80211_smps_mode { | ||||
| @@ -1521,6 +1522,7 @@ enum ieee80211_smps_mode { | ||||
|  struct ieee80211_conf { | ||||
|  	u32 flags; | ||||
|  	int power_level, dynamic_ps_timeout; | ||||
| @@ -87,7 +87,7 @@ | ||||
|  	CFG80211_TESTMODE_CMD(ieee80211_testmode_cmd) | ||||
| --- a/net/mac80211/ieee80211_i.h | ||||
| +++ b/net/mac80211/ieee80211_i.h | ||||
| @@ -1372,6 +1372,7 @@ struct ieee80211_local { | ||||
| @@ -1376,6 +1376,7 @@ struct ieee80211_local { | ||||
|  	int dynamic_ps_forced_timeout; | ||||
|   | ||||
|  	int user_power_level; /* in dBm, for all interfaces */ | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Felix Fietkau
					Felix Fietkau