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, ®); @@ -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); - /* Disable interrupts, will be enabled again in the interrupt thread. */ - rt2x00dev->ops->lib->set_device_state(rt2x00dev, - STATE_RADIO_IRQ_OFF_ISR); + 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); - return IRQ_WAKE_THREAD; + /* + * Leave the TX_FIFO_STATUS interrupt enabled to not lose any + * tx status reports. + */ + rt2800_register_read(rt2x00dev, INT_MASK_CSR, ®); + rt2x00_set_field32(®, INT_MASK_CSR_TX_FIFO_STATUS, 1); + rt2800_register_write(rt2x00dev, INT_MASK_CSR, reg); + + 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 #include #include +#include #include @@ -521,6 +522,11 @@ struct rt2x00lib_ops { */ irq_handler_t irq_handler_thread; + /* + * TX status tasklet handler. + */ + void (*txstatus_tasklet) (unsigned long data); + /* * Device init handlers. */ @@ -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 @@ -812,6 +812,30 @@ static int rt2x00lib_probe_hw(struct rt2x00_dev *rt2x00dev) else if (test_bit(DRIVER_REQUIRE_DMA, &rt2x00dev->flags)) 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. */ @@ -1027,6 +1051,16 @@ void rt2x00lib_remove_dev(struct rt2x00_dev *rt2x00dev) cancel_work_sync(&rt2x00dev->rxdone_work); 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. */