diff mbox series

[RFC,v5,3/4] mac80211: Add airtime accounting and scheduling to TXQs

Message ID 153908837900.9471.5394468800857658136.stgit@alrua-kau (mailing list archive)
State RFC
Delegated to: Johannes Berg
Headers show
Series Move TXQ scheduling and airtime fairness into mac80211 | expand

Commit Message

Toke Høiland-Jørgensen Oct. 9, 2018, 12:32 p.m. UTC
This adds airtime accounting and scheduling to the mac80211 TXQ
scheduler. A new callback, ieee80211_sta_register_airtime(), is added
that drivers can call to report airtime usage for stations.

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

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

Signed-off-by: Toke Høiland-Jørgensen <toke@toke.dk>
---
 include/net/mac80211.h     |   52 +++++++++++++++++++++++++++
 net/mac80211/cfg.c         |    3 ++
 net/mac80211/debugfs.c     |    3 ++
 net/mac80211/debugfs_sta.c |   51 +++++++++++++++++++++++++-
 net/mac80211/ieee80211_i.h |    5 +++
 net/mac80211/main.c        |    6 +++
 net/mac80211/sta_info.c    |   52 +++++++++++++++++++++++++--
 net/mac80211/sta_info.h    |   13 +++++++
 net/mac80211/status.c      |    6 +++
 net/mac80211/tx.c          |   86 ++++++++++++++++++++++++++++++++++++++++++--
 net/mac80211/util.c        |   75 ++++++++++++++++++++++++++++++++++++++
 11 files changed, 341 insertions(+), 11 deletions(-)

Comments

Rajkumar Manoharan Oct. 10, 2018, 4:52 a.m. UTC | #1
On 2018-10-09 05:32, Toke Høiland-Jørgensen wrote:
> This adds airtime accounting and scheduling to the mac80211 TXQ
> scheduler. A new callback, ieee80211_sta_register_airtime(), is added
> that drivers can call to report airtime usage for stations.
> 
> When airtime information is present, mac80211 will schedule TXQs
> (through ieee80211_next_txq()) in a way that enforces airtime fairness
> between active stations. This scheduling works the same way as the 
> ath9k
> in-driver airtime fairness scheduling. If no airtime usage is reported
> by the driver, the scheduler will default to round-robin scheduling.
> 
> For drivers that don't control TXQ scheduling in software, a new API
> function, ieee80211_txq_may_transmit(), is added which the driver can 
> use
> to check if the TXQ is eligible for transmission, or should be 
> throttled to
> enforce fairness. Calls to this function must also be enclosed in
> ieee80211_txq_schedule_{start,end}() calls to ensure proper locking. 
> TXQs
> that are throttled by ieee802111_txq_may_transmit() will be woken up 
> again
> by a check added to the ieee80211_wake_txqs() tasklet.
> 

Toke,

I am observing soft lockup issues again with this new series while 
running
traffic with 50 clients. I am continuing testing with earlier series 
along with
snippet I shared. When driver operates in pull-mode, throttled txqs are 
marked
and refilled in airtime_tasklet. This is causing major throughput drops 
and
packet loss and I am suspecting the latency in replenishing deficit.
Whereas in push-mode or in ath9k model, refill happens quicker at every 
packet
indication as well as tx completion.

I am planning to get rid of tasklet completely as it is only meant for 
pull-mode.
It would be better to refill in may_transmit() itself.

-Rajkumar
Toke Høiland-Jørgensen Oct. 10, 2018, 11:15 a.m. UTC | #2
Rajkumar Manoharan <rmanohar@codeaurora.org> writes:

> On 2018-10-09 05:32, Toke Høiland-Jørgensen wrote:
>> This adds airtime accounting and scheduling to the mac80211 TXQ
>> scheduler. A new callback, ieee80211_sta_register_airtime(), is added
>> that drivers can call to report airtime usage for stations.
>> 
>> When airtime information is present, mac80211 will schedule TXQs
>> (through ieee80211_next_txq()) in a way that enforces airtime fairness
>> between active stations. This scheduling works the same way as the 
>> ath9k
>> in-driver airtime fairness scheduling. If no airtime usage is reported
>> by the driver, the scheduler will default to round-robin scheduling.
>> 
>> For drivers that don't control TXQ scheduling in software, a new API
>> function, ieee80211_txq_may_transmit(), is added which the driver can 
>> use
>> to check if the TXQ is eligible for transmission, or should be 
>> throttled to
>> enforce fairness. Calls to this function must also be enclosed in
>> ieee80211_txq_schedule_{start,end}() calls to ensure proper locking. 
>> TXQs
>> that are throttled by ieee802111_txq_may_transmit() will be woken up 
>> again
>> by a check added to the ieee80211_wake_txqs() tasklet.
>> 
>
> Toke,
>
> I am observing soft lockup issues again with this new series while
> running traffic with 50 clients. I am continuing testing with earlier
> series along with snippet I shared.

Are these new lockups (that was not in your patched previous version),
or did I just not get all your lock-related fixes incorporated?

> When driver operates in pull-mode, throttled txqs are marked and
> refilled in airtime_tasklet. This is causing major throughput drops
> and packet loss and I am suspecting the latency in replenishing
> deficit. Whereas in push-mode or in ath9k model, refill happens
> quicker at every packet indication as well as tx completion.

Yeah, the tasklet shouldn't be the main source of deficit replenishing.
Can see why that would give bad performance :)

> I am planning to get rid of tasklet completely as it is only meant for
> pull-mode. It would be better to refill in may_transmit() itself.

Hmm, right. So the way to do this correctly (from a fairness point of
view) would be something like this (in max_tx()):

if (this_txq.stn.deficit > 0)
  return true;

else if (any queued TXQ currently have positive deficit)
  return false; /* other TXQ should try may_tx() later and get permission */

else /* all deficits < 0 */
  return replenish_deficits(this_txq);

And replenish_deficits() would be something like:

replenish_deficits(this_txq) {
repeat:
  for (txq in queued txqs) {
    txq.stn.deficit += stn.weight;
    if (txq.stn.deficit > 0 && !wake_txq)
      wake_txq = txq;
  }
  if not wake_txq:
    goto repeat;

  if (this_txq.stn.deficit > 0)
    return true;
  else
    drv_wake_tx_queue(wake_txq);
}

The wake_tx_queue call may have to be delegated to a tasklet still, to
avoid the infinite recursion problem I mentioned earlier. But the
tasklet could be made simpler and wouldn't have to be called so often...

Does the above make sense?

-Toke
Rajkumar Manoharan Oct. 10, 2018, 11:20 p.m. UTC | #3
On 2018-10-10 04:15, Toke Høiland-Jørgensen wrote:
> Rajkumar Manoharan <rmanohar@codeaurora.org> writes:
> 
>> On 2018-10-09 05:32, Toke Høiland-Jørgensen wrote:
>>> This adds airtime accounting and scheduling to the mac80211 TXQ
>>> scheduler. A new callback, ieee80211_sta_register_airtime(), is added
>>> that drivers can call to report airtime usage for stations.
>>> 
>>> When airtime information is present, mac80211 will schedule TXQs
>>> (through ieee80211_next_txq()) in a way that enforces airtime 
>>> fairness
>>> between active stations. This scheduling works the same way as the
>>> ath9k
>>> in-driver airtime fairness scheduling. If no airtime usage is 
>>> reported
>>> by the driver, the scheduler will default to round-robin scheduling.
>>> 
>>> For drivers that don't control TXQ scheduling in software, a new API
>>> function, ieee80211_txq_may_transmit(), is added which the driver can
>>> use
>>> to check if the TXQ is eligible for transmission, or should be
>>> throttled to
>>> enforce fairness. Calls to this function must also be enclosed in
>>> ieee80211_txq_schedule_{start,end}() calls to ensure proper locking.
>>> TXQs
>>> that are throttled by ieee802111_txq_may_transmit() will be woken up
>>> again
>>> by a check added to the ieee80211_wake_txqs() tasklet.
>>> 
>> 
>> Toke,
>> 
>> I am observing soft lockup issues again with this new series while
>> running traffic with 50 clients. I am continuing testing with earlier
>> series along with snippet I shared.
> 
> Are these new lockups (that was not in your patched previous version),
> or did I just not get all your lock-related fixes incorporated?
> 
>> When driver operates in pull-mode, throttled txqs are marked and
>> refilled in airtime_tasklet. This is causing major throughput drops
>> and packet loss and I am suspecting the latency in replenishing
>> deficit. Whereas in push-mode or in ath9k model, refill happens
>> quicker at every packet indication as well as tx completion.
> 
> Yeah, the tasklet shouldn't be the main source of deficit replenishing.
> Can see why that would give bad performance :)
> 
>> I am planning to get rid of tasklet completely as it is only meant for
>> pull-mode. It would be better to refill in may_transmit() itself.
> 
> Hmm, right. So the way to do this correctly (from a fairness point of
> view) would be something like this (in max_tx()):
> 
> if (this_txq.stn.deficit > 0)
>   return true;
> 
> else if (any queued TXQ currently have positive deficit)
>   return false; /* other TXQ should try may_tx() later and get 
> permission */
> 
> else /* all deficits < 0 */
>   return replenish_deficits(this_txq);
> 
> And replenish_deficits() would be something like:
> 
> replenish_deficits(this_txq) {
> repeat:
>   for (txq in queued txqs) {
>     txq.stn.deficit += stn.weight;
>     if (txq.stn.deficit > 0 && !wake_txq)
>       wake_txq = txq;
>   }
>   if not wake_txq:
>     goto repeat;
> 
>   if (this_txq.stn.deficit > 0)
>     return true;
>   else
>     drv_wake_tx_queue(wake_txq);
> }
> 
> The wake_tx_queue call may have to be delegated to a tasklet still, to
> avoid the infinite recursion problem I mentioned earlier. But the
> tasklet could be made simpler and wouldn't have to be called so 
> often...
> 
> Does the above make sense?
> 
Hmm... mine is bit different. txqs are refilled only once for all txqs.
It will give more opportunity for non-served txqs. drv_wake_tx_queue 
won't be
called from may_tx as the driver anyway will not push packets in 
pull-mode.

bool ieee80211_txq_may_transmit(struct ieee80211_hw *hw,
                                 struct ieee80211_txq *txq)
{
         struct ieee80211_local *local = hw_to_local(hw);
         struct txq_info *txqi = to_txq_info(txq);
         struct sta_info *sta;
         u8 ac = txq->ac;

         lockdep_assert_held(&local->active_txq_lock[ac]);

         if (!txqi->txq.sta)
                 goto out;

         sta = container_of(txqi->txq.sta, struct sta_info, sta);
         if (sta->airtime[ac].deficit >= 0)
                 goto out;

         list_for_each_entry(txqi, &local->active_txqs[ac], 
schedule_order) {
                 if (!txqi->txq.sta)
                         continue;
                 sta = container_of(txqi->txq.sta, struct sta_info, sta);
                 sta->airtime[ac].deficit += sta->airtime_weight;
         }

         return false;

  out:
         list_del_init(&txqi->schedule_order);
         return true;
}

-Rajkumar
Toke Høiland-Jørgensen Oct. 11, 2018, 10:38 a.m. UTC | #4
Rajkumar Manoharan <rmanohar@codeaurora.org> writes:

> On 2018-10-10 04:15, Toke Høiland-Jørgensen wrote:
>> Rajkumar Manoharan <rmanohar@codeaurora.org> writes:
>> 
>>> On 2018-10-09 05:32, Toke Høiland-Jørgensen wrote:
>>>> This adds airtime accounting and scheduling to the mac80211 TXQ
>>>> scheduler. A new callback, ieee80211_sta_register_airtime(), is added
>>>> that drivers can call to report airtime usage for stations.
>>>> 
>>>> When airtime information is present, mac80211 will schedule TXQs
>>>> (through ieee80211_next_txq()) in a way that enforces airtime 
>>>> fairness
>>>> between active stations. This scheduling works the same way as the
>>>> ath9k
>>>> in-driver airtime fairness scheduling. If no airtime usage is 
>>>> reported
>>>> by the driver, the scheduler will default to round-robin scheduling.
>>>> 
>>>> For drivers that don't control TXQ scheduling in software, a new API
>>>> function, ieee80211_txq_may_transmit(), is added which the driver can
>>>> use
>>>> to check if the TXQ is eligible for transmission, or should be
>>>> throttled to
>>>> enforce fairness. Calls to this function must also be enclosed in
>>>> ieee80211_txq_schedule_{start,end}() calls to ensure proper locking.
>>>> TXQs
>>>> that are throttled by ieee802111_txq_may_transmit() will be woken up
>>>> again
>>>> by a check added to the ieee80211_wake_txqs() tasklet.
>>>> 
>>> 
>>> Toke,
>>> 
>>> I am observing soft lockup issues again with this new series while
>>> running traffic with 50 clients. I am continuing testing with earlier
>>> series along with snippet I shared.
>> 
>> Are these new lockups (that was not in your patched previous version),
>> or did I just not get all your lock-related fixes incorporated?
>> 
>>> When driver operates in pull-mode, throttled txqs are marked and
>>> refilled in airtime_tasklet. This is causing major throughput drops
>>> and packet loss and I am suspecting the latency in replenishing
>>> deficit. Whereas in push-mode or in ath9k model, refill happens
>>> quicker at every packet indication as well as tx completion.
>> 
>> Yeah, the tasklet shouldn't be the main source of deficit replenishing.
>> Can see why that would give bad performance :)
>> 
>>> I am planning to get rid of tasklet completely as it is only meant for
>>> pull-mode. It would be better to refill in may_transmit() itself.
>> 
>> Hmm, right. So the way to do this correctly (from a fairness point of
>> view) would be something like this (in max_tx()):
>> 
>> if (this_txq.stn.deficit > 0)
>>   return true;
>> 
>> else if (any queued TXQ currently have positive deficit)
>>   return false; /* other TXQ should try may_tx() later and get 
>> permission */
>> 
>> else /* all deficits < 0 */
>>   return replenish_deficits(this_txq);
>> 
>> And replenish_deficits() would be something like:
>> 
>> replenish_deficits(this_txq) {
>> repeat:
>>   for (txq in queued txqs) {
>>     txq.stn.deficit += stn.weight;
>>     if (txq.stn.deficit > 0 && !wake_txq)
>>       wake_txq = txq;
>>   }
>>   if not wake_txq:
>>     goto repeat;
>> 
>>   if (this_txq.stn.deficit > 0)
>>     return true;
>>   else
>>     drv_wake_tx_queue(wake_txq);
>> }
>> 
>> The wake_tx_queue call may have to be delegated to a tasklet still, to
>> avoid the infinite recursion problem I mentioned earlier. But the
>> tasklet could be made simpler and wouldn't have to be called so 
>> often...
>> 
>> Does the above make sense?
>> 
> Hmm... mine is bit different. txqs are refilled only once for all txqs.
> It will give more opportunity for non-served txqs. drv_wake_tx_queue 
> won't be
> called from may_tx as the driver anyway will not push packets in 
> pull-mode.

So, as far as I can tell, this requires the hardware to "keep trying"?
I.e., if it just stops scheduling a TXQ after may_transmit() returns
false, there is no guarantee that that TXQ will ever get re-awoken
unless a new packet arrives for it?

-Toke
Rajkumar Manoharan Oct. 12, 2018, 7:38 a.m. UTC | #5
On 2018-10-11 03:38, Toke Høiland-Jørgensen wrote:
> Rajkumar Manoharan <rmanohar@codeaurora.org> writes:
> 
>> Hmm... mine is bit different. txqs are refilled only once for all 
>> txqs.
>> It will give more opportunity for non-served txqs. drv_wake_tx_queue
>> won't be
>> called from may_tx as the driver anyway will not push packets in
>> pull-mode.
> 
> So, as far as I can tell, this requires the hardware to "keep trying"?
> I.e., if it just stops scheduling a TXQ after may_transmit() returns
> false, there is no guarantee that that TXQ will ever get re-awoken
> unless a new packet arrives for it?
> 
That is true and even now ath10k operates the same way in pull mode. Not
just packet arrival, even napi poll routine tries to pushes the packets.
One more thing, fetch indication may pull ~4ms/8ms of packets from each 
tid.
This makes deficit too low and so refilling txqs by just airtime_weight 
becomes
cumbersome. In may_transmit, the deficit are incremented by 20 * 
airtime_weight.
In future this will be also replaced by station specific quantum. we can 
revisit
this once BQL in place. Performance issue is resolved by this approach.
Do you foresee any issues?


#define IEEE80211_TXQ_MAY_TX_QUANTUM  20
bool ieee80211_txq_may_transmit(struct ieee80211_hw *hw,
                                 struct ieee80211_txq *txq)
{
         struct ieee80211_local *local = hw_to_local(hw);
         struct txq_info *txqi = to_txq_info(txq);
         struct sta_info *sta;
         u8 ac = txq->ac;

         lockdep_assert_held(&local->active_txq_lock[ac]);

         if (!txqi->txq.sta)
                 goto out;

         sta = container_of(txqi->txq.sta, struct sta_info, sta);
         if (sta->airtime[ac].deficit >= 0)
                 goto out;

         list_for_each_entry(txqi, &local->active_txqs[ac], 
schedule_order) {
                 if (!txqi->txq.sta)
                         continue;
                 sta = container_of(txqi->txq.sta, struct sta_info, sta);
                 sta->airtime[ac].deficit +=
                         (IEEE80211_TXQ_MAY_TX_QUANTUM * 
sta->airtime_weight);
         }

         return false;

  out:
         list_del_init(&txqi->schedule_order);
         return true;
}

-Rajkumar
Toke Høiland-Jørgensen Oct. 12, 2018, 10:16 a.m. UTC | #6
Rajkumar Manoharan <rmanohar@codeaurora.org> writes:

> On 2018-10-11 03:38, Toke Høiland-Jørgensen wrote:
>> Rajkumar Manoharan <rmanohar@codeaurora.org> writes:
>> 
>>> Hmm... mine is bit different. txqs are refilled only once for all 
>>> txqs.
>>> It will give more opportunity for non-served txqs. drv_wake_tx_queue
>>> won't be
>>> called from may_tx as the driver anyway will not push packets in
>>> pull-mode.
>> 
>> So, as far as I can tell, this requires the hardware to "keep trying"?
>> I.e., if it just stops scheduling a TXQ after may_transmit() returns
>> false, there is no guarantee that that TXQ will ever get re-awoken
>> unless a new packet arrives for it?
>> 
> That is true and even now ath10k operates the same way in pull mode.
> Not just packet arrival, even napi poll routine tries to pushes the
> packets.

I'm not sure I'm following? At every NAPI poll, the driver tries to push
to *all* TXQs?

> One more thing, fetch indication may pull ~4ms/8ms of packets from
> each tid. This makes deficit too low and so refilling txqs by just
> airtime_weight becomes cumbersome.

Yeah, in general we can't assume that each dequeue uses the same amount
of airtime as the quantum. This is why there's a loop; to fill up
quantum until the first stations gets into the positive.

> In may_transmit, the deficit are incremented by 20 * airtime_weight.
> In future this will be also replaced by station specific quantum. we
> can revisit this once BQL in place. Performance issue is resolved by
> this approach. Do you foresee any issues?

Just using a larger quantum will work as long as all stations send
roughly the same amount of data (airtime) at each transmission. Which is
often the case when you're benchmarking, but not in general. Think of
the size of the quantum as the granularity that the scheduler can
provide fairness at.

What I'd suggest is that instead of increasing the quantum, you do one
of the following:

- Just loop with the smaller quantum until one of the stations go into
  the positive (what we do now).

- Go through all active stations, find the one that is closest being in
  the positive, and add that amount to the quantum. I.e., something
  like (assuming no station has positive deficit; if one does, you don't
  want to add anything anyway):
  
  to_add = -(max(stn.deficit) for stn in active stations)
  for stn in active stations:
    stn.deficit += to_add + stn.weight


-Toke
Dave Taht Oct. 13, 2018, 7:09 a.m. UTC | #7
On Fri, Oct 12, 2018 at 12:38 AM Rajkumar Manoharan
<rmanohar@codeaurora.org> wrote:
>
> On 2018-10-11 03:38, Toke Høiland-Jørgensen wrote:
> > Rajkumar Manoharan <rmanohar@codeaurora.org> writes:
> >
> >> Hmm... mine is bit different. txqs are refilled only once for all
> >> txqs.
> >> It will give more opportunity for non-served txqs. drv_wake_tx_queue
> >> won't be
> >> called from may_tx as the driver anyway will not push packets in
> >> pull-mode.
> >
> > So, as far as I can tell, this requires the hardware to "keep trying"?
> > I.e., if it just stops scheduling a TXQ after may_transmit() returns
> > false, there is no guarantee that that TXQ will ever get re-awoken
> > unless a new packet arrives for it?
> >
> That is true and even now ath10k operates the same way in pull mode. Not
> just packet arrival, even napi poll routine tries to pushes the packets.
> One more thing, fetch indication may pull ~4ms/8ms of packets from each
> tid.
> This makes deficit too low and so refilling txqs by just airtime_weight
> becomes
> cumbersome. In may_transmit, the deficit are incremented by 20 *
> airtime_weight.
> In future this will be also replaced by station specific quantum. we can
> revisit
> this once BQL in place. Performance issue is resolved by this approach.
> Do you foresee any issues?

I'll have some time in the coming weeks to be able to test this stuff.
I'm mostly interested
in algorithmic correctness more than the API changes...

Is there a version of these patches that is stable enough on ath9 or ath10k?

Do I foresee any issues? Jeeze, no, we *never* have any issues with wifi.

"fetch indication may pull ~4ms/8ms of packets from each tid"

made me really twitchy.
>
> #define IEEE80211_TXQ_MAY_TX_QUANTUM  20
> bool ieee80211_txq_may_transmit(struct ieee80211_hw *hw,
>                                  struct ieee80211_txq *txq)
> {
>          struct ieee80211_local *local = hw_to_local(hw);
>          struct txq_info *txqi = to_txq_info(txq);
>          struct sta_info *sta;
>          u8 ac = txq->ac;
>
>          lockdep_assert_held(&local->active_txq_lock[ac]);
>
>          if (!txqi->txq.sta)
>                  goto out;
>
>          sta = container_of(txqi->txq.sta, struct sta_info, sta);
>          if (sta->airtime[ac].deficit >= 0)
>                  goto out;
>
>          list_for_each_entry(txqi, &local->active_txqs[ac],
> schedule_order) {
>                  if (!txqi->txq.sta)
>                          continue;
>                  sta = container_of(txqi->txq.sta, struct sta_info, sta);
>                  sta->airtime[ac].deficit +=
>                          (IEEE80211_TXQ_MAY_TX_QUANTUM *
> sta->airtime_weight);
>          }
>
>          return false;
>
>   out:
>          list_del_init(&txqi->schedule_order);
>          return true;
> }
>
> -Rajkumar
> _______________________________________________
> Make-wifi-fast mailing list
> Make-wifi-fast@lists.bufferbloat.net
> https://lists.bufferbloat.net/listinfo/make-wifi-fast
Rajkumar Manoharan Oct. 16, 2018, 7:07 a.m. UTC | #8
On 2018-10-12 03:16, Toke Høiland-Jørgensen wrote:
> 
> - Just loop with the smaller quantum until one of the stations go into
>   the positive (what we do now).
> 
> - Go through all active stations, find the one that is closest being in
>   the positive, and add that amount to the quantum. I.e., something
>   like (assuming no station has positive deficit; if one does, you 
> don't
>   want to add anything anyway):
> 
>   to_add = -(max(stn.deficit) for stn in active stations)
>   for stn in active stations:
>     stn.deficit += to_add + stn.weight
> 
Toke,

Sorry for the delayed response. I did lot of experiments. Below are my 
observations.
Sorry for lengthy reply.

In current model, next_txq() is main routine that serves DRR and 
fairness is
enforced by serving only only first txq. Here the first node could be 
either
newly initiated traffic or returned node by return_txq(). This works 
perfectly
as long as the driver is running any RR algo.

Whereas in ath10k, firmware runs its own RR in pull mode and builds txq 
list
based on driver's hostq table. In this case it can not be simply assumed 
that
firmware always gives fetch request for first node of mac80211's txq 
list.
i.e both RR algo could be out of sync.

Two major differences b/w ath9k and ath10k

1) Serving txqs
The ath9k always serves txq by next_txq and so that the txqs_list is 
rotated to serve
other txq. But in ath10k (pull-mode), first node becomes sticky one 
until it is
picked by firmware via fetch indication and it becomes negative deficit.
The sequence is followed in wake_tx_queue

    - dequeue first node
    - push is not allowed
    - enqueue same txq back to head

2) Refill rate of deficit.

The ath9k refills deficit mostly in hot path by next_txq() in tx & isr 
routine.
In case of ath10k, due to above problem, deficits wont be filled in hot 
path.
Either it should be filled in fetch_ind itself or by scheduling another 
task.
Both the approaches are slower compared to hot path when the driver is 
bursting
aggregation. On an idle condition a single fetch indication can dequeue 
~190 msdus
from each tid of give stn list. This drains the deficit quickly and 
becomes too low.
To speed up this, either refill the station by multiples of stn airtime 
weight or
allows the txqs_list rotation. So that next_txq will be used for 
refilling deficit.

Attaching return_txq() change that helps to get rid of quantum multiple.

-Rajkumar
diff --git a/drivers/net/wireless/ath/ath10k/htt_rx.c b/drivers/net/wireless/ath/ath10k/htt_rx.c
index 625a4ab37ea0..269ae8311056 100644
--- a/drivers/net/wireless/ath/ath10k/htt_rx.c
+++ b/drivers/net/wireless/ath/ath10k/htt_rx.c
@@ -2352,7 +2352,7 @@ static void ath10k_htt_rx_tx_fetch_ind(struct ath10k *ar, struct sk_buff *skb)
 			num_msdus++;
 			num_bytes += ret;
 		}
-		ieee80211_return_txq(hw, txq);
+		ieee80211_return_txq(hw, txq, true);
 		ieee80211_txq_schedule_end(hw, txq->ac);
 
 		record->num_msdus = cpu_to_le16(num_msdus);
diff --git a/drivers/net/wireless/ath/ath10k/mac.c b/drivers/net/wireless/ath/ath10k/mac.c
index cf64d9e02a24..d39bc841ea04 100644
--- a/drivers/net/wireless/ath/ath10k/mac.c
+++ b/drivers/net/wireless/ath/ath10k/mac.c
@@ -4206,7 +4206,7 @@ static int ath10k_mac_schedule_txq(struct ieee80211_hw *hw, u32 ac)
 			if (ret < 0)
 				break;
 		}
-		ieee80211_return_txq(hw, txq);
+		ieee80211_return_txq(hw, txq, true);
 		ath10k_htt_tx_txq_update(hw, txq);
 		if (ret == -EBUSY)
 			break;
@@ -4475,18 +4475,21 @@ static void ath10k_mac_op_wake_tx_queue(struct ieee80211_hw *hw,
 {
 	u8 ac = txq->ac;
 	int ret = 0;
+	bool pushed = false;
 
+	ath10k_htt_tx_txq_update(hw, txq);
 	ieee80211_txq_schedule_start(hw, ac);
 	txq = ieee80211_next_txq(hw, ac);
 	if (!txq)
 		goto out;
 
 	while (ath10k_mac_tx_can_push(hw, txq)) {
+		pushed = true;
 		ret = ath10k_mac_tx_push_txq(hw, txq);
 		if (ret < 0)
 			break;
 	}
-	ieee80211_return_txq(hw, txq);
+	ieee80211_return_txq(hw, txq, pushed);
 	ath10k_htt_tx_txq_update(hw, txq);
 out:
 	ieee80211_txq_schedule_end(hw, ac);
diff --git a/drivers/net/wireless/ath/ath9k/xmit.c b/drivers/net/wireless/ath/ath9k/xmit.c
index 6aab06909e76..40ff0bdbf7c9 100644
--- a/drivers/net/wireless/ath/ath9k/xmit.c
+++ b/drivers/net/wireless/ath/ath9k/xmit.c
@@ -117,7 +117,7 @@ void ath_tx_queue_tid(struct ath_softc *sc, struct ath_atx_tid *tid)
 	struct ieee80211_txq *queue = container_of(
 		(void *)tid, struct ieee80211_txq, drv_priv);
 
-	ieee80211_return_txq(sc->hw, queue);
+	ieee80211_return_txq(sc->hw, queue, false);
 }
 
 void ath9k_wake_tx_queue(struct ieee80211_hw *hw, struct ieee80211_txq *queue)
@@ -1913,7 +1913,7 @@ void ath_txq_schedule(struct ath_softc *sc, struct ath_txq *txq)
 		ret = ath_tx_sched_aggr(sc, txq, tid);
 		ath_dbg(common, QUEUE, "ath_tx_sched_aggr returned %d\n", ret);
 
-		ieee80211_return_txq(hw, queue);
+		ieee80211_return_txq(hw, queue, false);
 	}
 
 out:
diff --git a/include/net/mac80211.h b/include/net/mac80211.h
index 9cadfa408f50..995e19e29d9e 100644
--- a/include/net/mac80211.h
+++ b/include/net/mac80211.h
@@ -6102,7 +6102,7 @@ struct ieee80211_txq *ieee80211_next_txq(struct ieee80211_hw *hw, u8 ac);
  * Should only be called between calls to ieee80211_txq_schedule_start()
  * and ieee80211_txq_schedule_end().
  */
-void ieee80211_return_txq(struct ieee80211_hw *hw, struct ieee80211_txq *txq);
+void ieee80211_return_txq(struct ieee80211_hw *hw, struct ieee80211_txq *txqi, bool to_tail);
 
 /**
  * ieee80211_txq_schedule_start - acquire locks for safe scheduling of an AC
diff --git a/net/mac80211/driver-ops.h b/net/mac80211/driver-ops.h
index 39dc40ac5abe..530270f8cc52 100644
--- a/net/mac80211/driver-ops.h
+++ b/net/mac80211/driver-ops.h
@@ -1175,7 +1175,7 @@ static inline void schedule_and_wake_txq(struct ieee80211_local *local,
 					 struct txq_info *txqi)
 {
 	spin_lock_bh(&local->active_txq_lock[txqi->txq.ac]);
-	ieee80211_return_txq(&local->hw, &txqi->txq);
+	ieee80211_return_txq(&local->hw, &txqi->txq, false);
 	spin_unlock_bh(&local->active_txq_lock[txqi->txq.ac]);
 	drv_wake_tx_queue(local, txqi);
 }
diff --git a/net/mac80211/tx.c b/net/mac80211/tx.c
index d8682930a469..acf0505bdd7f 100644
--- a/net/mac80211/tx.c
+++ b/net/mac80211/tx.c
@@ -3623,7 +3623,8 @@ struct ieee80211_txq *ieee80211_next_txq(struct ieee80211_hw *hw, u8 ac)
 EXPORT_SYMBOL(ieee80211_next_txq);
 
 void ieee80211_return_txq(struct ieee80211_hw *hw,
-			    struct ieee80211_txq *txq)
+			  struct ieee80211_txq *txq,
+			  bool to_tail)
 {
 	struct ieee80211_local *local = hw_to_local(hw);
 	struct txq_info *txqi = to_txq_info(txq);
@@ -3641,7 +3642,7 @@ void ieee80211_return_txq(struct ieee80211_hw *hw,
 		 */
 		if (wiphy_ext_feature_isset(local->hw.wiphy,
 					    NL80211_EXT_FEATURE_AIRTIME_FAIRNESS)
-		    && txqi->txq.sta)
+		    && txqi->txq.sta && !to_tail)
 			list_add(&txqi->schedule_order,
 				 &local->active_txqs[txq->ac]);
 		else
@@ -3652,7 +3653,6 @@ void ieee80211_return_txq(struct ieee80211_hw *hw,
 }
 EXPORT_SYMBOL(ieee80211_return_txq);
 
-#define IEEE80211_TXQ_MAY_TX_QUANTUM  20
 bool ieee80211_txq_may_transmit(struct ieee80211_hw *hw,
 				struct ieee80211_txq *txq)
 {
@@ -3670,13 +3670,8 @@ bool ieee80211_txq_may_transmit(struct ieee80211_hw *hw,
 	if (sta->airtime[ac].deficit >= 0)
 		goto out;
 
-	list_for_each_entry(txqi, &local->active_txqs[ac], schedule_order) {
-		if (!txqi->txq.sta)
-			continue;
-		sta = container_of(txqi->txq.sta, struct sta_info, sta);
-		sta->airtime[ac].deficit +=
-			(IEEE80211_TXQ_MAY_TX_QUANTUM * sta->airtime_weight);
-	}
+	sta->airtime[ac].deficit += sta->airtime_weight;
+	list_move_tail(&txqi->schedule_order, &local->active_txqs[ac]);
 
 	return false;
Toke Høiland-Jørgensen Oct. 16, 2018, 10:20 a.m. UTC | #9
Rajkumar Manoharan <rmanohar@codeaurora.org> writes:

> On 2018-10-12 03:16, Toke Høiland-Jørgensen wrote:
>> 
>> - Just loop with the smaller quantum until one of the stations go into
>>   the positive (what we do now).
>> 
>> - Go through all active stations, find the one that is closest being in
>>   the positive, and add that amount to the quantum. I.e., something
>>   like (assuming no station has positive deficit; if one does, you 
>> don't
>>   want to add anything anyway):
>> 
>>   to_add = -(max(stn.deficit) for stn in active stations)
>>   for stn in active stations:
>>     stn.deficit += to_add + stn.weight
>> 
> Toke,
>
> Sorry for the delayed response. I did lot of experiments. Below are my 
> observations.
> Sorry for lengthy reply.
>
> In current model, next_txq() is main routine that serves DRR and
> fairness is enforced by serving only only first txq. Here the first
> node could be either newly initiated traffic or returned node by
> return_txq(). This works perfectly as long as the driver is running
> any RR algo.
>
> Whereas in ath10k, firmware runs its own RR in pull mode and builds
> txq list based on driver's hostq table. In this case it can not be
> simply assumed that firmware always gives fetch request for first node
> of mac80211's txq list. i.e both RR algo could be out of sync.

So I'm wondering why they don't sync; if the hardware is just doing RR
scheduling, eventually it should hit the TXQ that's first in the queue
and keep in sync after that?

How are you testing, and what metrics are you using?

> On an idle condition a single fetch indication can dequeue ~190 msdus
> from each tid of give stn list.

Wow, that sounds pretty bad. Guess we need the airtime queue limits! :)

> diff --git a/drivers/net/wireless/ath/ath10k/htt_rx.c b/drivers/net/wireless/ath/ath10k/htt_rx.c
> index 625a4ab37ea0..269ae8311056 100644
> --- a/drivers/net/wireless/ath/ath10k/htt_rx.c
> +++ b/drivers/net/wireless/ath/ath10k/htt_rx.c
> @@ -2352,7 +2352,7 @@ static void ath10k_htt_rx_tx_fetch_ind(struct ath10k *ar, struct sk_buff *skb)
>  			num_msdus++;
>  			num_bytes += ret;
>  		}
> -		ieee80211_return_txq(hw, txq);
> +		ieee80211_return_txq(hw, txq, true);

I don't like the extra parameter; a similar one was in an earlier
version of my patch set, but I'd prefer that mac80211 just does the
right thing...

Do I understand it correctly that push/pull mode is selected solely by
hardware/firmware versions? Because in that case we could split it into
two feature flags instead...

> @@ -3670,13 +3670,8 @@ bool ieee80211_txq_may_transmit(struct ieee80211_hw *hw,
>  	if (sta->airtime[ac].deficit >= 0)
>  		goto out;
>  
> -	list_for_each_entry(txqi, &local->active_txqs[ac], schedule_order) {
> -		if (!txqi->txq.sta)
> -			continue;
> -		sta = container_of(txqi->txq.sta, struct sta_info, sta);
> -		sta->airtime[ac].deficit +=
> -			(IEEE80211_TXQ_MAY_TX_QUANTUM * sta->airtime_weight);
> -	}
> +	sta->airtime[ac].deficit += sta->airtime_weight;
> +	list_move_tail(&txqi->schedule_order, &local->active_txqs[ac]);

I'm wondering whether this actually succeeds in achieving fairness? This
basically allows a TXQ to be plucked from any point in the list, get a
quantum increase and be put back on, no matter the state of other TXQs.

Did you test how well the stations divide their airtime? And if so,
under which conditions?

-Toke
diff mbox series

Patch

diff --git a/include/net/mac80211.h b/include/net/mac80211.h
index 469e88a32f94..fa10420a53ff 100644
--- a/include/net/mac80211.h
+++ b/include/net/mac80211.h
@@ -5349,6 +5349,34 @@  void ieee80211_sta_eosp(struct ieee80211_sta *pubsta);
  */
 void ieee80211_send_eosp_nullfunc(struct ieee80211_sta *pubsta, int tid);
 
+/**
+ * ieee80211_sta_register_airtime - register airtime usage for a sta/tid
+ *
+ * Register airtime usage for a given sta on a given tid. The driver can call
+ * this function to notify mac80211 that a station used a certain amount of
+ * airtime. This information will be used by the TXQ scheduler to schedule
+ * stations in a way that ensures airtime fairness.
+ *
+ * The reported airtime should as a minimum include all time that is spent
+ * transmitting to the remote station, including overhead and padding, but not
+ * including time spent waiting for a TXOP. If the time is not reported by the
+ * hardware it can in some cases be calculated from the rate and known frame
+ * composition. When possible, the time should include any failed transmission
+ * attempts.
+ *
+ * The driver can either call this function synchronously for every packet or
+ * aggregate, or asynchronously as airtime usage information becomes available.
+ * TX and RX airtime can be reported together, or separately by setting one of
+ * them to 0.
+ *
+ * @pubsta: the station
+ * @tid: the TID to register airtime for
+ * @tx_airtime: airtime used during TX (in usec)
+ * @rx_airtime: airtime used during RX (in usec)
+ */
+void ieee80211_sta_register_airtime(struct ieee80211_sta *pubsta, u8 tid,
+				    u32 tx_airtime, u32 rx_airtime);
+
 /**
  * ieee80211_iter_keys - iterate keys programmed into the device
  * @hw: pointer obtained from ieee80211_alloc_hw()
@@ -6105,6 +6133,30 @@  void ieee80211_txq_schedule_start(struct ieee80211_hw *hw, u8 ac);
  */
 void ieee80211_txq_schedule_end(struct ieee80211_hw *hw, u8 ac);
 
+/**
+ * ieee80211_txq_may_transmit - check whether TXQ is allowed to transmit
+ *
+ * This function is used to check whether given txq is allowed to transmit by
+ * the airtime scheduler, and can be used by drivers to access the airtime
+ * fairness accounting without going using the scheduling order enfored by
+ * next_txq().
+ *
+ * Returns %true if the airtime scheduler thinks the TXQ should be allowed to
+ * transmit, and %false if it should be throttled. This function can also have
+ * the side effect of rotating the TXQ in the scheduler rotation, which will
+ * eventually bring the deficit to positive and allow the station to transmit
+ * again. If a TXQ is throttled, it will be marked and eventually woken up again
+ * through drv_wake_tx_queue().
+ *
+ * If this function returns %true, the driver is expected to schedule packets
+ * for transmission, and then return the TXQ through ieee80211_return_txq().
+ *
+ * @hw: pointer as obtained from ieee80211_alloc_hw()
+ * @txq: pointer obtained from station or virtual interface
+ */
+bool ieee80211_txq_may_transmit(struct ieee80211_hw *hw,
+				struct ieee80211_txq *txq);
+
 /**
  * ieee80211_txq_get_depth - get pending frame/byte count of given txq
  *
diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c
index 504627e2117f..c640d3ee5f04 100644
--- a/net/mac80211/cfg.c
+++ b/net/mac80211/cfg.c
@@ -1388,6 +1388,9 @@  static int sta_apply_parameters(struct ieee80211_local *local,
 	if (ieee80211_vif_is_mesh(&sdata->vif))
 		sta_apply_mesh_params(local, sta, params);
 
+	if (params->airtime_weight)
+		sta->airtime_weight = params->airtime_weight;
+
 	/* set the STA state after all sta info from usermode has been set */
 	if (test_sta_flag(sta, WLAN_STA_TDLS_PEER) ||
 	    set & BIT(NL80211_STA_FLAG_ASSOCIATED)) {
diff --git a/net/mac80211/debugfs.c b/net/mac80211/debugfs.c
index 3fe541e358f3..81c5fec2eae7 100644
--- a/net/mac80211/debugfs.c
+++ b/net/mac80211/debugfs.c
@@ -383,6 +383,9 @@  void debugfs_hw_add(struct ieee80211_local *local)
 	if (local->ops->wake_tx_queue)
 		DEBUGFS_ADD_MODE(aqm, 0600);
 
+	debugfs_create_u16("airtime_flags", 0600,
+			   phyd, &local->airtime_flags);
+
 	statsd = debugfs_create_dir("statistics", phyd);
 
 	/* if the dir failed, don't put all the other things into the root! */
diff --git a/net/mac80211/debugfs_sta.c b/net/mac80211/debugfs_sta.c
index af5185a836e5..701c532d95a6 100644
--- a/net/mac80211/debugfs_sta.c
+++ b/net/mac80211/debugfs_sta.c
@@ -181,9 +181,10 @@  static ssize_t sta_aqm_read(struct file *file, char __user *userbuf,
 			       txqi->tin.tx_bytes,
 			       txqi->tin.tx_packets,
 			       txqi->flags,
-			       txqi->flags & (1<<IEEE80211_TXQ_STOP) ? "STOP" : "RUN",
-			       txqi->flags & (1<<IEEE80211_TXQ_AMPDU) ? " AMPDU" : "",
-			       txqi->flags & (1<<IEEE80211_TXQ_NO_AMSDU) ? " NO-AMSDU" : "");
+			       test_bit(IEEE80211_TXQ_STOP, &txqi->flags) ? "STOP" :
+			       test_bit(IEEE80211_TXQ_AIRTIME_THROTTLE, &txqi->flags) ? "THROTTLE" : "RUN",
+			       test_bit(IEEE80211_TXQ_AMPDU, &txqi->flags) ? " AMPDU" : "",
+			       test_bit(IEEE80211_TXQ_NO_AMSDU, &txqi->flags) ? " NO-AMSDU" : "");
 	}
 
 	rcu_read_unlock();
@@ -195,6 +196,46 @@  static ssize_t sta_aqm_read(struct file *file, char __user *userbuf,
 }
 STA_OPS(aqm);
 
+static ssize_t sta_airtime_read(struct file *file, char __user *userbuf,
+				size_t count, loff_t *ppos)
+{
+	struct sta_info *sta = file->private_data;
+	struct ieee80211_local *local = sta->sdata->local;
+	size_t bufsz = 200;
+	char *buf = kzalloc(bufsz, GFP_KERNEL), *p = buf;
+	u64 rx_airtime = 0, tx_airtime = 0;
+	s64 deficit[IEEE80211_NUM_ACS];
+	ssize_t rv;
+	int ac;
+
+	if (!buf)
+		return -ENOMEM;
+
+	for (ac = 0; ac < IEEE80211_NUM_ACS; ac++) {
+		spin_lock_bh(&local->active_txq_lock[ac]);
+		rx_airtime += sta->airtime[ac].rx_airtime;
+		tx_airtime += sta->airtime[ac].tx_airtime;
+		deficit[ac] = sta->airtime[ac].deficit;
+		spin_unlock_bh(&local->active_txq_lock[ac]);
+	}
+
+	p += scnprintf(p, bufsz + buf - p,
+		"RX: %llu us\nTX: %llu us\nWeight: %u\n"
+		"Deficit: VO: %lld us VI: %lld us BE: %lld us BK: %lld us\n",
+		rx_airtime,
+		tx_airtime,
+		sta->airtime_weight,
+		deficit[0],
+		deficit[1],
+		deficit[2],
+		deficit[3]);
+
+	rv = simple_read_from_buffer(userbuf, count, ppos, buf, p - buf);
+	kfree(buf);
+	return rv;
+}
+STA_OPS(airtime);
+
 static ssize_t sta_agg_status_read(struct file *file, char __user *userbuf,
 					size_t count, loff_t *ppos)
 {
@@ -906,6 +947,10 @@  void ieee80211_sta_debugfs_add(struct sta_info *sta)
 	if (local->ops->wake_tx_queue)
 		DEBUGFS_ADD(aqm);
 
+	if (wiphy_ext_feature_isset(local->hw.wiphy,
+				    NL80211_EXT_FEATURE_AIRTIME_FAIRNESS))
+		DEBUGFS_ADD(airtime);
+
 	if (sizeof(sta->driver_buffered_tids) == sizeof(u32))
 		debugfs_create_x32("driver_buffered_tids", 0400,
 				   sta->debugfs_dir,
diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h
index 976531717902..21040d9ad693 100644
--- a/net/mac80211/ieee80211_i.h
+++ b/net/mac80211/ieee80211_i.h
@@ -819,6 +819,7 @@  enum txq_info_flags {
 	IEEE80211_TXQ_AMPDU,
 	IEEE80211_TXQ_NO_AMSDU,
 	IEEE80211_TXQ_STOP_NETIF_TX,
+	IEEE80211_TXQ_AIRTIME_THROTTLE,
 };
 
 /**
@@ -1136,6 +1137,8 @@  struct ieee80211_local {
 	struct list_head active_txqs[IEEE80211_NUM_ACS];
 	u16 schedule_round[IEEE80211_NUM_ACS];
 
+	u16 airtime_flags;
+
 	const struct ieee80211_ops *ops;
 
 	/*
@@ -1240,6 +1243,7 @@  struct ieee80211_local {
 	struct sk_buff_head pending[IEEE80211_MAX_QUEUES];
 	struct tasklet_struct tx_pending_tasklet;
 	struct tasklet_struct wake_txqs_tasklet;
+	struct tasklet_struct airtime_tasklet;
 
 	atomic_t agg_queue_stop[IEEE80211_MAX_QUEUES];
 
@@ -2053,6 +2057,7 @@  void ieee80211_txq_remove_vlan(struct ieee80211_local *local,
 void ieee80211_fill_txq_stats(struct cfg80211_txq_stats *txqstats,
 			      struct txq_info *txqi);
 void ieee80211_wake_txqs(unsigned long data);
+void ieee80211_kick_airtime(unsigned long data);
 void ieee80211_send_auth(struct ieee80211_sub_if_data *sdata,
 			 u16 transaction, u16 auth_alg, u16 status,
 			 const u8 *extra, size_t extra_len, const u8 *bssid,
diff --git a/net/mac80211/main.c b/net/mac80211/main.c
index d9315de90b48..cfa0b7936e14 100644
--- a/net/mac80211/main.c
+++ b/net/mac80211/main.c
@@ -667,6 +667,7 @@  struct ieee80211_hw *ieee80211_alloc_hw_nm(size_t priv_data_len,
 		INIT_LIST_HEAD(&local->active_txqs[i]);
 		spin_lock_init(&local->active_txq_lock[i]);
 	}
+	local->airtime_flags = AIRTIME_USE_TX | AIRTIME_USE_RX;
 
 	INIT_LIST_HEAD(&local->chanctx_list);
 	mutex_init(&local->chanctx_mtx);
@@ -702,9 +703,12 @@  struct ieee80211_hw *ieee80211_alloc_hw_nm(size_t priv_data_len,
 	tasklet_init(&local->tx_pending_tasklet, ieee80211_tx_pending,
 		     (unsigned long)local);
 
-	if (ops->wake_tx_queue)
+	if (ops->wake_tx_queue) {
 		tasklet_init(&local->wake_txqs_tasklet, ieee80211_wake_txqs,
 			     (unsigned long)local);
+		tasklet_init(&local->airtime_tasklet, ieee80211_kick_airtime,
+			     (unsigned long)local);
+	}
 
 	tasklet_init(&local->tasklet,
 		     ieee80211_tasklet_handler,
diff --git a/net/mac80211/sta_info.c b/net/mac80211/sta_info.c
index c2f5cb7df54f..98b7e9239df1 100644
--- a/net/mac80211/sta_info.c
+++ b/net/mac80211/sta_info.c
@@ -90,7 +90,6 @@  static void __cleanup_single_sta(struct sta_info *sta)
 	struct tid_ampdu_tx *tid_tx;
 	struct ieee80211_sub_if_data *sdata = sta->sdata;
 	struct ieee80211_local *local = sdata->local;
-	struct fq *fq = &local->fq;
 	struct ps_data *ps;
 
 	if (test_sta_flag(sta, WLAN_STA_PS_STA) ||
@@ -120,9 +119,7 @@  static void __cleanup_single_sta(struct sta_info *sta)
 
 			txqi = to_txq_info(sta->sta.txq[i]);
 
-			spin_lock_bh(&fq->lock);
 			ieee80211_txq_purge(local, txqi);
-			spin_unlock_bh(&fq->lock);
 		}
 	}
 
@@ -387,9 +384,12 @@  struct sta_info *sta_info_alloc(struct ieee80211_sub_if_data *sdata,
 	if (sta_prepare_rate_control(local, sta, gfp))
 		goto free_txq;
 
+	sta->airtime_weight = IEEE80211_DEFAULT_AIRTIME_WEIGHT;
+
 	for (i = 0; i < IEEE80211_NUM_ACS; i++) {
 		skb_queue_head_init(&sta->ps_tx_buf[i]);
 		skb_queue_head_init(&sta->tx_filtered[i]);
+		sta->airtime[i].deficit = sta->airtime_weight;
 	}
 
 	for (i = 0; i < IEEE80211_NUM_TIDS; i++)
@@ -1826,6 +1826,35 @@  void ieee80211_sta_set_buffered(struct ieee80211_sta *pubsta,
 }
 EXPORT_SYMBOL(ieee80211_sta_set_buffered);
 
+void ieee80211_sta_register_airtime(struct ieee80211_sta *pubsta, u8 tid,
+				    u32 tx_airtime, u32 rx_airtime)
+{
+	struct sta_info *sta = container_of(pubsta, struct sta_info, sta);
+	struct ieee80211_local *local = sta->sdata->local;
+	struct txq_info *txqi;
+	u8 ac = ieee80211_ac_from_tid(tid);
+	u32 airtime = 0;
+
+	if (sta->local->airtime_flags & AIRTIME_USE_TX)
+		airtime += tx_airtime;
+	if (sta->local->airtime_flags & AIRTIME_USE_RX)
+		airtime += rx_airtime;
+
+	spin_lock_bh(&local->active_txq_lock[ac]);
+	sta->airtime[ac].tx_airtime += tx_airtime;
+	sta->airtime[ac].rx_airtime += rx_airtime;
+	sta->airtime[ac].deficit -= airtime;
+
+	if (sta->airtime[ac].deficit < 0) {
+		txqi = to_txq_info(pubsta->txq[tid]);
+		if (list_empty(&txqi->schedule_order))
+			list_add_tail(&txqi->schedule_order,
+				      &local->active_txqs[ac]);
+	}
+	spin_unlock_bh(&local->active_txq_lock[ac]);
+}
+EXPORT_SYMBOL(ieee80211_sta_register_airtime);
+
 int sta_info_move_state(struct sta_info *sta,
 			enum ieee80211_sta_state new_state)
 {
@@ -2188,6 +2217,23 @@  void sta_set_sinfo(struct sta_info *sta, struct station_info *sinfo,
 		sinfo->filled |= BIT_ULL(NL80211_STA_INFO_TX_FAILED);
 	}
 
+	if (!(sinfo->filled & BIT(NL80211_STA_INFO_RX_DURATION))) {
+		for (ac = 0; ac < IEEE80211_NUM_ACS; ac++)
+			sinfo->rx_duration += sta->airtime[ac].rx_airtime;
+		sinfo->filled |= BIT(NL80211_STA_INFO_RX_DURATION);
+	}
+
+	if (!(sinfo->filled & BIT(NL80211_STA_INFO_TX_DURATION))) {
+		for (ac = 0; ac < IEEE80211_NUM_ACS; ac++)
+			sinfo->tx_duration += sta->airtime[ac].tx_airtime;
+		sinfo->filled |= BIT(NL80211_STA_INFO_TX_DURATION);
+	}
+
+	if (!(sinfo->filled & BIT(NL80211_STA_INFO_AIRTIME_WEIGHT))) {
+		sinfo->airtime_weight = sta->airtime_weight;
+		sinfo->filled |= BIT(NL80211_STA_INFO_AIRTIME_WEIGHT);
+	}
+
 	sinfo->rx_dropped_misc = sta->rx_stats.dropped;
 	if (sta->pcpu_rx_stats) {
 		for_each_possible_cpu(cpu) {
diff --git a/net/mac80211/sta_info.h b/net/mac80211/sta_info.h
index 9a04327d71d1..b1b0fd6a2e21 100644
--- a/net/mac80211/sta_info.h
+++ b/net/mac80211/sta_info.h
@@ -127,6 +127,16 @@  enum ieee80211_agg_stop_reason {
 	AGG_STOP_DESTROY_STA,
 };
 
+/* Debugfs flags to enable/disable use of RX/TX airtime in scheduler */
+#define AIRTIME_USE_TX		BIT(0)
+#define AIRTIME_USE_RX		BIT(1)
+
+struct airtime_info {
+	u64 rx_airtime;
+	u64 tx_airtime;
+	s64 deficit;
+};
+
 struct sta_info;
 
 /**
@@ -563,6 +573,9 @@  struct sta_info {
 	} tx_stats;
 	u16 tid_seq[IEEE80211_QOS_CTL_TID_MASK + 1];
 
+	struct airtime_info airtime[IEEE80211_NUM_ACS];
+	u16 airtime_weight;
+
 	/*
 	 * Aggregation information, locked with lock.
 	 */
diff --git a/net/mac80211/status.c b/net/mac80211/status.c
index 9a6d7208bf4f..664379797c46 100644
--- a/net/mac80211/status.c
+++ b/net/mac80211/status.c
@@ -821,6 +821,12 @@  static void __ieee80211_tx_status(struct ieee80211_hw *hw,
 			ieee80211_sta_tx_notify(sta->sdata, (void *) skb->data,
 						acked, info->status.tx_time);
 
+		if (info->status.tx_time &&
+		    wiphy_ext_feature_isset(local->hw.wiphy,
+					    NL80211_EXT_FEATURE_AIRTIME_FAIRNESS))
+			ieee80211_sta_register_airtime(&sta->sta, tid,
+						       info->status.tx_time, 0);
+
 		if (ieee80211_hw_check(&local->hw, REPORTS_TX_ACK_STATUS)) {
 			if (info->flags & IEEE80211_TX_STAT_ACK) {
 				if (sta->status_stats.lost_packets)
diff --git a/net/mac80211/tx.c b/net/mac80211/tx.c
index 1e071121cb44..e86d3e464523 100644
--- a/net/mac80211/tx.c
+++ b/net/mac80211/tx.c
@@ -1484,8 +1484,11 @@  void ieee80211_txq_purge(struct ieee80211_local *local,
 	struct fq *fq = &local->fq;
 	struct fq_tin *tin = &txqi->tin;
 
+	spin_lock_bh(&fq->lock);
 	fq_tin_reset(fq, tin, fq_skb_free_func);
 	ieee80211_purge_tx_queue(&local->hw, &txqi->frags);
+	spin_unlock_bh(&fq->lock);
+
 	spin_lock_bh(&local->active_txq_lock[txqi->txq.ac]);
 	list_del_init(&txqi->schedule_order);
 	spin_unlock_bh(&local->active_txq_lock[txqi->txq.ac]);
@@ -3634,11 +3637,27 @@  struct ieee80211_txq *ieee80211_next_txq(struct ieee80211_hw *hw, u8 ac)
 
 	lockdep_assert_held(&local->active_txq_lock[ac]);
 
+ begin:
 	txqi = list_first_entry_or_null(&local->active_txqs[ac],
 					struct txq_info,
 					schedule_order);
+	if (!txqi)
+		return NULL;
+
+	if (txqi->txq.sta) {
+		struct sta_info *sta = container_of(txqi->txq.sta,
+						struct sta_info, sta);
+
+		if (sta->airtime[txqi->txq.ac].deficit < 0) {
+			sta->airtime[txqi->txq.ac].deficit += sta->airtime_weight;
+			list_move_tail(&txqi->schedule_order,
+				       &local->active_txqs[txqi->txq.ac]);
+			goto begin;
+		}
+	}
+
 
-	if (!txqi || txqi->schedule_round == local->schedule_round[ac])
+	if (txqi->schedule_round == local->schedule_round[ac])
 		return NULL;
 
 	list_del_init(&txqi->schedule_order);
@@ -3656,13 +3675,71 @@  void ieee80211_return_txq(struct ieee80211_hw *hw,
 	lockdep_assert_held(&local->active_txq_lock[txq->ac]);
 
 	if (list_empty(&txqi->schedule_order) &&
-	    (!skb_queue_empty(&txqi->frags) || txqi->tin.backlog_packets))
-		list_add_tail(&txqi->schedule_order,
-			      &local->active_txqs[txq->ac]);
+	    (!skb_queue_empty(&txqi->frags) || txqi->tin.backlog_packets)) {
+		/* If airtime accounting is active, always enqueue STAs at the
+		 * head of the list to ensure that they only get moved to the
+		 * back by the airtime DRR scheduler once they have a negative
+		 * deficit. A station that already has a negative deficit will
+		 * get immediately moved to the back of the list on the next
+		 * call to ieee80211_next_txq().
+		 */
+		if (wiphy_ext_feature_isset(local->hw.wiphy,
+					    NL80211_EXT_FEATURE_AIRTIME_FAIRNESS)
+		    && txqi->txq.sta)
+			list_add(&txqi->schedule_order,
+				 &local->active_txqs[txq->ac]);
+		else
+			list_add_tail(&txqi->schedule_order,
+				      &local->active_txqs[txq->ac]);
+	}
 
 }
 EXPORT_SYMBOL(ieee80211_return_txq);
 
+bool ieee80211_txq_may_transmit(struct ieee80211_hw *hw,
+				struct ieee80211_txq *txq)
+{
+	struct ieee80211_local *local = hw_to_local(hw);
+	struct txq_info *txqi = to_txq_info(txq);
+	struct sta_info *sta;
+	bool ret = false;
+
+	lockdep_assert_held(&local->active_txq_lock[txqi->txq.ac]);
+
+	if (!txqi->txq.sta) {
+		ret = true;
+		goto out;
+	}
+
+	sta = container_of(txqi->txq.sta, struct sta_info, sta);
+	if (sta->airtime[txqi->txq.ac].deficit >= 0) {
+		ret = true;
+		goto out;
+	}
+
+	/* Not currently eligible, but if the txq is first in the scheduler
+	 * queue, increase deficit and rotate queues so it may be eligible
+	 * next time.
+	 */
+	if (txqi == list_first_entry(&local->active_txqs[txqi->txq.ac],
+				     struct txq_info,
+				     schedule_order)) {
+		sta->airtime[txqi->txq.ac].deficit += sta->airtime_weight;
+		list_move_tail(&txqi->schedule_order,
+			       &local->active_txqs[txqi->txq.ac]);
+	}
+
+ out:
+	if (ret) {
+		clear_bit(IEEE80211_TXQ_AIRTIME_THROTTLE, &txqi->flags);
+		list_del_init(&txqi->schedule_order);
+	} else
+		set_bit(IEEE80211_TXQ_AIRTIME_THROTTLE, &txqi->flags);
+
+	return ret;
+}
+EXPORT_SYMBOL(ieee80211_txq_may_transmit);
+
 void ieee80211_txq_schedule_start(struct ieee80211_hw *hw, u8 ac)
 {
 	struct ieee80211_local *local = hw_to_local(hw);
@@ -3677,6 +3754,7 @@  void ieee80211_txq_schedule_end(struct ieee80211_hw *hw, u8 ac)
 	struct ieee80211_local *local = hw_to_local(hw);
 
 	spin_unlock_bh(&local->active_txq_lock[ac]);
+	tasklet_schedule(&local->airtime_tasklet);
 }
 EXPORT_SYMBOL(ieee80211_txq_schedule_end);
 
diff --git a/net/mac80211/util.c b/net/mac80211/util.c
index 36a3c2ada515..bcad2298a975 100644
--- a/net/mac80211/util.c
+++ b/net/mac80211/util.c
@@ -240,6 +240,77 @@  __le16 ieee80211_ctstoself_duration(struct ieee80211_hw *hw,
 }
 EXPORT_SYMBOL(ieee80211_ctstoself_duration);
 
+static bool __ieee80211_kick_airtime(struct ieee80211_local *local, int ac)
+{
+	struct txq_info *txqi = NULL;
+	bool reschedule = false;
+	struct sta_info *sta;
+
+	spin_lock_bh(&local->active_txq_lock[ac]);
+
+ begin:
+	if (list_empty(&local->active_txqs[ac]))
+		goto out;
+
+	list_for_each_entry(txqi, &local->active_txqs[ac], schedule_order) {
+		/* If we find a non-sta TXQ before an eligible STA-TXQ, schedule
+		 * this */
+		if (!txqi->txq.sta)
+			goto out;
+
+		sta = container_of(txqi->txq.sta, struct sta_info, sta);
+
+		if (sta->removed) {
+			reschedule = true;
+			txqi = NULL;
+			goto out;
+		}
+
+		if (sta->airtime[ac].deficit >= 0) {
+			if (!test_and_clear_bit(IEEE80211_TXQ_AIRTIME_THROTTLE,
+						&txqi->flags))
+				continue;
+
+			goto out;
+		}
+	}
+
+	/* We made it through the list without finding an eligible TXQ, which
+	 * means TX is stuck; increase all deficits to get things unstuck, then
+	 * start over.
+	 */
+	list_for_each_entry(txqi, &local->active_txqs[ac], schedule_order) {
+		sta = container_of(txqi->txq.sta, struct sta_info, sta);
+		sta->airtime[ac].deficit += sta->airtime_weight;
+	}
+	goto begin;
+
+ out:
+	spin_unlock_bh(&local->active_txq_lock[ac]);
+	if (txqi)
+		drv_wake_tx_queue(local, txqi);
+
+	return reschedule;
+}
+
+void ieee80211_kick_airtime(unsigned long data)
+{
+	struct ieee80211_local *local = (struct ieee80211_local *)data;
+	bool reschedule = false;
+	int i;
+
+	rcu_read_lock();
+
+	for (i = 0; i < IEEE80211_NUM_ACS; i++)
+		if (__ieee80211_kick_airtime(local, i))
+			reschedule = true;
+
+	rcu_read_unlock();
+
+	if (reschedule)
+		tasklet_schedule(&local->airtime_tasklet);
+}
+
 static void __ieee80211_wake_txqs(struct ieee80211_sub_if_data *sdata, int ac)
 {
 	struct ieee80211_local *local = sdata->local;
@@ -250,6 +321,10 @@  static void __ieee80211_wake_txqs(struct ieee80211_sub_if_data *sdata, int ac)
 	struct sta_info *sta;
 	int i;
 
+	if (wiphy_ext_feature_isset(local->hw.wiphy,
+				    NL80211_EXT_FEATURE_AIRTIME_FAIRNESS))
+	    __ieee80211_kick_airtime(local, ac);
+
 	spin_lock_bh(&fq->lock);
 
 	if (sdata->vif.type == NL80211_IFTYPE_AP)