aboutsummaryrefslogtreecommitdiffstats
path: root/drivers
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
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')
-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)
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,
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 @@
36#include <linux/mutex.h> 36#include <linux/mutex.h>
37#include <linux/etherdevice.h> 37#include <linux/etherdevice.h>
38#include <linux/input-polldev.h> 38#include <linux/input-polldev.h>
39#include <linux/kfifo.h>
39 40
40#include <net/mac80211.h> 41#include <net/mac80211.h>
41 42
@@ -522,6 +523,11 @@ struct rt2x00lib_ops {
522 irq_handler_t irq_handler_thread; 523 irq_handler_t irq_handler_thread;
523 524
524 /* 525 /*
526 * TX status tasklet handler.
527 */
528 void (*txstatus_tasklet) (unsigned long data);
529
530 /*
525 * Device init handlers. 531 * Device init handlers.
526 */ 532 */
527 int (*probe_hw) (struct rt2x00_dev *rt2x00dev); 533 int (*probe_hw) (struct rt2x00_dev *rt2x00dev);
@@ -651,6 +657,7 @@ enum rt2x00_flags {
651 DRIVER_REQUIRE_DMA, 657 DRIVER_REQUIRE_DMA,
652 DRIVER_REQUIRE_COPY_IV, 658 DRIVER_REQUIRE_COPY_IV,
653 DRIVER_REQUIRE_L2PAD, 659 DRIVER_REQUIRE_L2PAD,
660 DRIVER_REQUIRE_TXSTATUS_FIFO,
654 661
655 /* 662 /*
656 * Driver features 663 * Driver features
@@ -884,6 +891,16 @@ struct rt2x00_dev {
884 * and interrupt thread routine. 891 * and interrupt thread routine.
885 */ 892 */
886 u32 irqvalue[2]; 893 u32 irqvalue[2];
894
895 /*
896 * FIFO for storing tx status reports between isr and tasklet.
897 */
898 struct kfifo txstatus_fifo;
899
900 /*
901 * Tasklet for processing tx status reports (rt2800pci).
902 */
903 struct tasklet_struct txstatus_tasklet;
887}; 904};
888 905
889/* 906/*
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)
813 rt2x00dev->hw->extra_tx_headroom += RT2X00_ALIGN_SIZE; 813 rt2x00dev->hw->extra_tx_headroom += RT2X00_ALIGN_SIZE;
814 814
815 /* 815 /*
816 * Allocate tx status FIFO for driver use.
817 */
818 if (test_bit(DRIVER_REQUIRE_TXSTATUS_FIFO, &rt2x00dev->flags) &&
819 rt2x00dev->ops->lib->txstatus_tasklet) {
820 /*
821 * Allocate txstatus fifo and tasklet, we use a size of 512
822 * for the kfifo which is big enough to store 512/4=128 tx
823 * status reports. In the worst case (tx status for all tx
824 * queues gets reported before we've got a chance to handle
825 * them) 24*4=384 tx status reports need to be cached.
826 */
827 status = kfifo_alloc(&rt2x00dev->txstatus_fifo, 512,
828 GFP_KERNEL);
829 if (status)
830 return status;
831
832 /* tasklet for processing the tx status reports. */
833 tasklet_init(&rt2x00dev->txstatus_tasklet,
834 rt2x00dev->ops->lib->txstatus_tasklet,
835 (unsigned long)rt2x00dev);
836
837 }
838
839 /*
816 * Register HW. 840 * Register HW.
817 */ 841 */
818 status = ieee80211_register_hw(rt2x00dev->hw); 842 status = ieee80211_register_hw(rt2x00dev->hw);
@@ -1028,6 +1052,16 @@ void rt2x00lib_remove_dev(struct rt2x00_dev *rt2x00dev)
1028 cancel_work_sync(&rt2x00dev->txdone_work); 1052 cancel_work_sync(&rt2x00dev->txdone_work);
1029 1053
1030 /* 1054 /*
1055 * Free the tx status fifo.
1056 */
1057 kfifo_free(&rt2x00dev->txstatus_fifo);
1058
1059 /*
1060 * Kill the tx status tasklet.
1061 */
1062 tasklet_kill(&rt2x00dev->txstatus_tasklet);
1063
1064 /*
1031 * Uninitialize device. 1065 * Uninitialize device.
1032 */ 1066 */
1033 rt2x00lib_uninitialize(rt2x00dev); 1067 rt2x00lib_uninitialize(rt2x00dev);