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 | --- a/include/net/cfg80211.h | ||||||
| +++ b/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.) |   *	(as advertised by the nl80211 feature flag.) | ||||||
|   * @get_tx_power: store the current TX power into the dbm variable; |   * @get_tx_power: store the current TX power into the dbm variable; | ||||||
|   *	return 0 if successful |   *	return 0 if successful | ||||||
| @@ -8,7 +8,7 @@ | |||||||
|   * |   * | ||||||
|   * @set_wds_peer: set the WDS peer for a WDS interface |   * @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); |  				enum nl80211_tx_power_setting type, int mbm); | ||||||
|  	int	(*get_tx_power)(struct wiphy *wiphy, struct wireless_dev *wdev, |  	int	(*get_tx_power)(struct wiphy *wiphy, struct wireless_dev *wdev, | ||||||
|  				int *dbm); |  				int *dbm); | ||||||
| @@ -18,7 +18,7 @@ | |||||||
|  				const u8 *addr); |  				const u8 *addr); | ||||||
| --- a/include/net/mac80211.h | --- a/include/net/mac80211.h | ||||||
| +++ b/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 |   * @power_level: requested transmit power (in dBm), backward compatibility | ||||||
|   *	value only that is set to the minimum of all interfaces |   *	value only that is set to the minimum of all interfaces | ||||||
| @@ -26,7 +26,7 @@ | |||||||
|   * |   * | ||||||
|   * @chandef: the channel definition to tune to |   * @chandef: the channel definition to tune to | ||||||
|   * @radar_enabled: whether radar detection is enabled |   * @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 { |  struct ieee80211_conf { | ||||||
|  	u32 flags; |  	u32 flags; | ||||||
|  	int power_level, dynamic_ps_timeout; |  	int power_level, dynamic_ps_timeout; | ||||||
| @@ -87,7 +87,7 @@ | |||||||
|  	CFG80211_TESTMODE_CMD(ieee80211_testmode_cmd) |  	CFG80211_TESTMODE_CMD(ieee80211_testmode_cmd) | ||||||
| --- a/net/mac80211/ieee80211_i.h | --- a/net/mac80211/ieee80211_i.h | ||||||
| +++ b/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 dynamic_ps_forced_timeout; | ||||||
|   |   | ||||||
|  	int user_power_level; /* in dBm, for all interfaces */ |  	int user_power_level; /* in dBm, for all interfaces */ | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Felix Fietkau
					Felix Fietkau