iwlwifi: mvm: add reorder timeout per frame

Add a timer in order to release expired frames from the
reorder buffer.
This is needed since some APs do not retransmit frames
to fill in the reorder holes and in TCP it results with
a complete stall of traffic.

This has a few side effects on the general design:

The nssn may not reflect the the head of the reorder buffer.
This situation is valid, and packets with SN lower than the
reorder buffer head will be dropped.

Another side effect is that since the reorder timer might expire
we need to lock the reorder buffer.
This however is fine since the locking is only inside a
single reorder buffer between RX path and reorder timeout and
there is no outside contention.

Signed-off-by: Sara Sharon <sara.sharon@intel.com>
Signed-off-by: Luca Coelho <luciano.coelho@intel.com>
diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/rxmq.c b/drivers/net/wireless/intel/iwlwifi/mvm/rxmq.c
index 4f320dc..ed187af 100644
--- a/drivers/net/wireless/intel/iwlwifi/mvm/rxmq.c
+++ b/drivers/net/wireless/intel/iwlwifi/mvm/rxmq.c
@@ -395,6 +395,8 @@
 	return ret;
 }
 
+#define RX_REORDER_BUF_TIMEOUT_MQ (HZ / 10)
+
 static void iwl_mvm_release_frames(struct iwl_mvm *mvm,
 				   struct ieee80211_sta *sta,
 				   struct napi_struct *napi,
@@ -403,6 +405,12 @@
 {
 	u16 ssn = reorder_buf->head_sn;
 
+	lockdep_assert_held(&reorder_buf->lock);
+
+	/* ignore nssn smaller than head sn - this can happen due to timeout */
+	if (ieee80211_sn_less(nssn, ssn))
+		return;
+
 	while (ieee80211_sn_less(ssn, nssn)) {
 		int index = ssn % reorder_buf->buf_size;
 		struct sk_buff_head *skb_list = &reorder_buf->entries[index];
@@ -422,6 +430,66 @@
 		}
 	}
 	reorder_buf->head_sn = nssn;
+
+	if (reorder_buf->num_stored && !reorder_buf->removed) {
+		u16 index = reorder_buf->head_sn % reorder_buf->buf_size;
+
+		while (!skb_peek_tail(&reorder_buf->entries[index]))
+			index = (index + 1) % reorder_buf->buf_size;
+		/* modify timer to match next frame's expiration time */
+		mod_timer(&reorder_buf->reorder_timer,
+			  reorder_buf->reorder_time[index] + 1 +
+			  RX_REORDER_BUF_TIMEOUT_MQ);
+	} else {
+		del_timer(&reorder_buf->reorder_timer);
+	}
+}
+
+void iwl_mvm_reorder_timer_expired(unsigned long data)
+{
+	struct iwl_mvm_reorder_buffer *buf = (void *)data;
+	int i;
+	u16 sn = 0, index = 0;
+	bool expired = false;
+
+	spin_lock_bh(&buf->lock);
+
+	if (!buf->num_stored || buf->removed) {
+		spin_unlock_bh(&buf->lock);
+		return;
+	}
+
+	for (i = 0; i < buf->buf_size ; i++) {
+		index = (buf->head_sn + i) % buf->buf_size;
+
+		if (!skb_peek_tail(&buf->entries[index]))
+			continue;
+		if (!time_after(jiffies, buf->reorder_time[index] +
+				RX_REORDER_BUF_TIMEOUT_MQ))
+			break;
+		expired = true;
+		sn = ieee80211_sn_add(buf->head_sn, i + 1);
+	}
+
+	if (expired) {
+		struct ieee80211_sta *sta;
+
+		rcu_read_lock();
+		sta = rcu_dereference(buf->mvm->fw_id_to_mac_id[buf->sta_id]);
+		/* SN is set to the last expired frame + 1 */
+		iwl_mvm_release_frames(buf->mvm, sta, NULL, buf, sn);
+		rcu_read_unlock();
+	} else if (buf->num_stored) {
+		/*
+		 * If no frame expired and there are stored frames, index is now
+		 * pointing to the first unexpired frame - modify timer
+		 * accordingly to this frame.
+		 */
+		mod_timer(&buf->reorder_timer,
+			  buf->reorder_time[index] +
+			  1 + RX_REORDER_BUF_TIMEOUT_MQ);
+	}
+	spin_unlock_bh(&buf->lock);
 }
 
 static void iwl_mvm_del_ba(struct iwl_mvm *mvm, int queue,
@@ -448,9 +516,12 @@
 	reorder_buf = &ba_data->reorder_buf[queue];
 
 	/* release all frames that are in the reorder buffer to the stack */
+	spin_lock_bh(&reorder_buf->lock);
 	iwl_mvm_release_frames(mvm, sta, NULL, reorder_buf,
 			       ieee80211_sn_add(reorder_buf->head_sn,
 						reorder_buf->buf_size));
+	spin_unlock_bh(&reorder_buf->lock);
+	del_timer_sync(&reorder_buf->reorder_timer);
 
 out:
 	rcu_read_unlock();
@@ -545,6 +616,8 @@
 
 	buffer = &baid_data->reorder_buf[queue];
 
+	spin_lock_bh(&buffer->lock);
+
 	/*
 	 * If there was a significant jump in the nssn - adjust.
 	 * If the SN is smaller than the NSSN it might need to first go into
@@ -564,8 +637,10 @@
 
 	/* release immediately if allowed by nssn and no stored frames */
 	if (!buffer->num_stored && ieee80211_sn_less(sn, nssn)) {
-		buffer->head_sn = nssn;
+		if (ieee80211_sn_less(buffer->head_sn, nssn))
+			buffer->head_sn = nssn;
 		/* No need to update AMSDU last SN - we are moving the head */
+		spin_unlock_bh(&buffer->lock);
 		return false;
 	}
 
@@ -589,16 +664,20 @@
 	/* put in reorder buffer */
 	__skb_queue_tail(&buffer->entries[index], skb);
 	buffer->num_stored++;
+	buffer->reorder_time[index] = jiffies;
+
 	if (amsdu) {
 		buffer->last_amsdu = sn;
 		buffer->last_sub_index = sub_frame_idx;
 	}
 
 	iwl_mvm_release_frames(mvm, sta, napi, buffer, nssn);
+	spin_unlock_bh(&buffer->lock);
 	return true;
 
 drop:
 	kfree_skb(skb);
+	spin_unlock_bh(&buffer->lock);
 	return true;
 }