aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/net/wireless/rt2x00/rt2800pci.c
diff options
context:
space:
mode:
authorHelmut Schaa <helmut.schaa@googlemail.com>2010-10-02 05:27:35 -0400
committerJohn W. Linville <linville@tuxdriver.com>2010-10-05 13:35:26 -0400
commit96c3da7d7d7c37ee308ad6813947f48a71cca573 (patch)
tree525a6031138d4e4fbe8cd487f5fb3a42db50cb2f /drivers/net/wireless/rt2x00/rt2800pci.c
parent144333313b156a9e99d80e39e034a3cba00adeaf (diff)
rt2x00: rework tx status handling in rt2800pci
This patch changes the way tx status reports are handled by rt2800pci. Previously rt2800pci would sometimes lose tx status reports as the TX_STA_FIFO register is a fifo of 16 entries that can overflow in case we don't read it often/fast enough. Since interrupts are disabled in the device during the execution of the interrupt thread it happend sometimes under high network and CPU load that processing took too long and a few tx status reports were dropped by the hw. To fix this issue the TX_STA_FIFO register is read directly in the interrupt handler and stored in a kfifo which is large enough to hold all status reports of all used tx queues. To process the status reports a new tasklet txstatus_tasklet is used. Using the already used interrupt thread is not possible since we don't want to disable the TX_FIFO_STATUS interrupt while processing them and it is not possible to schedule the interrupt thread multiple times for execution. A tasklet instead can be scheduled multiple times which allows to leave the TX_FIFO_STATUS interrupt enabled while a previously scheduled tasklet is still executing. In short: All other interrupts are handled in the interrupt thread as before. Only the TX_FIFO_STATUS interrupt is partly handled in the interrupt handler and finished in the according tasklet. One drawback of this patch is that it duplicates some code from rt2800lib. However, that can be cleaned up in the future once the rt2800usb and rt2800pci tx status handling converge more. Using this patch on a Ralink RT3052 embedded board gives me a reliable wireless connection even under high CPU and network load. I've transferred several gigabytes without any queue lockups. Signed-off-by: Helmut Schaa <helmut.schaa@googlemail.com> Signed-off-by: Ivo van Doorn <IvDoorn@gmail.com> Signed-off-by: John W. Linville <linville@tuxdriver.com>
Diffstat (limited to 'drivers/net/wireless/rt2x00/rt2800pci.c')
-rw-r--r--drivers/net/wireless/rt2x00/rt2800pci.c150
1 files changed, 137 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)
660 rt2800_config(rt2x00dev, &libconf, IEEE80211_CONF_CHANGE_PS); 660 rt2800_config(rt2x00dev, &libconf, IEEE80211_CONF_CHANGE_PS);
661} 661}
662 662
663static void rt2800pci_txdone(struct rt2x00_dev *rt2x00dev)
664{
665 struct data_queue *queue;
666 struct queue_entry *entry;
667 u32 status;
668 u8 qid;
669
670 while (!kfifo_is_empty(&rt2x00dev->txstatus_fifo)) {
671 /* Now remove the tx status from the FIFO */
672 if (kfifo_out(&rt2x00dev->txstatus_fifo, &status,
673 sizeof(status)) != sizeof(status)) {
674 WARN_ON(1);
675 break;
676 }
677
678 qid = rt2x00_get_field32(status, TX_STA_FIFO_PID_TYPE) - 1;
679 if (qid >= QID_RX) {
680 /*
681 * Unknown queue, this shouldn't happen. Just drop
682 * this tx status.
683 */
684 WARNING(rt2x00dev, "Got TX status report with "
685 "unexpected pid %u, dropping", qid);
686 break;
687 }
688
689 queue = rt2x00queue_get_queue(rt2x00dev, qid);
690 if (unlikely(queue == NULL)) {
691 /*
692 * The queue is NULL, this shouldn't happen. Stop
693 * processing here and drop the tx status
694 */
695 WARNING(rt2x00dev, "Got TX status for an unavailable "
696 "queue %u, dropping", qid);
697 break;
698 }
699
700 if (rt2x00queue_empty(queue)) {
701 /*
702 * The queue is empty. Stop processing here
703 * and drop the tx status.
704 */
705 WARNING(rt2x00dev, "Got TX status for an empty "
706 "queue %u, dropping", qid);
707 break;
708 }
709
710 entry = rt2x00queue_get_entry(queue, Q_INDEX_DONE);
711 rt2800_txdone_entry(entry, status);
712 }
713}
714
715static void rt2800pci_txstatus_tasklet(unsigned long data)
716{
717 rt2800pci_txdone((struct rt2x00_dev *)data);
718}
719
663static irqreturn_t rt2800pci_interrupt_thread(int irq, void *dev_instance) 720static irqreturn_t rt2800pci_interrupt_thread(int irq, void *dev_instance)
664{ 721{
665 struct rt2x00_dev *rt2x00dev = dev_instance; 722 struct rt2x00_dev *rt2x00dev = dev_instance;
@@ -684,13 +741,7 @@ static irqreturn_t rt2800pci_interrupt_thread(int irq, void *dev_instance)
684 rt2x00pci_rxdone(rt2x00dev); 741 rt2x00pci_rxdone(rt2x00dev);
685 742
686 /* 743 /*
687 * 4 - Tx done interrupt. 744 * 4 - Auto wakeup interrupt.
688 */
689 if (rt2x00_get_field32(reg, INT_SOURCE_CSR_TX_FIFO_STATUS))
690 rt2800_txdone(rt2x00dev);
691
692 /*
693 * 5 - Auto wakeup interrupt.
694 */ 745 */
695 if (rt2x00_get_field32(reg, INT_SOURCE_CSR_AUTO_WAKEUP)) 746 if (rt2x00_get_field32(reg, INT_SOURCE_CSR_AUTO_WAKEUP))
696 rt2800pci_wakeup(rt2x00dev); 747 rt2800pci_wakeup(rt2x00dev);
@@ -702,10 +753,58 @@ static irqreturn_t rt2800pci_interrupt_thread(int irq, void *dev_instance)
702 return IRQ_HANDLED; 753 return IRQ_HANDLED;
703} 754}
704 755
756static void rt2800pci_txstatus_interrupt(struct rt2x00_dev *rt2x00dev)
757{
758 u32 status;
759 int i;
760
761 /*
762 * The TX_FIFO_STATUS interrupt needs special care. We should
763 * read TX_STA_FIFO but we should do it immediately as otherwise
764 * the register can overflow and we would lose status reports.
765 *
766 * Hence, read the TX_STA_FIFO register and copy all tx status
767 * reports into a kernel FIFO which is handled in the txstatus
768 * tasklet. We use a tasklet to process the tx status reports
769 * because we can schedule the tasklet multiple times (when the
770 * interrupt fires again during tx status processing).
771 *
772 * Furthermore we don't disable the TX_FIFO_STATUS
773 * interrupt here but leave it enabled so that the TX_STA_FIFO
774 * can also be read while the interrupt thread gets executed.
775 *
776 * Since we have only one producer and one consumer we don't
777 * need to lock the kfifo.
778 */
779 for (i = 0; i < TX_ENTRIES; i++) {
780 rt2800_register_read(rt2x00dev, TX_STA_FIFO, &status);
781
782 if (!rt2x00_get_field32(status, TX_STA_FIFO_VALID))
783 break;
784
785 if (kfifo_is_full(&rt2x00dev->txstatus_fifo)) {
786 WARNING(rt2x00dev, "TX status FIFO overrun,"
787 " drop tx status report.\n");
788 break;
789 }
790
791 if (kfifo_in(&rt2x00dev->txstatus_fifo, &status,
792 sizeof(status)) != sizeof(status)) {
793 WARNING(rt2x00dev, "TX status FIFO overrun,"
794 "drop tx status report.\n");
795 break;
796 }
797 }
798
799 /* Schedule the tasklet for processing the tx status. */
800 tasklet_schedule(&rt2x00dev->txstatus_tasklet);
801}
802
705static irqreturn_t rt2800pci_interrupt(int irq, void *dev_instance) 803static irqreturn_t rt2800pci_interrupt(int irq, void *dev_instance)
706{ 804{
707 struct rt2x00_dev *rt2x00dev = dev_instance; 805 struct rt2x00_dev *rt2x00dev = dev_instance;
708 u32 reg; 806 u32 reg;
807 irqreturn_t ret = IRQ_HANDLED;
709 808
710 /* Read status and ACK all interrupts */ 809 /* Read status and ACK all interrupts */
711 rt2800_register_read(rt2x00dev, INT_SOURCE_CSR, &reg); 810 rt2800_register_read(rt2x00dev, INT_SOURCE_CSR, &reg);
@@ -717,15 +816,38 @@ static irqreturn_t rt2800pci_interrupt(int irq, void *dev_instance)
717 if (!test_bit(DEVICE_STATE_ENABLED_RADIO, &rt2x00dev->flags)) 816 if (!test_bit(DEVICE_STATE_ENABLED_RADIO, &rt2x00dev->flags))
718 return IRQ_HANDLED; 817 return IRQ_HANDLED;
719 818
720 /* Store irqvalue for use in the interrupt thread. */ 819 if (rt2x00_get_field32(reg, INT_SOURCE_CSR_TX_FIFO_STATUS))
721 rt2x00dev->irqvalue[0] = reg; 820 rt2800pci_txstatus_interrupt(rt2x00dev);
821
822 if (rt2x00_get_field32(reg, INT_SOURCE_CSR_PRE_TBTT) ||
823 rt2x00_get_field32(reg, INT_SOURCE_CSR_TBTT) ||
824 rt2x00_get_field32(reg, INT_SOURCE_CSR_RX_DONE) ||
825 rt2x00_get_field32(reg, INT_SOURCE_CSR_AUTO_WAKEUP)) {
826 /*
827 * All other interrupts are handled in the interrupt thread.
828 * Store irqvalue for use in the interrupt thread.
829 */
830 rt2x00dev->irqvalue[0] = reg;
722 831
723 /* Disable interrupts, will be enabled again in the interrupt thread. */ 832 /*
724 rt2x00dev->ops->lib->set_device_state(rt2x00dev, 833 * Disable interrupts, will be enabled again in the
725 STATE_RADIO_IRQ_OFF_ISR); 834 * interrupt thread.
835 */
836 rt2x00dev->ops->lib->set_device_state(rt2x00dev,
837 STATE_RADIO_IRQ_OFF_ISR);
726 838
839 /*
840 * Leave the TX_FIFO_STATUS interrupt enabled to not lose any
841 * tx status reports.
842 */
843 rt2800_register_read(rt2x00dev, INT_MASK_CSR, &reg);
844 rt2x00_set_field32(&reg, INT_MASK_CSR_TX_FIFO_STATUS, 1);
845 rt2800_register_write(rt2x00dev, INT_MASK_CSR, reg);
727 846
728 return IRQ_WAKE_THREAD; 847 ret = IRQ_WAKE_THREAD;
848 }
849
850 return ret;
729} 851}
730 852
731/* 853/*
@@ -788,6 +910,7 @@ static int rt2800pci_probe_hw(struct rt2x00_dev *rt2x00dev)
788 __set_bit(DRIVER_REQUIRE_FIRMWARE, &rt2x00dev->flags); 910 __set_bit(DRIVER_REQUIRE_FIRMWARE, &rt2x00dev->flags);
789 __set_bit(DRIVER_REQUIRE_DMA, &rt2x00dev->flags); 911 __set_bit(DRIVER_REQUIRE_DMA, &rt2x00dev->flags);
790 __set_bit(DRIVER_REQUIRE_L2PAD, &rt2x00dev->flags); 912 __set_bit(DRIVER_REQUIRE_L2PAD, &rt2x00dev->flags);
913 __set_bit(DRIVER_REQUIRE_TXSTATUS_FIFO, &rt2x00dev->flags);
791 if (!modparam_nohwcrypt) 914 if (!modparam_nohwcrypt)
792 __set_bit(CONFIG_SUPPORT_HW_CRYPTO, &rt2x00dev->flags); 915 __set_bit(CONFIG_SUPPORT_HW_CRYPTO, &rt2x00dev->flags);
793 __set_bit(DRIVER_SUPPORT_LINK_TUNING, &rt2x00dev->flags); 916 __set_bit(DRIVER_SUPPORT_LINK_TUNING, &rt2x00dev->flags);
@@ -837,6 +960,7 @@ static const struct rt2800_ops rt2800pci_rt2800_ops = {
837static const struct rt2x00lib_ops rt2800pci_rt2x00_ops = { 960static const struct rt2x00lib_ops rt2800pci_rt2x00_ops = {
838 .irq_handler = rt2800pci_interrupt, 961 .irq_handler = rt2800pci_interrupt,
839 .irq_handler_thread = rt2800pci_interrupt_thread, 962 .irq_handler_thread = rt2800pci_interrupt_thread,
963 .txstatus_tasklet = rt2800pci_txstatus_tasklet,
840 .probe_hw = rt2800pci_probe_hw, 964 .probe_hw = rt2800pci_probe_hw,
841 .get_firmware_name = rt2800pci_get_firmware_name, 965 .get_firmware_name = rt2800pci_get_firmware_name,
842 .check_firmware = rt2800_check_firmware, 966 .check_firmware = rt2800_check_firmware,