diff options
Diffstat (limited to 'drivers/net/wireless/rt2x00/rt2800pci.c')
| -rw-r--r-- | drivers/net/wireless/rt2x00/rt2800pci.c | 116 | 
1 files changed, 113 insertions, 3 deletions
diff --git a/drivers/net/wireless/rt2x00/rt2800pci.c b/drivers/net/wireless/rt2x00/rt2800pci.c index ba5a05625aa..565a80d0e56 100644 --- a/drivers/net/wireless/rt2x00/rt2800pci.c +++ b/drivers/net/wireless/rt2x00/rt2800pci.c @@ -730,6 +730,11 @@ static void rt2800pci_fill_rxdone(struct queue_entry *entry,  	 * Process the RXWI structure that is at the start of the buffer.  	 */  	rt2800_process_rxwi(entry, rxdesc); + +	/* +	 * Remove RXWI descriptor from start of buffer. +	 */ +	skb_pull(entry->skb, RXWI_DESC_SIZE);  }  /* @@ -743,10 +748,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; @@ -784,8 +869,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;  |