Commit 2bff8ebf authored by Christian Lamparter's avatar Christian Lamparter Committed by John W. Linville

mac80211: AMPDU rx reorder timeout timer

This patch introduces a new timer, which will release
queued-up MPDUs from the reorder buffer, whenever
they've waited for more than HT_RX_REORDER_BUF_TIMEOUT
(which is at around 100 ms).

The advantage of having a dedicated timer, instead of
relying on a constant stream of freshly arriving aMPDUs
to release the old ones, is particularly observable when
even a small fraction of MPDUs are forever lost at
low network speeds.

Previously under these circumstances frames would become
stuck in the reorder buffer and the network stack of both
HT peers throttled back, instead of revving up and
gunning the pipes.
Signed-off-by: default avatarChristian Lamparter <chunkeey@googlemail.com>
Signed-off-by: default avatarJohn W. Linville <linville@tuxdriver.com>
parent 071d9ac2
...@@ -86,6 +86,7 @@ void ___ieee80211_stop_rx_ba_session(struct sta_info *sta, u16 tid, ...@@ -86,6 +86,7 @@ void ___ieee80211_stop_rx_ba_session(struct sta_info *sta, u16 tid,
tid, 0, reason); tid, 0, reason);
del_timer_sync(&tid_rx->session_timer); del_timer_sync(&tid_rx->session_timer);
del_timer_sync(&tid_rx->reorder_timer);
call_rcu(&tid_rx->rcu_head, ieee80211_free_tid_rx); call_rcu(&tid_rx->rcu_head, ieee80211_free_tid_rx);
} }
...@@ -120,6 +121,20 @@ static void sta_rx_agg_session_timer_expired(unsigned long data) ...@@ -120,6 +121,20 @@ static void sta_rx_agg_session_timer_expired(unsigned long data)
ieee80211_queue_work(&sta->local->hw, &sta->ampdu_mlme.work); ieee80211_queue_work(&sta->local->hw, &sta->ampdu_mlme.work);
} }
static void sta_rx_agg_reorder_timer_expired(unsigned long data)
{
u8 *ptid = (u8 *)data;
u8 *timer_to_id = ptid - *ptid;
struct sta_info *sta = container_of(timer_to_id, struct sta_info,
timer_to_tid[0]);
rcu_read_lock();
spin_lock(&sta->lock);
ieee80211_release_reorder_timeout(sta, *ptid);
spin_unlock(&sta->lock);
rcu_read_unlock();
}
static void ieee80211_send_addba_resp(struct ieee80211_sub_if_data *sdata, u8 *da, u16 tid, static void ieee80211_send_addba_resp(struct ieee80211_sub_if_data *sdata, u8 *da, u16 tid,
u8 dialog_token, u16 status, u16 policy, u8 dialog_token, u16 status, u16 policy,
u16 buf_size, u16 timeout) u16 buf_size, u16 timeout)
...@@ -251,11 +266,18 @@ void ieee80211_process_addba_request(struct ieee80211_local *local, ...@@ -251,11 +266,18 @@ void ieee80211_process_addba_request(struct ieee80211_local *local,
goto end; goto end;
} }
spin_lock_init(&tid_agg_rx->reorder_lock);
/* rx timer */ /* rx timer */
tid_agg_rx->session_timer.function = sta_rx_agg_session_timer_expired; tid_agg_rx->session_timer.function = sta_rx_agg_session_timer_expired;
tid_agg_rx->session_timer.data = (unsigned long)&sta->timer_to_tid[tid]; tid_agg_rx->session_timer.data = (unsigned long)&sta->timer_to_tid[tid];
init_timer(&tid_agg_rx->session_timer); init_timer(&tid_agg_rx->session_timer);
/* rx reorder timer */
tid_agg_rx->reorder_timer.function = sta_rx_agg_reorder_timer_expired;
tid_agg_rx->reorder_timer.data = (unsigned long)&sta->timer_to_tid[tid];
init_timer(&tid_agg_rx->reorder_timer);
/* prepare reordering buffer */ /* prepare reordering buffer */
tid_agg_rx->reorder_buf = tid_agg_rx->reorder_buf =
kcalloc(buf_size, sizeof(struct sk_buff *), GFP_ATOMIC); kcalloc(buf_size, sizeof(struct sk_buff *), GFP_ATOMIC);
......
...@@ -1136,6 +1136,7 @@ void ieee80211_start_tx_ba_cb(struct ieee80211_vif *vif, u8 *ra, u16 tid); ...@@ -1136,6 +1136,7 @@ void ieee80211_start_tx_ba_cb(struct ieee80211_vif *vif, u8 *ra, u16 tid);
void ieee80211_stop_tx_ba_cb(struct ieee80211_vif *vif, u8 *ra, u8 tid); void ieee80211_stop_tx_ba_cb(struct ieee80211_vif *vif, u8 *ra, u8 tid);
void ieee80211_ba_session_work(struct work_struct *work); void ieee80211_ba_session_work(struct work_struct *work);
void ieee80211_tx_ba_session_handle_start(struct sta_info *sta, int tid); void ieee80211_tx_ba_session_handle_start(struct sta_info *sta, int tid);
void ieee80211_release_reorder_timeout(struct sta_info *sta, int tid);
/* Spectrum management */ /* Spectrum management */
void ieee80211_process_measurement_req(struct ieee80211_sub_if_data *sdata, void ieee80211_process_measurement_req(struct ieee80211_sub_if_data *sdata,
......
...@@ -572,6 +572,8 @@ static void ieee80211_release_reorder_frames(struct ieee80211_hw *hw, ...@@ -572,6 +572,8 @@ static void ieee80211_release_reorder_frames(struct ieee80211_hw *hw,
* frames that have not yet been received are assumed to be lost and the skb * frames that have not yet been received are assumed to be lost and the skb
* can be released for processing. This may also release other skb's from the * can be released for processing. This may also release other skb's from the
* reorder buffer if there are no additional gaps between the frames. * reorder buffer if there are no additional gaps between the frames.
*
* Callers must hold tid_agg_rx->reorder_lock.
*/ */
#define HT_RX_REORDER_BUF_TIMEOUT (HZ / 10) #define HT_RX_REORDER_BUF_TIMEOUT (HZ / 10)
...@@ -579,7 +581,7 @@ static void ieee80211_sta_reorder_release(struct ieee80211_hw *hw, ...@@ -579,7 +581,7 @@ static void ieee80211_sta_reorder_release(struct ieee80211_hw *hw,
struct tid_ampdu_rx *tid_agg_rx, struct tid_ampdu_rx *tid_agg_rx,
struct sk_buff_head *frames) struct sk_buff_head *frames)
{ {
int index; int index, j;
/* release the buffer until next missing frame */ /* release the buffer until next missing frame */
index = seq_sub(tid_agg_rx->head_seq_num, tid_agg_rx->ssn) % index = seq_sub(tid_agg_rx->head_seq_num, tid_agg_rx->ssn) %
...@@ -590,7 +592,6 @@ static void ieee80211_sta_reorder_release(struct ieee80211_hw *hw, ...@@ -590,7 +592,6 @@ static void ieee80211_sta_reorder_release(struct ieee80211_hw *hw,
* No buffers ready to be released, but check whether any * No buffers ready to be released, but check whether any
* frames in the reorder buffer have timed out. * frames in the reorder buffer have timed out.
*/ */
int j;
int skipped = 1; int skipped = 1;
for (j = (index + 1) % tid_agg_rx->buf_size; j != index; for (j = (index + 1) % tid_agg_rx->buf_size; j != index;
j = (j + 1) % tid_agg_rx->buf_size) { j = (j + 1) % tid_agg_rx->buf_size) {
...@@ -600,7 +601,7 @@ static void ieee80211_sta_reorder_release(struct ieee80211_hw *hw, ...@@ -600,7 +601,7 @@ static void ieee80211_sta_reorder_release(struct ieee80211_hw *hw,
} }
if (!time_after(jiffies, tid_agg_rx->reorder_time[j] + if (!time_after(jiffies, tid_agg_rx->reorder_time[j] +
HT_RX_REORDER_BUF_TIMEOUT)) HT_RX_REORDER_BUF_TIMEOUT))
break; goto set_release_timer;
#ifdef CONFIG_MAC80211_HT_DEBUG #ifdef CONFIG_MAC80211_HT_DEBUG
if (net_ratelimit()) if (net_ratelimit())
...@@ -624,6 +625,25 @@ static void ieee80211_sta_reorder_release(struct ieee80211_hw *hw, ...@@ -624,6 +625,25 @@ static void ieee80211_sta_reorder_release(struct ieee80211_hw *hw,
index = seq_sub(tid_agg_rx->head_seq_num, tid_agg_rx->ssn) % index = seq_sub(tid_agg_rx->head_seq_num, tid_agg_rx->ssn) %
tid_agg_rx->buf_size; tid_agg_rx->buf_size;
} }
if (tid_agg_rx->stored_mpdu_num) {
j = index = seq_sub(tid_agg_rx->head_seq_num,
tid_agg_rx->ssn) % tid_agg_rx->buf_size;
for (; j != (index - 1) % tid_agg_rx->buf_size;
j = (j + 1) % tid_agg_rx->buf_size) {
if (tid_agg_rx->reorder_buf[j])
break;
}
set_release_timer:
mod_timer(&tid_agg_rx->reorder_timer,
tid_agg_rx->reorder_time[j] +
HT_RX_REORDER_BUF_TIMEOUT);
} else {
del_timer(&tid_agg_rx->reorder_timer);
}
} }
/* /*
...@@ -641,14 +661,16 @@ static bool ieee80211_sta_manage_reorder_buf(struct ieee80211_hw *hw, ...@@ -641,14 +661,16 @@ static bool ieee80211_sta_manage_reorder_buf(struct ieee80211_hw *hw,
u16 mpdu_seq_num = (sc & IEEE80211_SCTL_SEQ) >> 4; u16 mpdu_seq_num = (sc & IEEE80211_SCTL_SEQ) >> 4;
u16 head_seq_num, buf_size; u16 head_seq_num, buf_size;
int index; int index;
bool ret = true;
buf_size = tid_agg_rx->buf_size; buf_size = tid_agg_rx->buf_size;
head_seq_num = tid_agg_rx->head_seq_num; head_seq_num = tid_agg_rx->head_seq_num;
spin_lock(&tid_agg_rx->reorder_lock);
/* frame with out of date sequence number */ /* frame with out of date sequence number */
if (seq_less(mpdu_seq_num, head_seq_num)) { if (seq_less(mpdu_seq_num, head_seq_num)) {
dev_kfree_skb(skb); dev_kfree_skb(skb);
return true; goto out;
} }
/* /*
...@@ -669,7 +691,7 @@ static bool ieee80211_sta_manage_reorder_buf(struct ieee80211_hw *hw, ...@@ -669,7 +691,7 @@ static bool ieee80211_sta_manage_reorder_buf(struct ieee80211_hw *hw,
/* check if we already stored this frame */ /* check if we already stored this frame */
if (tid_agg_rx->reorder_buf[index]) { if (tid_agg_rx->reorder_buf[index]) {
dev_kfree_skb(skb); dev_kfree_skb(skb);
return true; goto out;
} }
/* /*
...@@ -679,7 +701,8 @@ static bool ieee80211_sta_manage_reorder_buf(struct ieee80211_hw *hw, ...@@ -679,7 +701,8 @@ static bool ieee80211_sta_manage_reorder_buf(struct ieee80211_hw *hw,
if (mpdu_seq_num == tid_agg_rx->head_seq_num && if (mpdu_seq_num == tid_agg_rx->head_seq_num &&
tid_agg_rx->stored_mpdu_num == 0) { tid_agg_rx->stored_mpdu_num == 0) {
tid_agg_rx->head_seq_num = seq_inc(tid_agg_rx->head_seq_num); tid_agg_rx->head_seq_num = seq_inc(tid_agg_rx->head_seq_num);
return false; ret = false;
goto out;
} }
/* put the frame in the reordering buffer */ /* put the frame in the reordering buffer */
...@@ -688,7 +711,9 @@ static bool ieee80211_sta_manage_reorder_buf(struct ieee80211_hw *hw, ...@@ -688,7 +711,9 @@ static bool ieee80211_sta_manage_reorder_buf(struct ieee80211_hw *hw,
tid_agg_rx->stored_mpdu_num++; tid_agg_rx->stored_mpdu_num++;
ieee80211_sta_reorder_release(hw, tid_agg_rx, frames); ieee80211_sta_reorder_release(hw, tid_agg_rx, frames);
return true; out:
spin_unlock(&tid_agg_rx->reorder_lock);
return ret;
} }
/* /*
...@@ -2387,6 +2412,37 @@ static void ieee80211_invoke_rx_handlers(struct ieee80211_sub_if_data *sdata, ...@@ -2387,6 +2412,37 @@ static void ieee80211_invoke_rx_handlers(struct ieee80211_sub_if_data *sdata,
#undef CALL_RXH #undef CALL_RXH
} }
/*
* This function makes calls into the RX path. Therefore the
* caller must hold the sta_info->lock and everything has to
* be under rcu_read_lock protection as well.
*/
void ieee80211_release_reorder_timeout(struct sta_info *sta, int tid)
{
struct sk_buff_head frames;
struct ieee80211_rx_data rx = { };
__skb_queue_head_init(&frames);
/* construct rx struct */
rx.sta = sta;
rx.sdata = sta->sdata;
rx.local = sta->local;
rx.queue = tid;
rx.flags |= IEEE80211_RX_RA_MATCH;
if (unlikely(test_bit(SCAN_HW_SCANNING, &sta->local->scanning) ||
test_bit(SCAN_OFF_CHANNEL, &sta->local->scanning)))
rx.flags |= IEEE80211_RX_IN_SCAN;
spin_lock(&sta->ampdu_mlme.tid_rx[tid]->reorder_lock);
ieee80211_sta_reorder_release(&sta->local->hw,
sta->ampdu_mlme.tid_rx[tid], &frames);
spin_unlock(&sta->ampdu_mlme.tid_rx[tid]->reorder_lock);
ieee80211_rx_handlers(&rx, &frames);
}
/* main receive path */ /* main receive path */
static int prepare_for_handlers(struct ieee80211_sub_if_data *sdata, static int prepare_for_handlers(struct ieee80211_sub_if_data *sdata,
......
...@@ -103,6 +103,7 @@ struct tid_ampdu_tx { ...@@ -103,6 +103,7 @@ struct tid_ampdu_tx {
* @reorder_buf: buffer to reorder incoming aggregated MPDUs * @reorder_buf: buffer to reorder incoming aggregated MPDUs
* @reorder_time: jiffies when skb was added * @reorder_time: jiffies when skb was added
* @session_timer: check if peer keeps Tx-ing on the TID (by timeout value) * @session_timer: check if peer keeps Tx-ing on the TID (by timeout value)
* @reorder_timer: releases expired frames from the reorder buffer.
* @head_seq_num: head sequence number in reordering buffer. * @head_seq_num: head sequence number in reordering buffer.
* @stored_mpdu_num: number of MPDUs in reordering buffer * @stored_mpdu_num: number of MPDUs in reordering buffer
* @ssn: Starting Sequence Number expected to be aggregated. * @ssn: Starting Sequence Number expected to be aggregated.
...@@ -110,20 +111,25 @@ struct tid_ampdu_tx { ...@@ -110,20 +111,25 @@ struct tid_ampdu_tx {
* @timeout: reset timer value (in TUs). * @timeout: reset timer value (in TUs).
* @dialog_token: dialog token for aggregation session * @dialog_token: dialog token for aggregation session
* @rcu_head: RCU head used for freeing this struct * @rcu_head: RCU head used for freeing this struct
* @reorder_lock: serializes access to reorder buffer, see below.
* *
* This structure is protected by RCU and the per-station * This structure is protected by RCU and the per-station
* spinlock. Assignments to the array holding it must hold * spinlock. Assignments to the array holding it must hold
* the spinlock, only the RX path can access it under RCU * the spinlock.
* lock-free. The RX path, since it is single-threaded, *
* can even modify the structure without locking since the * The @reorder_lock is used to protect the variables and
* only other modifications to it are done when the struct * arrays such as @reorder_buf, @reorder_time, @head_seq_num,
* can not yet or no longer be found by the RX path. * @stored_mpdu_num and @reorder_time from being corrupted by
* concurrent access of the RX path and the expired frame
* release timer.
*/ */
struct tid_ampdu_rx { struct tid_ampdu_rx {
struct rcu_head rcu_head; struct rcu_head rcu_head;
spinlock_t reorder_lock;
struct sk_buff **reorder_buf; struct sk_buff **reorder_buf;
unsigned long *reorder_time; unsigned long *reorder_time;
struct timer_list session_timer; struct timer_list session_timer;
struct timer_list reorder_timer;
u16 head_seq_num; u16 head_seq_num;
u16 stored_mpdu_num; u16 stored_mpdu_num;
u16 ssn; u16 ssn;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment