diff options
Diffstat (limited to 'drivers/net/wireless/rt2x00/rt2800pci.c')
-rw-r--r-- | drivers/net/wireless/rt2x00/rt2800pci.c | 165 |
1 files changed, 145 insertions, 20 deletions
diff --git a/drivers/net/wireless/rt2x00/rt2800pci.c b/drivers/net/wireless/rt2x00/rt2800pci.c index 005ee153e0c..85a134cd62b 100644 --- a/drivers/net/wireless/rt2x00/rt2800pci.c +++ b/drivers/net/wireless/rt2x00/rt2800pci.c @@ -241,6 +241,7 @@ static void rt2800pci_clear_entry(struct queue_entry *entry) { struct queue_entry_priv_pci *entry_priv = entry->priv_data; struct skb_frame_desc *skbdesc = get_skb_frame_desc(entry->skb); + struct rt2x00_dev *rt2x00dev = entry->queue->rt2x00dev; u32 word; if (entry->queue->qid == QID_RX) { @@ -251,6 +252,13 @@ static void rt2800pci_clear_entry(struct queue_entry *entry) rt2x00_desc_read(entry_priv->desc, 1, &word); rt2x00_set_field32(&word, RXD_W1_DMA_DONE, 0); rt2x00_desc_write(entry_priv->desc, 1, word); + + /* + * Set RX IDX in register to inform hardware that we have + * handled this entry and it is available for reuse again. + */ + rt2800_register_write(rt2x00dev, RX_CRX_IDX, + entry->entry_idx); } else { rt2x00_desc_read(entry_priv->desc, 1, &word); rt2x00_set_field32(&word, TXD_W1_DMA_DONE, 1); @@ -599,7 +607,6 @@ static void rt2800pci_kill_tx_queue(struct data_queue *queue) static void rt2800pci_fill_rxdone(struct queue_entry *entry, struct rxdone_entry_desc *rxdesc) { - struct rt2x00_dev *rt2x00dev = entry->queue->rt2x00dev; struct queue_entry_priv_pci *entry_priv = entry->priv_data; __le32 *rxd = entry_priv->desc; u32 word; @@ -641,12 +648,6 @@ 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); - - /* - * Set RX IDX in register to inform hardware that we have handled - * this entry and it is available for reuse again. - */ - rt2800_register_write(rt2x00dev, RX_CRX_IDX, entry->entry_idx); } /* @@ -660,6 +661,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 +742,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 +754,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 +817,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); + + /* + * 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 IRQ_WAKE_THREAD; + return ret; } /* @@ -788,6 +911,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 +961,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, |