diff options
author | Helmut Schaa <helmut.schaa@googlemail.com> | 2011-01-30 07:18:38 -0500 |
---|---|---|
committer | John W. Linville <linville@tuxdriver.com> | 2011-01-31 15:06:23 -0500 |
commit | a9d61e9e779579c66c0d4c8af203d51dbca1473c (patch) | |
tree | 05f2923aa5c397f605ea4ef0c819e043cb797521 /drivers/net/wireless/rt2x00 | |
parent | c8e15a1e2c93880160f31ed2e6b02c1322f7f48d (diff) |
rt2x00: Convert rt2800pci to use tasklets
Fix interrupt processing on slow machines by using individual tasklets
for each different device interrupt. This ensures that while a RX or TX
status tasklet is scheduled only the according device interrupt is
masked and other interrupts such as TBTT can still be processed.
Also, this allows us to use tasklet_hi_schedule for TBTT and PRETBTT
processing which is required to not send out beacons with a wrong DTIM
count (due to delayed periodic beacon updates). Furthermore, this
improves the latency between the TBTT and sending out buffered multi-
and broadcast traffic.
As a nice bonus, the interrupt handling overhead is reduced such that
rt2800pci gains around 25% more throuhput on a rt3052 MIPS board.
Signed-off-by: Helmut Schaa <helmut.schaa@googlemail.com>
Acked-by: Gertjan van Wingerde <gwingerde@gmail.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')
-rw-r--r-- | drivers/net/wireless/rt2x00/rt2800pci.c | 173 |
1 files changed, 114 insertions, 59 deletions
diff --git a/drivers/net/wireless/rt2x00/rt2800pci.c b/drivers/net/wireless/rt2x00/rt2800pci.c index e4d97ad25bf4..31483c79a457 100644 --- a/drivers/net/wireless/rt2x00/rt2800pci.c +++ b/drivers/net/wireless/rt2x00/rt2800pci.c | |||
@@ -200,6 +200,13 @@ static void rt2800pci_start_queue(struct data_queue *queue) | |||
200 | rt2800_register_write(rt2x00dev, MAC_SYS_CTRL, reg); | 200 | rt2800_register_write(rt2x00dev, MAC_SYS_CTRL, reg); |
201 | break; | 201 | break; |
202 | case QID_BEACON: | 202 | case QID_BEACON: |
203 | /* | ||
204 | * Allow beacon tasklets to be scheduled for periodic | ||
205 | * beacon updates. | ||
206 | */ | ||
207 | tasklet_enable(&rt2x00dev->tbtt_tasklet); | ||
208 | tasklet_enable(&rt2x00dev->pretbtt_tasklet); | ||
209 | |||
203 | rt2800_register_read(rt2x00dev, BCN_TIME_CFG, ®); | 210 | rt2800_register_read(rt2x00dev, BCN_TIME_CFG, ®); |
204 | rt2x00_set_field32(®, BCN_TIME_CFG_TSF_TICKING, 1); | 211 | rt2x00_set_field32(®, BCN_TIME_CFG_TSF_TICKING, 1); |
205 | rt2x00_set_field32(®, BCN_TIME_CFG_TBTT_ENABLE, 1); | 212 | rt2x00_set_field32(®, BCN_TIME_CFG_TBTT_ENABLE, 1); |
@@ -258,6 +265,12 @@ static void rt2800pci_stop_queue(struct data_queue *queue) | |||
258 | rt2800_register_read(rt2x00dev, INT_TIMER_EN, ®); | 265 | rt2800_register_read(rt2x00dev, INT_TIMER_EN, ®); |
259 | rt2x00_set_field32(®, INT_TIMER_EN_PRE_TBTT_TIMER, 0); | 266 | rt2x00_set_field32(®, INT_TIMER_EN_PRE_TBTT_TIMER, 0); |
260 | rt2800_register_write(rt2x00dev, INT_TIMER_EN, reg); | 267 | rt2800_register_write(rt2x00dev, INT_TIMER_EN, reg); |
268 | |||
269 | /* | ||
270 | * Wait for tbtt tasklets to finish. | ||
271 | */ | ||
272 | tasklet_disable(&rt2x00dev->tbtt_tasklet); | ||
273 | tasklet_disable(&rt2x00dev->pretbtt_tasklet); | ||
261 | break; | 274 | break; |
262 | default: | 275 | default: |
263 | break; | 276 | break; |
@@ -408,6 +421,7 @@ static void rt2800pci_toggle_irq(struct rt2x00_dev *rt2x00dev, | |||
408 | int mask = (state == STATE_RADIO_IRQ_ON) || | 421 | int mask = (state == STATE_RADIO_IRQ_ON) || |
409 | (state == STATE_RADIO_IRQ_ON_ISR); | 422 | (state == STATE_RADIO_IRQ_ON_ISR); |
410 | u32 reg; | 423 | u32 reg; |
424 | unsigned long flags; | ||
411 | 425 | ||
412 | /* | 426 | /* |
413 | * When interrupts are being enabled, the interrupt registers | 427 | * When interrupts are being enabled, the interrupt registers |
@@ -417,10 +431,16 @@ static void rt2800pci_toggle_irq(struct rt2x00_dev *rt2x00dev, | |||
417 | rt2800_register_read(rt2x00dev, INT_SOURCE_CSR, ®); | 431 | rt2800_register_read(rt2x00dev, INT_SOURCE_CSR, ®); |
418 | rt2800_register_write(rt2x00dev, INT_SOURCE_CSR, reg); | 432 | rt2800_register_write(rt2x00dev, INT_SOURCE_CSR, reg); |
419 | 433 | ||
434 | /* | ||
435 | * Enable tasklets. The beacon related tasklets are | ||
436 | * enabled when the beacon queue is started. | ||
437 | */ | ||
420 | tasklet_enable(&rt2x00dev->txstatus_tasklet); | 438 | tasklet_enable(&rt2x00dev->txstatus_tasklet); |
421 | } else if (state == STATE_RADIO_IRQ_OFF) | 439 | tasklet_enable(&rt2x00dev->rxdone_tasklet); |
422 | tasklet_disable(&rt2x00dev->txstatus_tasklet); | 440 | tasklet_enable(&rt2x00dev->autowake_tasklet); |
441 | } | ||
423 | 442 | ||
443 | spin_lock_irqsave(&rt2x00dev->irqmask_lock, flags); | ||
424 | rt2800_register_read(rt2x00dev, INT_MASK_CSR, ®); | 444 | rt2800_register_read(rt2x00dev, INT_MASK_CSR, ®); |
425 | rt2x00_set_field32(®, INT_MASK_CSR_RXDELAYINT, 0); | 445 | rt2x00_set_field32(®, INT_MASK_CSR_RXDELAYINT, 0); |
426 | rt2x00_set_field32(®, INT_MASK_CSR_TXDELAYINT, 0); | 446 | rt2x00_set_field32(®, INT_MASK_CSR_TXDELAYINT, 0); |
@@ -441,6 +461,17 @@ static void rt2800pci_toggle_irq(struct rt2x00_dev *rt2x00dev, | |||
441 | rt2x00_set_field32(®, INT_MASK_CSR_RX_COHERENT, 0); | 461 | rt2x00_set_field32(®, INT_MASK_CSR_RX_COHERENT, 0); |
442 | rt2x00_set_field32(®, INT_MASK_CSR_TX_COHERENT, 0); | 462 | rt2x00_set_field32(®, INT_MASK_CSR_TX_COHERENT, 0); |
443 | rt2800_register_write(rt2x00dev, INT_MASK_CSR, reg); | 463 | rt2800_register_write(rt2x00dev, INT_MASK_CSR, reg); |
464 | spin_unlock_irqrestore(&rt2x00dev->irqmask_lock, flags); | ||
465 | |||
466 | if (state == STATE_RADIO_IRQ_OFF) { | ||
467 | /* | ||
468 | * Ensure that all tasklets are finished before | ||
469 | * disabling the interrupts. | ||
470 | */ | ||
471 | tasklet_disable(&rt2x00dev->txstatus_tasklet); | ||
472 | tasklet_disable(&rt2x00dev->rxdone_tasklet); | ||
473 | tasklet_disable(&rt2x00dev->autowake_tasklet); | ||
474 | } | ||
444 | } | 475 | } |
445 | 476 | ||
446 | static int rt2800pci_init_registers(struct rt2x00_dev *rt2x00dev) | 477 | static int rt2800pci_init_registers(struct rt2x00_dev *rt2x00dev) |
@@ -721,45 +752,60 @@ static void rt2800pci_txdone(struct rt2x00_dev *rt2x00dev) | |||
721 | } | 752 | } |
722 | } | 753 | } |
723 | 754 | ||
724 | static void rt2800pci_txstatus_tasklet(unsigned long data) | 755 | static void rt2800pci_enable_interrupt(struct rt2x00_dev *rt2x00dev, |
725 | { | 756 | struct rt2x00_field32 irq_field) |
726 | rt2800pci_txdone((struct rt2x00_dev *)data); | ||
727 | } | ||
728 | |||
729 | static irqreturn_t rt2800pci_interrupt_thread(int irq, void *dev_instance) | ||
730 | { | 757 | { |
731 | struct rt2x00_dev *rt2x00dev = dev_instance; | 758 | unsigned long flags; |
732 | u32 reg = rt2x00dev->irqvalue[0]; | 759 | u32 reg; |
733 | 760 | ||
734 | /* | 761 | /* |
735 | * 1 - Pre TBTT interrupt. | 762 | * Enable a single interrupt. The interrupt mask register |
763 | * access needs locking. | ||
736 | */ | 764 | */ |
737 | if (rt2x00_get_field32(reg, INT_SOURCE_CSR_PRE_TBTT)) | 765 | spin_lock_irqsave(&rt2x00dev->irqmask_lock, flags); |
738 | rt2x00lib_pretbtt(rt2x00dev); | 766 | rt2800_register_read(rt2x00dev, INT_MASK_CSR, ®); |
767 | rt2x00_set_field32(®, irq_field, 1); | ||
768 | rt2800_register_write(rt2x00dev, INT_MASK_CSR, reg); | ||
769 | spin_unlock_irqrestore(&rt2x00dev->irqmask_lock, flags); | ||
770 | } | ||
739 | 771 | ||
740 | /* | 772 | static void rt2800pci_txstatus_tasklet(unsigned long data) |
741 | * 2 - Beacondone interrupt. | 773 | { |
742 | */ | 774 | rt2800pci_txdone((struct rt2x00_dev *)data); |
743 | if (rt2x00_get_field32(reg, INT_SOURCE_CSR_TBTT)) | ||
744 | rt2x00lib_beacondone(rt2x00dev); | ||
745 | 775 | ||
746 | /* | 776 | /* |
747 | * 3 - Rx ring done interrupt. | 777 | * No need to enable the tx status interrupt here as we always |
778 | * leave it enabled to minimize the possibility of a tx status | ||
779 | * register overflow. See comment in interrupt handler. | ||
748 | */ | 780 | */ |
749 | if (rt2x00_get_field32(reg, INT_SOURCE_CSR_RX_DONE)) | 781 | } |
750 | rt2x00pci_rxdone(rt2x00dev); | ||
751 | 782 | ||
752 | /* | 783 | static void rt2800pci_pretbtt_tasklet(unsigned long data) |
753 | * 4 - Auto wakeup interrupt. | 784 | { |
754 | */ | 785 | struct rt2x00_dev *rt2x00dev = (struct rt2x00_dev *)data; |
755 | if (rt2x00_get_field32(reg, INT_SOURCE_CSR_AUTO_WAKEUP)) | 786 | rt2x00lib_pretbtt(rt2x00dev); |
756 | rt2800pci_wakeup(rt2x00dev); | 787 | rt2800pci_enable_interrupt(rt2x00dev, INT_MASK_CSR_PRE_TBTT); |
788 | } | ||
757 | 789 | ||
758 | /* Enable interrupts again. */ | 790 | static void rt2800pci_tbtt_tasklet(unsigned long data) |
759 | rt2x00dev->ops->lib->set_device_state(rt2x00dev, | 791 | { |
760 | STATE_RADIO_IRQ_ON_ISR); | 792 | struct rt2x00_dev *rt2x00dev = (struct rt2x00_dev *)data; |
793 | rt2x00lib_beacondone(rt2x00dev); | ||
794 | rt2800pci_enable_interrupt(rt2x00dev, INT_MASK_CSR_TBTT); | ||
795 | } | ||
761 | 796 | ||
762 | return IRQ_HANDLED; | 797 | static void rt2800pci_rxdone_tasklet(unsigned long data) |
798 | { | ||
799 | struct rt2x00_dev *rt2x00dev = (struct rt2x00_dev *)data; | ||
800 | rt2x00pci_rxdone(rt2x00dev); | ||
801 | rt2800pci_enable_interrupt(rt2x00dev, INT_MASK_CSR_RX_DONE); | ||
802 | } | ||
803 | |||
804 | static void rt2800pci_autowake_tasklet(unsigned long data) | ||
805 | { | ||
806 | struct rt2x00_dev *rt2x00dev = (struct rt2x00_dev *)data; | ||
807 | rt2800pci_wakeup(rt2x00dev); | ||
808 | rt2800pci_enable_interrupt(rt2x00dev, INT_MASK_CSR_AUTO_WAKEUP); | ||
763 | } | 809 | } |
764 | 810 | ||
765 | static void rt2800pci_txstatus_interrupt(struct rt2x00_dev *rt2x00dev) | 811 | static void rt2800pci_txstatus_interrupt(struct rt2x00_dev *rt2x00dev) |
@@ -805,8 +851,8 @@ static void rt2800pci_txstatus_interrupt(struct rt2x00_dev *rt2x00dev) | |||
805 | static irqreturn_t rt2800pci_interrupt(int irq, void *dev_instance) | 851 | static irqreturn_t rt2800pci_interrupt(int irq, void *dev_instance) |
806 | { | 852 | { |
807 | struct rt2x00_dev *rt2x00dev = dev_instance; | 853 | struct rt2x00_dev *rt2x00dev = dev_instance; |
808 | u32 reg; | 854 | u32 reg, mask; |
809 | irqreturn_t ret = IRQ_HANDLED; | 855 | unsigned long flags; |
810 | 856 | ||
811 | /* Read status and ACK all interrupts */ | 857 | /* Read status and ACK all interrupts */ |
812 | rt2800_register_read(rt2x00dev, INT_SOURCE_CSR, ®); | 858 | rt2800_register_read(rt2x00dev, INT_SOURCE_CSR, ®); |
@@ -818,38 +864,44 @@ static irqreturn_t rt2800pci_interrupt(int irq, void *dev_instance) | |||
818 | if (!test_bit(DEVICE_STATE_ENABLED_RADIO, &rt2x00dev->flags)) | 864 | if (!test_bit(DEVICE_STATE_ENABLED_RADIO, &rt2x00dev->flags)) |
819 | return IRQ_HANDLED; | 865 | return IRQ_HANDLED; |
820 | 866 | ||
821 | if (rt2x00_get_field32(reg, INT_SOURCE_CSR_TX_FIFO_STATUS)) | 867 | /* |
822 | rt2800pci_txstatus_interrupt(rt2x00dev); | 868 | * Since INT_MASK_CSR and INT_SOURCE_CSR use the same bits |
869 | * for interrupts and interrupt masks we can just use the value of | ||
870 | * INT_SOURCE_CSR to create the interrupt mask. | ||
871 | */ | ||
872 | mask = ~reg; | ||
823 | 873 | ||
824 | if (rt2x00_get_field32(reg, INT_SOURCE_CSR_PRE_TBTT) || | 874 | if (rt2x00_get_field32(reg, INT_SOURCE_CSR_TX_FIFO_STATUS)) { |
825 | rt2x00_get_field32(reg, INT_SOURCE_CSR_TBTT) || | 875 | rt2800pci_txstatus_interrupt(rt2x00dev); |
826 | rt2x00_get_field32(reg, INT_SOURCE_CSR_RX_DONE) || | ||
827 | rt2x00_get_field32(reg, INT_SOURCE_CSR_AUTO_WAKEUP)) { | ||
828 | /* | 876 | /* |
829 | * All other interrupts are handled in the interrupt thread. | 877 | * Never disable the TX_FIFO_STATUS interrupt. |
830 | * Store irqvalue for use in the interrupt thread. | ||
831 | */ | 878 | */ |
832 | rt2x00dev->irqvalue[0] = reg; | 879 | rt2x00_set_field32(&mask, INT_MASK_CSR_TX_FIFO_STATUS, 1); |
880 | } | ||
833 | 881 | ||
834 | /* | 882 | if (rt2x00_get_field32(reg, INT_SOURCE_CSR_PRE_TBTT)) |
835 | * Disable interrupts, will be enabled again in the | 883 | tasklet_hi_schedule(&rt2x00dev->pretbtt_tasklet); |
836 | * interrupt thread. | ||
837 | */ | ||
838 | rt2x00dev->ops->lib->set_device_state(rt2x00dev, | ||
839 | STATE_RADIO_IRQ_OFF_ISR); | ||
840 | 884 | ||
841 | /* | 885 | if (rt2x00_get_field32(reg, INT_SOURCE_CSR_TBTT)) |
842 | * Leave the TX_FIFO_STATUS interrupt enabled to not lose any | 886 | tasklet_hi_schedule(&rt2x00dev->tbtt_tasklet); |
843 | * tx status reports. | ||
844 | */ | ||
845 | rt2800_register_read(rt2x00dev, INT_MASK_CSR, ®); | ||
846 | rt2x00_set_field32(®, INT_MASK_CSR_TX_FIFO_STATUS, 1); | ||
847 | rt2800_register_write(rt2x00dev, INT_MASK_CSR, reg); | ||
848 | 887 | ||
849 | ret = IRQ_WAKE_THREAD; | 888 | if (rt2x00_get_field32(reg, INT_SOURCE_CSR_RX_DONE)) |
850 | } | 889 | tasklet_schedule(&rt2x00dev->rxdone_tasklet); |
851 | 890 | ||
852 | return ret; | 891 | if (rt2x00_get_field32(reg, INT_SOURCE_CSR_AUTO_WAKEUP)) |
892 | tasklet_schedule(&rt2x00dev->autowake_tasklet); | ||
893 | |||
894 | /* | ||
895 | * Disable all interrupts for which a tasklet was scheduled right now, | ||
896 | * the tasklet will reenable the appropriate interrupts. | ||
897 | */ | ||
898 | spin_lock_irqsave(&rt2x00dev->irqmask_lock, flags); | ||
899 | rt2800_register_read(rt2x00dev, INT_MASK_CSR, ®); | ||
900 | reg &= mask; | ||
901 | rt2800_register_write(rt2x00dev, INT_MASK_CSR, reg); | ||
902 | spin_unlock_irqrestore(&rt2x00dev->irqmask_lock, flags); | ||
903 | |||
904 | return IRQ_HANDLED; | ||
853 | } | 905 | } |
854 | 906 | ||
855 | /* | 907 | /* |
@@ -964,8 +1016,11 @@ static const struct rt2800_ops rt2800pci_rt2800_ops = { | |||
964 | 1016 | ||
965 | static const struct rt2x00lib_ops rt2800pci_rt2x00_ops = { | 1017 | static const struct rt2x00lib_ops rt2800pci_rt2x00_ops = { |
966 | .irq_handler = rt2800pci_interrupt, | 1018 | .irq_handler = rt2800pci_interrupt, |
967 | .irq_handler_thread = rt2800pci_interrupt_thread, | 1019 | .txstatus_tasklet = rt2800pci_txstatus_tasklet, |
968 | .txstatus_tasklet = rt2800pci_txstatus_tasklet, | 1020 | .pretbtt_tasklet = rt2800pci_pretbtt_tasklet, |
1021 | .tbtt_tasklet = rt2800pci_tbtt_tasklet, | ||
1022 | .rxdone_tasklet = rt2800pci_rxdone_tasklet, | ||
1023 | .autowake_tasklet = rt2800pci_autowake_tasklet, | ||
969 | .probe_hw = rt2800pci_probe_hw, | 1024 | .probe_hw = rt2800pci_probe_hw, |
970 | .get_firmware_name = rt2800pci_get_firmware_name, | 1025 | .get_firmware_name = rt2800pci_get_firmware_name, |
971 | .check_firmware = rt2800_check_firmware, | 1026 | .check_firmware = rt2800_check_firmware, |