summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--drivers/net/wireless/rt2x00/rt2800pci.c150
-rw-r--r--drivers/net/wireless/rt2x00/rt2x00.h17
-rw-r--r--drivers/net/wireless/rt2x00/rt2x00dev.c34
3 files changed, 188 insertions, 13 deletions
diff --git a/drivers/net/wireless/rt2x00/rt2800pci.c b/drivers/net/wireless/rt2x00/rt2800pci.c
index 005ee153e0cc..3806454b827b 100644
--- a/drivers/net/wireless/rt2x00/rt2800pci.c
+++ b/drivers/net/wireless/rt2x00/rt2800pci.c
@@ -660,6 +660,63 @@ static void rt2800pci_wakeup(struct rt2x00_dev *rt2x00dev)
rt2800_config(rt2x00dev, &libconf, IEEE80211_CONF_CHANGE_PS);
}
+static void rt2800pci_txdone(struct rt2x00_dev *rt2x00dev)
+{
+ struct data_queue *queue;
+ struct queue_entry *entry;
+ u32 status;
+ u8 qid;
+
+ while (!kfifo_is_empty(&rt2x00dev->txstatus_fifo)) {
+ /* Now remove the tx status from the FIFO */
+ if (kfifo_out(&rt2x00dev->txstatus_fifo, &status,
+ sizeof(status)) != sizeof(status)) {
+ WARN_ON(1);
+ break;
+ }
+
+ qid = rt2x00_get_field32(status, TX_STA_FIFO_PID_TYPE) - 1;
+ if (qid >= QID_RX) {
+ /*
+ * Unknown queue, this shouldn't happen. Just drop
+ * this tx status.
+ */
+ WARNING(rt2x00dev, "Got TX status report with "
+ "unexpected pid %u, dropping", qid);
+ break;
+ }
+
+ queue = rt2x00queue_get_queue(rt2x00dev, qid);
+ if (unlikely(queue == NULL)) {
+ /*
+ * The queue is NULL, this shouldn't happen. Stop
+ * processing here and drop the tx status
+ */
+ WARNING(rt2x00dev, "Got TX status for an unavailable "
+ "queue %u, dropping", qid);
+ break;
+ }
+
+ if (rt2x00queue_empty(queue)) {
+ /*
+ * The queue is empty. Stop processing here
+ * and drop the tx status.
+ */
+ WARNING(rt2x00dev, "Got TX status for an empty "
+ "queue %u, dropping", qid);
+ break;
+ }
+
+ entry = rt2x00queue_get_entry(queue, Q_INDEX_DONE);
+ rt2800_txdone_entry(entry, status);
+ }
+}
+
+static void rt2800pci_txstatus_tasklet(unsigned long data)
+{
+ rt2800pci_txdone((struct rt2x00_dev *)data);
+}
+
static irqreturn_t rt2800pci_interrupt_thread(int irq, void *dev_instance)
{
struct rt2x00_dev *rt2x00dev = dev_instance;
@@ -684,13 +741,7 @@ static irqreturn_t rt2800pci_interrupt_thread(int irq, void *dev_instance)
rt2x00pci_rxdone(rt2x00dev);
/*
- * 4 - Tx done interrupt.
- */
- if (rt2x00_get_field32(reg, INT_SOURCE_CSR_TX_FIFO_STATUS))
- rt2800_txdone(rt2x00dev);
-
- /*
- * 5 - Auto wakeup interrupt.
+ * 4 - Auto wakeup interrupt.
*/
if (rt2x00_get_field32(reg, INT_SOURCE_CSR_AUTO_WAKEUP))
rt2800pci_wakeup(rt2x00dev);
@@ -702,10 +753,58 @@ static irqreturn_t rt2800pci_interrupt_thread(int irq, void *dev_instance)
return IRQ_HANDLED;
}
+static void rt2800pci_txstatus_interrupt(struct rt2x00_dev *rt2x00dev)
+{
+ u32 status;
+ int i;
+
+ /*
+ * The TX_FIFO_STATUS interrupt needs special care. We should
+ * read TX_STA_FIFO but we should do it immediately as otherwise
+ * the register can overflow and we would lose status reports.
+ *
+ * Hence, read the TX_STA_FIFO register and copy all tx status
+ * reports into a kernel FIFO which is handled in the txstatus
+ * tasklet. We use a tasklet to process the tx status reports
+ * because we can schedule the tasklet multiple times (when the
+ * interrupt fires again during tx status processing).
+ *
+ * Furthermore we don't disable the TX_FIFO_STATUS
+ * interrupt here but leave it enabled so that the TX_STA_FIFO
+ * can also be read while the interrupt thread gets executed.
+ *
+ * Since we have only one producer and one consumer we don't
+ * need to lock the kfifo.
+ */
+ for (i = 0; i < TX_ENTRIES; i++) {
+ rt2800_register_read(rt2x00dev, TX_STA_FIFO, &status);
+
+ if (!rt2x00_get_field32(status, TX_STA_FIFO_VALID))
+ break;
+
+ if (kfifo_is_full(&rt2x00dev->txstatus_fifo)) {
+ WARNING(rt2x00dev, "TX status FIFO overrun,"
+ " drop tx status report.\n");
+ break;
+ }
+
+ if (kfifo_in(&rt2x00dev->txstatus_fifo, &status,
+ sizeof(status)) != sizeof(status)) {
+ WARNING(rt2x00dev, "TX status FIFO overrun,"
+ "drop tx status report.\n");
+ break;
+ }
+ }
+
+ /* Schedule the tasklet for processing the tx status. */
+ tasklet_schedule(&rt2x00dev->txstatus_tasklet);
+}
+
static irqreturn_t rt2800pci_interrupt(int irq, void *dev_instance)
{
struct rt2x00_dev *rt2x00dev = dev_instance;
u32 reg;
+ irqreturn_t ret = IRQ_HANDLED;
/* Read status and ACK all interrupts */
rt2800_register_read(rt2x00dev, INT_SOURCE_CSR, &reg);
@@ -717,15 +816,38 @@ static irqreturn_t rt2800pci_interrupt(int irq, void *dev_instance)
if (!test_bit(DEVICE_STATE_ENABLED_RADIO, &rt2x00dev->flags))
return IRQ_HANDLED;
- /* Store irqvalue for use in the interrupt thread. */
- rt2x00dev->irqvalue[0] = reg;
+ if (rt2x00_get_field32(reg, INT_SOURCE_CSR_TX_FIFO_STATUS))
+ rt2800pci_txstatus_interrupt(rt2x00dev);
+
+ if (rt2x00_get_field32(reg, INT_SOURCE_CSR_PRE_TBTT) ||
+ rt2x00_get_field32(reg, INT_SOURCE_CSR_TBTT) ||
+ rt2x00_get_field32(reg, INT_SOURCE_CSR_RX_DONE) ||
+ rt2x00_get_field32(reg, INT_SOURCE_CSR_AUTO_WAKEUP)) {
+ /*
+ * All other interrupts are handled in the interrupt thread.
+ * Store irqvalue for use in the interrupt thread.
+ */
+ rt2x00dev->irqvalue[0] = reg;
- /* Disable interrupts, will be enabled again in the interrupt thread. */
- rt2x00dev->ops->lib->set_device_state(rt2x00dev,
- STATE_RADIO_IRQ_OFF_ISR);
+ /*
+ * Disable interrupts, will be enabled again in the
+ * interrupt thread.
+ */
+ rt2x00dev->ops->lib->set_device_state(rt2x00dev,
+ STATE_RADIO_IRQ_OFF_ISR);
+ /*
+ * Leave the TX_FIFO_STATUS interrupt enabled to not lose any
+ * tx status reports.
+ */
+ rt2800_register_read(rt2x00dev, INT_MASK_CSR, &reg);
+ rt2x00_set_field32(&reg, INT_MASK_CSR_TX_FIFO_STATUS, 1);
+ rt2800_register_write(rt2x00dev, INT_MASK_CSR, reg);
- return IRQ_WAKE_THREAD;
+ ret = IRQ_WAKE_THREAD;
+ }
+
+ return ret;
}
/*
@@ -788,6 +910,7 @@ static int rt2800pci_probe_hw(struct rt2x00_dev *rt2x00dev)
__set_bit(DRIVER_REQUIRE_FIRMWARE, &rt2x00dev->flags);
__set_bit(DRIVER_REQUIRE_DMA, &rt2x00dev->flags);
__set_bit(DRIVER_REQUIRE_L2PAD, &rt2x00dev->flags);
+ __set_bit(DRIVER_REQUIRE_TXSTATUS_FIFO, &rt2x00dev->flags);
if (!modparam_nohwcrypt)
__set_bit(CONFIG_SUPPORT_HW_CRYPTO, &rt2x00dev->flags);
__set_bit(DRIVER_SUPPORT_LINK_TUNING, &rt2x00dev->flags);
@@ -837,6 +960,7 @@ static const struct rt2800_ops rt2800pci_rt2800_ops = {
static const struct rt2x00lib_ops rt2800pci_rt2x00_ops = {
.irq_handler = rt2800pci_interrupt,
.irq_handler_thread = rt2800pci_interrupt_thread,
+ .txstatus_tasklet = rt2800pci_txstatus_tasklet,
.probe_hw = rt2800pci_probe_hw,
.get_firmware_name = rt2800pci_get_firmware_name,
.check_firmware = rt2800_check_firmware,
diff --git a/drivers/net/wireless/rt2x00/rt2x00.h b/drivers/net/wireless/rt2x00/rt2x00.h
index 7832a5996a8c..bab10adae537 100644
--- a/drivers/net/wireless/rt2x00/rt2x00.h
+++ b/drivers/net/wireless/rt2x00/rt2x00.h
@@ -36,6 +36,7 @@
#include <linux/mutex.h>
#include <linux/etherdevice.h>
#include <linux/input-polldev.h>
+#include <linux/kfifo.h>
#include <net/mac80211.h>
@@ -522,6 +523,11 @@ struct rt2x00lib_ops {
irq_handler_t irq_handler_thread;
/*
+ * TX status tasklet handler.
+ */
+ void (*txstatus_tasklet) (unsigned long data);
+
+ /*
* Device init handlers.
*/
int (*probe_hw) (struct rt2x00_dev *rt2x00dev);
@@ -651,6 +657,7 @@ enum rt2x00_flags {
DRIVER_REQUIRE_DMA,
DRIVER_REQUIRE_COPY_IV,
DRIVER_REQUIRE_L2PAD,
+ DRIVER_REQUIRE_TXSTATUS_FIFO,
/*
* Driver features
@@ -884,6 +891,16 @@ struct rt2x00_dev {
* and interrupt thread routine.
*/
u32 irqvalue[2];
+
+ /*
+ * FIFO for storing tx status reports between isr and tasklet.
+ */
+ struct kfifo txstatus_fifo;
+
+ /*
+ * Tasklet for processing tx status reports (rt2800pci).
+ */
+ struct tasklet_struct txstatus_tasklet;
};
/*
diff --git a/drivers/net/wireless/rt2x00/rt2x00dev.c b/drivers/net/wireless/rt2x00/rt2x00dev.c
index 053fdd3bd720..b03e6e41dc1e 100644
--- a/drivers/net/wireless/rt2x00/rt2x00dev.c
+++ b/drivers/net/wireless/rt2x00/rt2x00dev.c
@@ -813,6 +813,30 @@ static int rt2x00lib_probe_hw(struct rt2x00_dev *rt2x00dev)
rt2x00dev->hw->extra_tx_headroom += RT2X00_ALIGN_SIZE;
/*
+ * Allocate tx status FIFO for driver use.
+ */
+ if (test_bit(DRIVER_REQUIRE_TXSTATUS_FIFO, &rt2x00dev->flags) &&
+ rt2x00dev->ops->lib->txstatus_tasklet) {
+ /*
+ * Allocate txstatus fifo and tasklet, we use a size of 512
+ * for the kfifo which is big enough to store 512/4=128 tx
+ * status reports. In the worst case (tx status for all tx
+ * queues gets reported before we've got a chance to handle
+ * them) 24*4=384 tx status reports need to be cached.
+ */
+ status = kfifo_alloc(&rt2x00dev->txstatus_fifo, 512,
+ GFP_KERNEL);
+ if (status)
+ return status;
+
+ /* tasklet for processing the tx status reports. */
+ tasklet_init(&rt2x00dev->txstatus_tasklet,
+ rt2x00dev->ops->lib->txstatus_tasklet,
+ (unsigned long)rt2x00dev);
+
+ }
+
+ /*
* Register HW.
*/
status = ieee80211_register_hw(rt2x00dev->hw);
@@ -1028,6 +1052,16 @@ void rt2x00lib_remove_dev(struct rt2x00_dev *rt2x00dev)
cancel_work_sync(&rt2x00dev->txdone_work);
/*
+ * Free the tx status fifo.
+ */
+ kfifo_free(&rt2x00dev->txstatus_fifo);
+
+ /*
+ * Kill the tx status tasklet.
+ */
+ tasklet_kill(&rt2x00dev->txstatus_tasklet);
+
+ /*
* Uninitialize device.
*/
rt2x00lib_uninitialize(rt2x00dev);