diff options
author | Michal Kazior <michal.kazior@tieto.com> | 2014-07-16 06:09:31 -0400 |
---|---|---|
committer | Johannes Berg <johannes.berg@intel.com> | 2014-07-21 10:17:26 -0400 |
commit | 83eb935ec74a91468776cd86415abcb6ee23cca8 (patch) | |
tree | e0977824eadbba57f30adaa47f7d31c2a60ed6f1 /net/mac80211 | |
parent | 60e83deb4c1e7e8b6ab78e7331288bf4211bdeb6 (diff) |
mac80211: fix Rx reordering with RX_FLAG_AMSDU_MORE
Some drivers (e.g. ath10k) report A-MSDU subframes
individually with identical seqno. The A-MPDU Rx
reorder code did not account for that which made
it practically unusable with drivers using
RX_FLAG_AMSDU_MORE because it would end up
dropping a lot of frames resulting in confusion in
upper network transport layers.
Signed-off-by: Michal Kazior <michal.kazior@tieto.com>
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
Diffstat (limited to 'net/mac80211')
-rw-r--r-- | net/mac80211/agg-rx.c | 9 | ||||
-rw-r--r-- | net/mac80211/ieee80211_i.h | 15 | ||||
-rw-r--r-- | net/mac80211/rx.c | 57 | ||||
-rw-r--r-- | net/mac80211/sta_info.h | 5 |
4 files changed, 62 insertions, 24 deletions
diff --git a/net/mac80211/agg-rx.c b/net/mac80211/agg-rx.c index 31bf2586fb84..d38c49b644cd 100644 --- a/net/mac80211/agg-rx.c +++ b/net/mac80211/agg-rx.c | |||
@@ -52,7 +52,7 @@ static void ieee80211_free_tid_rx(struct rcu_head *h) | |||
52 | del_timer_sync(&tid_rx->reorder_timer); | 52 | del_timer_sync(&tid_rx->reorder_timer); |
53 | 53 | ||
54 | for (i = 0; i < tid_rx->buf_size; i++) | 54 | for (i = 0; i < tid_rx->buf_size; i++) |
55 | dev_kfree_skb(tid_rx->reorder_buf[i]); | 55 | __skb_queue_purge(&tid_rx->reorder_buf[i]); |
56 | kfree(tid_rx->reorder_buf); | 56 | kfree(tid_rx->reorder_buf); |
57 | kfree(tid_rx->reorder_time); | 57 | kfree(tid_rx->reorder_time); |
58 | kfree(tid_rx); | 58 | kfree(tid_rx); |
@@ -232,7 +232,7 @@ void ieee80211_process_addba_request(struct ieee80211_local *local, | |||
232 | struct tid_ampdu_rx *tid_agg_rx; | 232 | struct tid_ampdu_rx *tid_agg_rx; |
233 | u16 capab, tid, timeout, ba_policy, buf_size, start_seq_num, status; | 233 | u16 capab, tid, timeout, ba_policy, buf_size, start_seq_num, status; |
234 | u8 dialog_token; | 234 | u8 dialog_token; |
235 | int ret = -EOPNOTSUPP; | 235 | int i, ret = -EOPNOTSUPP; |
236 | 236 | ||
237 | /* extract session parameters from addba request frame */ | 237 | /* extract session parameters from addba request frame */ |
238 | dialog_token = mgmt->u.action.u.addba_req.dialog_token; | 238 | dialog_token = mgmt->u.action.u.addba_req.dialog_token; |
@@ -308,7 +308,7 @@ void ieee80211_process_addba_request(struct ieee80211_local *local, | |||
308 | 308 | ||
309 | /* prepare reordering buffer */ | 309 | /* prepare reordering buffer */ |
310 | tid_agg_rx->reorder_buf = | 310 | tid_agg_rx->reorder_buf = |
311 | kcalloc(buf_size, sizeof(struct sk_buff *), GFP_KERNEL); | 311 | kcalloc(buf_size, sizeof(struct sk_buff_head), GFP_KERNEL); |
312 | tid_agg_rx->reorder_time = | 312 | tid_agg_rx->reorder_time = |
313 | kcalloc(buf_size, sizeof(unsigned long), GFP_KERNEL); | 313 | kcalloc(buf_size, sizeof(unsigned long), GFP_KERNEL); |
314 | if (!tid_agg_rx->reorder_buf || !tid_agg_rx->reorder_time) { | 314 | if (!tid_agg_rx->reorder_buf || !tid_agg_rx->reorder_time) { |
@@ -318,6 +318,9 @@ void ieee80211_process_addba_request(struct ieee80211_local *local, | |||
318 | goto end; | 318 | goto end; |
319 | } | 319 | } |
320 | 320 | ||
321 | for (i = 0; i < buf_size; i++) | ||
322 | __skb_queue_head_init(&tid_agg_rx->reorder_buf[i]); | ||
323 | |||
321 | ret = drv_ampdu_action(local, sta->sdata, IEEE80211_AMPDU_RX_START, | 324 | ret = drv_ampdu_action(local, sta->sdata, IEEE80211_AMPDU_RX_START, |
322 | &sta->sta, tid, &start_seq_num, 0); | 325 | &sta->sta, tid, &start_seq_num, 0); |
323 | ht_dbg(sta->sdata, "Rx A-MPDU request on %pM tid %d result %d\n", | 326 | ht_dbg(sta->sdata, "Rx A-MPDU request on %pM tid %d result %d\n", |
diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h index 49731dd044bb..c504e99a5404 100644 --- a/net/mac80211/ieee80211_i.h +++ b/net/mac80211/ieee80211_i.h | |||
@@ -1729,6 +1729,21 @@ static inline void ieee802_11_parse_elems(const u8 *start, size_t len, | |||
1729 | ieee802_11_parse_elems_crc(start, len, action, elems, 0, 0); | 1729 | ieee802_11_parse_elems_crc(start, len, action, elems, 0, 0); |
1730 | } | 1730 | } |
1731 | 1731 | ||
1732 | static inline bool ieee80211_rx_reorder_ready(struct sk_buff_head *frames) | ||
1733 | { | ||
1734 | struct sk_buff *tail = skb_peek_tail(frames); | ||
1735 | struct ieee80211_rx_status *status; | ||
1736 | |||
1737 | if (!tail) | ||
1738 | return false; | ||
1739 | |||
1740 | status = IEEE80211_SKB_RXCB(tail); | ||
1741 | if (status->flag & RX_FLAG_AMSDU_MORE) | ||
1742 | return false; | ||
1743 | |||
1744 | return true; | ||
1745 | } | ||
1746 | |||
1732 | void ieee80211_dynamic_ps_enable_work(struct work_struct *work); | 1747 | void ieee80211_dynamic_ps_enable_work(struct work_struct *work); |
1733 | void ieee80211_dynamic_ps_disable_work(struct work_struct *work); | 1748 | void ieee80211_dynamic_ps_disable_work(struct work_struct *work); |
1734 | void ieee80211_dynamic_ps_timer(unsigned long data); | 1749 | void ieee80211_dynamic_ps_timer(unsigned long data); |
diff --git a/net/mac80211/rx.c b/net/mac80211/rx.c index 5a786d489f7e..bd2c9b22c945 100644 --- a/net/mac80211/rx.c +++ b/net/mac80211/rx.c | |||
@@ -688,20 +688,27 @@ static void ieee80211_release_reorder_frame(struct ieee80211_sub_if_data *sdata, | |||
688 | int index, | 688 | int index, |
689 | struct sk_buff_head *frames) | 689 | struct sk_buff_head *frames) |
690 | { | 690 | { |
691 | struct sk_buff *skb = tid_agg_rx->reorder_buf[index]; | 691 | struct sk_buff_head *skb_list = &tid_agg_rx->reorder_buf[index]; |
692 | struct sk_buff *skb; | ||
692 | struct ieee80211_rx_status *status; | 693 | struct ieee80211_rx_status *status; |
693 | 694 | ||
694 | lockdep_assert_held(&tid_agg_rx->reorder_lock); | 695 | lockdep_assert_held(&tid_agg_rx->reorder_lock); |
695 | 696 | ||
696 | if (!skb) | 697 | if (skb_queue_empty(skb_list)) |
697 | goto no_frame; | 698 | goto no_frame; |
698 | 699 | ||
699 | /* release the frame from the reorder ring buffer */ | 700 | if (!ieee80211_rx_reorder_ready(skb_list)) { |
701 | __skb_queue_purge(skb_list); | ||
702 | goto no_frame; | ||
703 | } | ||
704 | |||
705 | /* release frames from the reorder ring buffer */ | ||
700 | tid_agg_rx->stored_mpdu_num--; | 706 | tid_agg_rx->stored_mpdu_num--; |
701 | tid_agg_rx->reorder_buf[index] = NULL; | 707 | while ((skb = __skb_dequeue(skb_list))) { |
702 | status = IEEE80211_SKB_RXCB(skb); | 708 | status = IEEE80211_SKB_RXCB(skb); |
703 | status->rx_flags |= IEEE80211_RX_DEFERRED_RELEASE; | 709 | status->rx_flags |= IEEE80211_RX_DEFERRED_RELEASE; |
704 | __skb_queue_tail(frames, skb); | 710 | __skb_queue_tail(frames, skb); |
711 | } | ||
705 | 712 | ||
706 | no_frame: | 713 | no_frame: |
707 | tid_agg_rx->head_seq_num = ieee80211_sn_inc(tid_agg_rx->head_seq_num); | 714 | tid_agg_rx->head_seq_num = ieee80211_sn_inc(tid_agg_rx->head_seq_num); |
@@ -738,13 +745,13 @@ static void ieee80211_sta_reorder_release(struct ieee80211_sub_if_data *sdata, | |||
738 | struct tid_ampdu_rx *tid_agg_rx, | 745 | struct tid_ampdu_rx *tid_agg_rx, |
739 | struct sk_buff_head *frames) | 746 | struct sk_buff_head *frames) |
740 | { | 747 | { |
741 | int index, j; | 748 | int index, i, j; |
742 | 749 | ||
743 | lockdep_assert_held(&tid_agg_rx->reorder_lock); | 750 | lockdep_assert_held(&tid_agg_rx->reorder_lock); |
744 | 751 | ||
745 | /* release the buffer until next missing frame */ | 752 | /* release the buffer until next missing frame */ |
746 | index = tid_agg_rx->head_seq_num % tid_agg_rx->buf_size; | 753 | index = tid_agg_rx->head_seq_num % tid_agg_rx->buf_size; |
747 | if (!tid_agg_rx->reorder_buf[index] && | 754 | if (!ieee80211_rx_reorder_ready(&tid_agg_rx->reorder_buf[index]) && |
748 | tid_agg_rx->stored_mpdu_num) { | 755 | tid_agg_rx->stored_mpdu_num) { |
749 | /* | 756 | /* |
750 | * No buffers ready to be released, but check whether any | 757 | * No buffers ready to be released, but check whether any |
@@ -753,7 +760,8 @@ static void ieee80211_sta_reorder_release(struct ieee80211_sub_if_data *sdata, | |||
753 | int skipped = 1; | 760 | int skipped = 1; |
754 | for (j = (index + 1) % tid_agg_rx->buf_size; j != index; | 761 | for (j = (index + 1) % tid_agg_rx->buf_size; j != index; |
755 | j = (j + 1) % tid_agg_rx->buf_size) { | 762 | j = (j + 1) % tid_agg_rx->buf_size) { |
756 | if (!tid_agg_rx->reorder_buf[j]) { | 763 | if (!ieee80211_rx_reorder_ready( |
764 | &tid_agg_rx->reorder_buf[j])) { | ||
757 | skipped++; | 765 | skipped++; |
758 | continue; | 766 | continue; |
759 | } | 767 | } |
@@ -762,6 +770,11 @@ static void ieee80211_sta_reorder_release(struct ieee80211_sub_if_data *sdata, | |||
762 | HT_RX_REORDER_BUF_TIMEOUT)) | 770 | HT_RX_REORDER_BUF_TIMEOUT)) |
763 | goto set_release_timer; | 771 | goto set_release_timer; |
764 | 772 | ||
773 | /* don't leave incomplete A-MSDUs around */ | ||
774 | for (i = (index + 1) % tid_agg_rx->buf_size; i != j; | ||
775 | i = (i + 1) % tid_agg_rx->buf_size) | ||
776 | __skb_queue_purge(&tid_agg_rx->reorder_buf[i]); | ||
777 | |||
765 | ht_dbg_ratelimited(sdata, | 778 | ht_dbg_ratelimited(sdata, |
766 | "release an RX reorder frame due to timeout on earlier frames\n"); | 779 | "release an RX reorder frame due to timeout on earlier frames\n"); |
767 | ieee80211_release_reorder_frame(sdata, tid_agg_rx, j, | 780 | ieee80211_release_reorder_frame(sdata, tid_agg_rx, j, |
@@ -775,7 +788,8 @@ static void ieee80211_sta_reorder_release(struct ieee80211_sub_if_data *sdata, | |||
775 | skipped) & IEEE80211_SN_MASK; | 788 | skipped) & IEEE80211_SN_MASK; |
776 | skipped = 0; | 789 | skipped = 0; |
777 | } | 790 | } |
778 | } else while (tid_agg_rx->reorder_buf[index]) { | 791 | } else while (ieee80211_rx_reorder_ready( |
792 | &tid_agg_rx->reorder_buf[index])) { | ||
779 | ieee80211_release_reorder_frame(sdata, tid_agg_rx, index, | 793 | ieee80211_release_reorder_frame(sdata, tid_agg_rx, index, |
780 | frames); | 794 | frames); |
781 | index = tid_agg_rx->head_seq_num % tid_agg_rx->buf_size; | 795 | index = tid_agg_rx->head_seq_num % tid_agg_rx->buf_size; |
@@ -786,7 +800,8 @@ static void ieee80211_sta_reorder_release(struct ieee80211_sub_if_data *sdata, | |||
786 | 800 | ||
787 | for (; j != (index - 1) % tid_agg_rx->buf_size; | 801 | for (; j != (index - 1) % tid_agg_rx->buf_size; |
788 | j = (j + 1) % tid_agg_rx->buf_size) { | 802 | j = (j + 1) % tid_agg_rx->buf_size) { |
789 | if (tid_agg_rx->reorder_buf[j]) | 803 | if (ieee80211_rx_reorder_ready( |
804 | &tid_agg_rx->reorder_buf[j])) | ||
790 | break; | 805 | break; |
791 | } | 806 | } |
792 | 807 | ||
@@ -811,6 +826,7 @@ static bool ieee80211_sta_manage_reorder_buf(struct ieee80211_sub_if_data *sdata | |||
811 | struct sk_buff_head *frames) | 826 | struct sk_buff_head *frames) |
812 | { | 827 | { |
813 | struct ieee80211_hdr *hdr = (struct ieee80211_hdr *) skb->data; | 828 | struct ieee80211_hdr *hdr = (struct ieee80211_hdr *) skb->data; |
829 | struct ieee80211_rx_status *status = IEEE80211_SKB_RXCB(skb); | ||
814 | u16 sc = le16_to_cpu(hdr->seq_ctrl); | 830 | u16 sc = le16_to_cpu(hdr->seq_ctrl); |
815 | u16 mpdu_seq_num = (sc & IEEE80211_SCTL_SEQ) >> 4; | 831 | u16 mpdu_seq_num = (sc & IEEE80211_SCTL_SEQ) >> 4; |
816 | u16 head_seq_num, buf_size; | 832 | u16 head_seq_num, buf_size; |
@@ -845,7 +861,7 @@ static bool ieee80211_sta_manage_reorder_buf(struct ieee80211_sub_if_data *sdata | |||
845 | index = mpdu_seq_num % tid_agg_rx->buf_size; | 861 | index = mpdu_seq_num % tid_agg_rx->buf_size; |
846 | 862 | ||
847 | /* check if we already stored this frame */ | 863 | /* check if we already stored this frame */ |
848 | if (tid_agg_rx->reorder_buf[index]) { | 864 | if (ieee80211_rx_reorder_ready(&tid_agg_rx->reorder_buf[index])) { |
849 | dev_kfree_skb(skb); | 865 | dev_kfree_skb(skb); |
850 | goto out; | 866 | goto out; |
851 | } | 867 | } |
@@ -858,17 +874,20 @@ static bool ieee80211_sta_manage_reorder_buf(struct ieee80211_sub_if_data *sdata | |||
858 | */ | 874 | */ |
859 | if (mpdu_seq_num == tid_agg_rx->head_seq_num && | 875 | if (mpdu_seq_num == tid_agg_rx->head_seq_num && |
860 | tid_agg_rx->stored_mpdu_num == 0) { | 876 | tid_agg_rx->stored_mpdu_num == 0) { |
861 | tid_agg_rx->head_seq_num = | 877 | if (!(status->flag & RX_FLAG_AMSDU_MORE)) |
862 | ieee80211_sn_inc(tid_agg_rx->head_seq_num); | 878 | tid_agg_rx->head_seq_num = |
879 | ieee80211_sn_inc(tid_agg_rx->head_seq_num); | ||
863 | ret = false; | 880 | ret = false; |
864 | goto out; | 881 | goto out; |
865 | } | 882 | } |
866 | 883 | ||
867 | /* put the frame in the reordering buffer */ | 884 | /* put the frame in the reordering buffer */ |
868 | tid_agg_rx->reorder_buf[index] = skb; | 885 | __skb_queue_tail(&tid_agg_rx->reorder_buf[index], skb); |
869 | tid_agg_rx->reorder_time[index] = jiffies; | 886 | if (!(status->flag & RX_FLAG_AMSDU_MORE)) { |
870 | tid_agg_rx->stored_mpdu_num++; | 887 | tid_agg_rx->reorder_time[index] = jiffies; |
871 | ieee80211_sta_reorder_release(sdata, tid_agg_rx, frames); | 888 | tid_agg_rx->stored_mpdu_num++; |
889 | ieee80211_sta_reorder_release(sdata, tid_agg_rx, frames); | ||
890 | } | ||
872 | 891 | ||
873 | out: | 892 | out: |
874 | spin_unlock(&tid_agg_rx->reorder_lock); | 893 | spin_unlock(&tid_agg_rx->reorder_lock); |
diff --git a/net/mac80211/sta_info.h b/net/mac80211/sta_info.h index e37f00969526..d411bcc8ef08 100644 --- a/net/mac80211/sta_info.h +++ b/net/mac80211/sta_info.h | |||
@@ -155,7 +155,8 @@ struct tid_ampdu_tx { | |||
155 | /** | 155 | /** |
156 | * struct tid_ampdu_rx - TID aggregation information (Rx). | 156 | * struct tid_ampdu_rx - TID aggregation information (Rx). |
157 | * | 157 | * |
158 | * @reorder_buf: buffer to reorder incoming aggregated MPDUs | 158 | * @reorder_buf: buffer to reorder incoming aggregated MPDUs. An MPDU may be an |
159 | * A-MSDU with individually reported subframes. | ||
159 | * @reorder_time: jiffies when skb was added | 160 | * @reorder_time: jiffies when skb was added |
160 | * @session_timer: check if peer keeps Tx-ing on the TID (by timeout value) | 161 | * @session_timer: check if peer keeps Tx-ing on the TID (by timeout value) |
161 | * @reorder_timer: releases expired frames from the reorder buffer. | 162 | * @reorder_timer: releases expired frames from the reorder buffer. |
@@ -180,7 +181,7 @@ struct tid_ampdu_tx { | |||
180 | struct tid_ampdu_rx { | 181 | struct tid_ampdu_rx { |
181 | struct rcu_head rcu_head; | 182 | struct rcu_head rcu_head; |
182 | spinlock_t reorder_lock; | 183 | spinlock_t reorder_lock; |
183 | struct sk_buff **reorder_buf; | 184 | struct sk_buff_head *reorder_buf; |
184 | unsigned long *reorder_time; | 185 | unsigned long *reorder_time; |
185 | struct timer_list session_timer; | 186 | struct timer_list session_timer; |
186 | struct timer_list reorder_timer; | 187 | struct timer_list reorder_timer; |