rt2800usb: read TX_STA_FIFO asynchronously

Trying to fix the "TX status report missed" warnings
by reading the TX_STA_FIFO entries as quickly as possible.
The TX_STA_FIFO is too small in hardware, thus reading
it only from the workqueue is too slow and entries get lost.

Start an asynchronous read of the TX_STA_FIFO directly from
the TX URB completion callback (atomic context, thus it cannot
use the blocking rt2800_register_read()). If the async
read returns a valid FIFO entry, it is pushed into a larger
FIFO inside struct rt2x00_dev, until rt2800_txdone() picks
it up.

A .tx_dma_done callback is added to struct rt2x00lib_ops
to trigger the async read from the URB completion callback.

Signed-off-by: Johannes Stezenbach <js@sig21.net>
Signed-off-by: Ivo van Doorn <IvDoorn@gmail.com>
Signed-off-by: John W. Linville <linville@tuxdriver.com>
diff --git a/drivers/net/wireless/rt2x00/rt2800usb.c b/drivers/net/wireless/rt2x00/rt2800usb.c
index f3ce5e8..862430e 100644
--- a/drivers/net/wireless/rt2x00/rt2800usb.c
+++ b/drivers/net/wireless/rt2x00/rt2800usb.c
@@ -98,6 +98,35 @@
 	}
 }
 
+static void rt2800usb_tx_sta_fifo_read_completed(struct rt2x00_dev *rt2x00dev,
+						 int urb_status, u32 tx_status)
+{
+	if (urb_status) {
+		WARNING(rt2x00dev, "rt2x00usb_register_read_async failed: %d\n", urb_status);
+		return;
+	}
+
+	/* try to read all TX_STA_FIFO entries before scheduling txdone_work */
+	if (rt2x00_get_field32(tx_status, TX_STA_FIFO_VALID)) {
+		if (!kfifo_put(&rt2x00dev->txstatus_fifo, &tx_status)) {
+			WARNING(rt2x00dev, "TX status FIFO overrun, "
+				"drop tx status report.\n");
+			queue_work(rt2x00dev->workqueue, &rt2x00dev->txdone_work);
+		} else
+			rt2x00usb_register_read_async(rt2x00dev, TX_STA_FIFO,
+						      rt2800usb_tx_sta_fifo_read_completed);
+	} else if (!kfifo_is_empty(&rt2x00dev->txstatus_fifo))
+		queue_work(rt2x00dev->workqueue, &rt2x00dev->txdone_work);
+}
+
+static void rt2800usb_tx_dma_done(struct queue_entry *entry)
+{
+	struct rt2x00_dev *rt2x00dev = entry->queue->rt2x00dev;
+
+	rt2x00usb_register_read_async(rt2x00dev, TX_STA_FIFO,
+				      rt2800usb_tx_sta_fifo_read_completed);
+}
+
 /*
  * Firmware functions
  */
@@ -565,6 +594,7 @@
 		__set_bit(CAPABILITY_HW_CRYPTO, &rt2x00dev->cap_flags);
 	__set_bit(CAPABILITY_LINK_TUNING, &rt2x00dev->cap_flags);
 	__set_bit(REQUIRE_HT_TX_DESC, &rt2x00dev->cap_flags);
+	__set_bit(REQUIRE_TXSTATUS_FIFO, &rt2x00dev->cap_flags);
 
 	/*
 	 * Set the rssi offset.
@@ -635,6 +665,7 @@
 	.kick_queue		= rt2x00usb_kick_queue,
 	.stop_queue		= rt2800usb_stop_queue,
 	.flush_queue		= rt2x00usb_flush_queue,
+	.tx_dma_done		= rt2800usb_tx_dma_done,
 	.write_tx_desc		= rt2800usb_write_tx_desc,
 	.write_tx_data		= rt2800usb_write_tx_data,
 	.write_beacon		= rt2800_write_beacon,