summaryrefslogtreecommitdiffstats
path: root/drivers/net/wireless/rt2x00/rt2800pci.c
diff options
context:
space:
mode:
authorHelmut Schaa2013-03-15 09:57:57 +0100
committerJohn W. Linville2013-03-18 21:38:29 +0100
commit8857d6dc77e4e3afeee2f33c49597010130ed858 (patch)
tree578ceaaf88ea7ad9e439f48564f22a5eec16f7f8 /drivers/net/wireless/rt2x00/rt2800pci.c
parentrt2x00: Revert "rt2x00: remove unused argument" (diff)
downloadkernel-qcow2-linux-8857d6dc77e4e3afeee2f33c49597010130ed858.tar.gz
kernel-qcow2-linux-8857d6dc77e4e3afeee2f33c49597010130ed858.tar.xz
kernel-qcow2-linux-8857d6dc77e4e3afeee2f33c49597010130ed858.zip
rt2x00: Fix tx status reporting for reordered frames in rt2800pci
rt2800 hardware sometimes reorders tx frames when transmitting to multiple BA enabled STAs concurrently. For example a tx queue [ STA1 | STA2 | STA1 | STA2 ] can result in the tx status reports [ STA1 | STA1 | STA2 | STA2 ] when the hw decides to put the frames for STA1 in one AMPDU. To mitigate this effect associate the currently processed tx status to the first frame in the tx queue with a matching wcid. This patch fixes several problems related to incorrect tx status reporting. Furthermore the tx rate selection is much more stable when communicating with multiple STAs. Signed-off-by: Helmut Schaa <helmut.schaa@googlemail.com> Signed-off-by: John W. Linville <linville@tuxdriver.com>
Diffstat (limited to 'drivers/net/wireless/rt2x00/rt2800pci.c')
-rw-r--r--drivers/net/wireless/rt2x00/rt2800pci.c111
1 files changed, 108 insertions, 3 deletions
diff --git a/drivers/net/wireless/rt2x00/rt2800pci.c b/drivers/net/wireless/rt2x00/rt2800pci.c
index ded73da4de0b..80cf8d745c6f 100644
--- a/drivers/net/wireless/rt2x00/rt2800pci.c
+++ b/drivers/net/wireless/rt2x00/rt2800pci.c
@@ -742,10 +742,90 @@ static void rt2800pci_wakeup(struct rt2x00_dev *rt2x00dev)
rt2800_config(rt2x00dev, &libconf, IEEE80211_CONF_CHANGE_PS);
}
+static bool rt2800pci_txdone_entry_check(struct queue_entry *entry, u32 status)
+{
+ __le32 *txwi;
+ u32 word;
+ int wcid, tx_wcid;
+
+ wcid = rt2x00_get_field32(status, TX_STA_FIFO_WCID);
+
+ txwi = rt2800_drv_get_txwi(entry);
+ rt2x00_desc_read(txwi, 1, &word);
+ tx_wcid = rt2x00_get_field32(word, TXWI_W1_WIRELESS_CLI_ID);
+
+ return (tx_wcid == wcid);
+}
+
+static bool rt2800pci_txdone_find_entry(struct queue_entry *entry, void *data)
+{
+ u32 status = *(u32 *)data;
+
+ /*
+ * rt2800pci hardware might reorder frames when exchanging traffic
+ * with multiple BA enabled STAs.
+ *
+ * For example, a tx queue
+ * [ STA1 | STA2 | STA1 | STA2 ]
+ * can result in tx status reports
+ * [ STA1 | STA1 | STA2 | STA2 ]
+ * when the hw decides to aggregate the frames for STA1 into one AMPDU.
+ *
+ * To mitigate this effect, associate the tx status to the first frame
+ * in the tx queue with a matching wcid.
+ */
+ if (rt2800pci_txdone_entry_check(entry, status) &&
+ !test_bit(ENTRY_DATA_STATUS_SET, &entry->flags)) {
+ /*
+ * Got a matching frame, associate the tx status with
+ * the frame
+ */
+ entry->status = status;
+ set_bit(ENTRY_DATA_STATUS_SET, &entry->flags);
+ return true;
+ }
+
+ /* Check the next frame */
+ return false;
+}
+
+static bool rt2800pci_txdone_match_first(struct queue_entry *entry, void *data)
+{
+ u32 status = *(u32 *)data;
+
+ /*
+ * Find the first frame without tx status and assign this status to it
+ * regardless if it matches or not.
+ */
+ if (!test_bit(ENTRY_DATA_STATUS_SET, &entry->flags)) {
+ /*
+ * Got a matching frame, associate the tx status with
+ * the frame
+ */
+ entry->status = status;
+ set_bit(ENTRY_DATA_STATUS_SET, &entry->flags);
+ return true;
+ }
+
+ /* Check the next frame */
+ return false;
+}
+static bool rt2800pci_txdone_release_entries(struct queue_entry *entry,
+ void *data)
+{
+ if (test_bit(ENTRY_DATA_STATUS_SET, &entry->flags)) {
+ rt2800_txdone_entry(entry, entry->status,
+ rt2800pci_get_txwi(entry));
+ return false;
+ }
+
+ /* No more frames to release */
+ return true;
+}
+
static bool rt2800pci_txdone(struct rt2x00_dev *rt2x00dev)
{
struct data_queue *queue;
- struct queue_entry *entry;
u32 status;
u8 qid;
int max_tx_done = 16;
@@ -783,8 +863,33 @@ static bool rt2800pci_txdone(struct rt2x00_dev *rt2x00dev)
break;
}
- entry = rt2x00queue_get_entry(queue, Q_INDEX_DONE);
- rt2800_txdone_entry(entry, status, rt2800pci_get_txwi(entry));
+ /*
+ * Let's associate this tx status with the first
+ * matching frame.
+ */
+ if (!rt2x00queue_for_each_entry(queue, Q_INDEX_DONE,
+ Q_INDEX, &status,
+ rt2800pci_txdone_find_entry)) {
+ /*
+ * We cannot match the tx status to any frame, so just
+ * use the first one.
+ */
+ if (!rt2x00queue_for_each_entry(queue, Q_INDEX_DONE,
+ Q_INDEX, &status,
+ rt2800pci_txdone_match_first)) {
+ WARNING(rt2x00dev, "No frame found for TX "
+ "status on queue %u, dropping\n",
+ qid);
+ break;
+ }
+ }
+
+ /*
+ * Release all frames with a valid tx status.
+ */
+ rt2x00queue_for_each_entry(queue, Q_INDEX_DONE,
+ Q_INDEX, NULL,
+ rt2800pci_txdone_release_entries);
if (--max_tx_done == 0)
break;