| // SPDX-License-Identifier: ISC |
| /* |
| * Copyright (C) 2018 Felix Fietkau <nbd@nbd.name> |
| */ |
| #include "mt76.h" |
| |
| static unsigned long mt76_aggr_tid_to_timeo(u8 tidno) |
| { |
| /* Currently voice traffic (AC_VO) always runs without aggregation, |
| * no special handling is needed. AC_BE/AC_BK use tids 0-3. Just check |
| * for non AC_BK/AC_BE and set smaller timeout for it. */ |
| return HZ / (tidno >= 4 ? 25 : 10); |
| } |
| |
| static void |
| mt76_aggr_release(struct mt76_rx_tid *tid, struct sk_buff_head *frames, int idx) |
| { |
| struct sk_buff *skb; |
| |
| tid->head = ieee80211_sn_inc(tid->head); |
| |
| skb = tid->reorder_buf[idx]; |
| if (!skb) |
| return; |
| |
| tid->reorder_buf[idx] = NULL; |
| tid->nframes--; |
| __skb_queue_tail(frames, skb); |
| } |
| |
| static void |
| mt76_rx_aggr_release_frames(struct mt76_rx_tid *tid, |
| struct sk_buff_head *frames, |
| u16 head) |
| { |
| int idx; |
| |
| while (ieee80211_sn_less(tid->head, head)) { |
| idx = tid->head % tid->size; |
| mt76_aggr_release(tid, frames, idx); |
| } |
| } |
| |
| static void |
| mt76_rx_aggr_release_head(struct mt76_rx_tid *tid, struct sk_buff_head *frames) |
| { |
| int idx = tid->head % tid->size; |
| |
| while (tid->reorder_buf[idx]) { |
| mt76_aggr_release(tid, frames, idx); |
| idx = tid->head % tid->size; |
| } |
| } |
| |
| static void |
| mt76_rx_aggr_check_release(struct mt76_rx_tid *tid, struct sk_buff_head *frames) |
| { |
| struct mt76_rx_status *status; |
| struct sk_buff *skb; |
| int start, idx, nframes; |
| |
| if (!tid->nframes) |
| return; |
| |
| mt76_rx_aggr_release_head(tid, frames); |
| |
| start = tid->head % tid->size; |
| nframes = tid->nframes; |
| |
| for (idx = (tid->head + 1) % tid->size; |
| idx != start && nframes; |
| idx = (idx + 1) % tid->size) { |
| skb = tid->reorder_buf[idx]; |
| if (!skb) |
| continue; |
| |
| nframes--; |
| status = (struct mt76_rx_status *)skb->cb; |
| if (!time_after32(jiffies, |
| status->reorder_time + |
| mt76_aggr_tid_to_timeo(tid->num))) |
| continue; |
| |
| mt76_rx_aggr_release_frames(tid, frames, status->seqno); |
| } |
| |
| mt76_rx_aggr_release_head(tid, frames); |
| } |
| |
| static void |
| mt76_rx_aggr_reorder_work(struct work_struct *work) |
| { |
| struct mt76_rx_tid *tid = container_of(work, struct mt76_rx_tid, |
| reorder_work.work); |
| struct mt76_dev *dev = tid->dev; |
| struct sk_buff_head frames; |
| int nframes; |
| |
| __skb_queue_head_init(&frames); |
| |
| local_bh_disable(); |
| rcu_read_lock(); |
| |
| spin_lock(&tid->lock); |
| mt76_rx_aggr_check_release(tid, &frames); |
| nframes = tid->nframes; |
| spin_unlock(&tid->lock); |
| |
| if (nframes) |
| ieee80211_queue_delayed_work(tid->dev->hw, &tid->reorder_work, |
| mt76_aggr_tid_to_timeo(tid->num)); |
| mt76_rx_complete(dev, &frames, NULL); |
| |
| rcu_read_unlock(); |
| local_bh_enable(); |
| } |
| |
| static void |
| mt76_rx_aggr_check_ctl(struct sk_buff *skb, struct sk_buff_head *frames) |
| { |
| struct mt76_rx_status *status = (struct mt76_rx_status *)skb->cb; |
| struct ieee80211_bar *bar = mt76_skb_get_hdr(skb); |
| struct mt76_wcid *wcid = status->wcid; |
| struct mt76_rx_tid *tid; |
| u8 tidno = status->qos_ctl & IEEE80211_QOS_CTL_TID_MASK; |
| u16 seqno; |
| |
| if (!ieee80211_is_ctl(bar->frame_control)) |
| return; |
| |
| if (!ieee80211_is_back_req(bar->frame_control)) |
| return; |
| |
| status->qos_ctl = tidno = le16_to_cpu(bar->control) >> 12; |
| seqno = IEEE80211_SEQ_TO_SN(le16_to_cpu(bar->start_seq_num)); |
| tid = rcu_dereference(wcid->aggr[tidno]); |
| if (!tid) |
| return; |
| |
| spin_lock_bh(&tid->lock); |
| if (!tid->stopped) { |
| mt76_rx_aggr_release_frames(tid, frames, seqno); |
| mt76_rx_aggr_release_head(tid, frames); |
| } |
| spin_unlock_bh(&tid->lock); |
| } |
| |
| void mt76_rx_aggr_reorder(struct sk_buff *skb, struct sk_buff_head *frames) |
| { |
| struct mt76_rx_status *status = (struct mt76_rx_status *)skb->cb; |
| struct mt76_wcid *wcid = status->wcid; |
| struct ieee80211_sta *sta; |
| struct mt76_rx_tid *tid; |
| bool sn_less; |
| u16 seqno, head, size, idx; |
| u8 tidno = status->qos_ctl & IEEE80211_QOS_CTL_TID_MASK; |
| u8 ackp; |
| |
| __skb_queue_tail(frames, skb); |
| |
| sta = wcid_to_sta(wcid); |
| if (!sta) |
| return; |
| |
| if (!status->aggr) { |
| if (!(status->flag & RX_FLAG_8023)) |
| mt76_rx_aggr_check_ctl(skb, frames); |
| return; |
| } |
| |
| /* not part of a BA session */ |
| ackp = status->qos_ctl & IEEE80211_QOS_CTL_ACK_POLICY_MASK; |
| if (ackp == IEEE80211_QOS_CTL_ACK_POLICY_NOACK) |
| return; |
| |
| tid = rcu_dereference(wcid->aggr[tidno]); |
| if (!tid) |
| return; |
| |
| status->flag |= RX_FLAG_DUP_VALIDATED; |
| spin_lock_bh(&tid->lock); |
| |
| if (tid->stopped) |
| goto out; |
| |
| head = tid->head; |
| seqno = status->seqno; |
| size = tid->size; |
| sn_less = ieee80211_sn_less(seqno, head); |
| |
| if (!tid->started) { |
| if (sn_less) |
| goto out; |
| |
| tid->started = true; |
| } |
| |
| if (sn_less) { |
| __skb_unlink(skb, frames); |
| dev_kfree_skb(skb); |
| goto out; |
| } |
| |
| if (seqno == head) { |
| tid->head = ieee80211_sn_inc(head); |
| if (tid->nframes) |
| mt76_rx_aggr_release_head(tid, frames); |
| goto out; |
| } |
| |
| __skb_unlink(skb, frames); |
| |
| /* |
| * Frame sequence number exceeds buffering window, free up some space |
| * by releasing previous frames |
| */ |
| if (!ieee80211_sn_less(seqno, head + size)) { |
| head = ieee80211_sn_inc(ieee80211_sn_sub(seqno, size)); |
| mt76_rx_aggr_release_frames(tid, frames, head); |
| } |
| |
| idx = seqno % size; |
| |
| /* Discard if the current slot is already in use */ |
| if (tid->reorder_buf[idx]) { |
| dev_kfree_skb(skb); |
| goto out; |
| } |
| |
| status->reorder_time = jiffies; |
| tid->reorder_buf[idx] = skb; |
| tid->nframes++; |
| mt76_rx_aggr_release_head(tid, frames); |
| |
| ieee80211_queue_delayed_work(tid->dev->hw, &tid->reorder_work, |
| mt76_aggr_tid_to_timeo(tid->num)); |
| |
| out: |
| spin_unlock_bh(&tid->lock); |
| } |
| |
| int mt76_rx_aggr_start(struct mt76_dev *dev, struct mt76_wcid *wcid, u8 tidno, |
| u16 ssn, u16 size) |
| { |
| struct mt76_rx_tid *tid; |
| |
| mt76_rx_aggr_stop(dev, wcid, tidno); |
| |
| tid = kzalloc(struct_size(tid, reorder_buf, size), GFP_KERNEL); |
| if (!tid) |
| return -ENOMEM; |
| |
| tid->dev = dev; |
| tid->head = ssn; |
| tid->size = size; |
| tid->num = tidno; |
| INIT_DELAYED_WORK(&tid->reorder_work, mt76_rx_aggr_reorder_work); |
| spin_lock_init(&tid->lock); |
| |
| rcu_assign_pointer(wcid->aggr[tidno], tid); |
| |
| return 0; |
| } |
| EXPORT_SYMBOL_GPL(mt76_rx_aggr_start); |
| |
| static void mt76_rx_aggr_shutdown(struct mt76_dev *dev, struct mt76_rx_tid *tid) |
| { |
| u16 size = tid->size; |
| int i; |
| |
| spin_lock_bh(&tid->lock); |
| |
| tid->stopped = true; |
| for (i = 0; tid->nframes && i < size; i++) { |
| struct sk_buff *skb = tid->reorder_buf[i]; |
| |
| if (!skb) |
| continue; |
| |
| tid->reorder_buf[i] = NULL; |
| tid->nframes--; |
| dev_kfree_skb(skb); |
| } |
| |
| spin_unlock_bh(&tid->lock); |
| |
| cancel_delayed_work_sync(&tid->reorder_work); |
| } |
| |
| void mt76_rx_aggr_stop(struct mt76_dev *dev, struct mt76_wcid *wcid, u8 tidno) |
| { |
| struct mt76_rx_tid *tid = NULL; |
| |
| tid = rcu_replace_pointer(wcid->aggr[tidno], tid, |
| lockdep_is_held(&dev->mutex)); |
| if (tid) { |
| mt76_rx_aggr_shutdown(dev, tid); |
| kfree_rcu(tid, rcu_head); |
| } |
| } |
| EXPORT_SYMBOL_GPL(mt76_rx_aggr_stop); |