ath9k_hw: fix stopping rx DMA during resets

During PHY errors, the MAC can sometimes fail to enter an idle state on older
hardware (before AR9380) after an rx stop has been requested.

This typically shows up in the kernel log with messages like these:

ath: Could not stop RX, we could be confusing the DMA engine when we start RX up
------------[ cut here ]------------
WARNING: at drivers/net/wireless/ath/ath9k/recv.c:504 ath_stoprecv+0xcc/0xf0 [ath9k]()
Call Trace:
[<8023f0e8>] dump_stack+0x8/0x34
[<80075050>] warn_slowpath_common+0x78/0xa4
[<80075094>] warn_slowpath_null+0x18/0x24
[<80d66d60>] ath_stoprecv+0xcc/0xf0 [ath9k]
[<80d642cc>] ath_set_channel+0xbc/0x270 [ath9k]
[<80d65254>] ath_radio_disable+0x4a4/0x7fc [ath9k]

When this happens, the state that the MAC enters is easy to identify and
does not result in bogus DMA traffic, however to ensure a working state
after a channel change, the hardware should still be reset.

This patch adds detection for this specific MAC state, after which the above
warnings completely disappear in my tests.

Signed-off-by: Felix Fietkau <nbd@openwrt.org>
Cc: stable@kernel.org
Cc: Kyungwan Nam <Kyungwan.Nam@Atheros.com>
Signed-off-by: John W. Linville <linville@tuxdriver.com>

authored by Felix Fietkau and committed by John W. Linville 5882da02 4a39e781

+26 -16
-9
drivers/net/wireless/ath/ath9k/hw.c
··· 1254 1254 ah->txchainmask = common->tx_chainmask; 1255 1255 ah->rxchainmask = common->rx_chainmask; 1256 1256 1257 - if ((common->bus_ops->ath_bus_type != ATH_USB) && !ah->chip_fullsleep) { 1258 - ath9k_hw_abortpcurecv(ah); 1259 - if (!ath9k_hw_stopdmarecv(ah)) { 1260 - ath_dbg(common, ATH_DBG_XMIT, 1261 - "Failed to stop receive dma\n"); 1262 - bChannelChange = false; 1263 - } 1264 - } 1265 - 1266 1257 if (!ath9k_hw_setpower(ah, ATH9K_PM_AWAKE)) 1267 1258 return -EIO; 1268 1259
+22 -3
drivers/net/wireless/ath/ath9k/mac.c
··· 751 751 } 752 752 EXPORT_SYMBOL(ath9k_hw_abortpcurecv); 753 753 754 - bool ath9k_hw_stopdmarecv(struct ath_hw *ah) 754 + bool ath9k_hw_stopdmarecv(struct ath_hw *ah, bool *reset) 755 755 { 756 756 #define AH_RX_STOP_DMA_TIMEOUT 10000 /* usec */ 757 757 #define AH_RX_TIME_QUANTUM 100 /* usec */ 758 758 struct ath_common *common = ath9k_hw_common(ah); 759 + u32 mac_status, last_mac_status = 0; 759 760 int i; 761 + 762 + /* Enable access to the DMA observation bus */ 763 + REG_WRITE(ah, AR_MACMISC, 764 + ((AR_MACMISC_DMA_OBS_LINE_8 << AR_MACMISC_DMA_OBS_S) | 765 + (AR_MACMISC_MISC_OBS_BUS_1 << 766 + AR_MACMISC_MISC_OBS_BUS_MSB_S))); 760 767 761 768 REG_WRITE(ah, AR_CR, AR_CR_RXD); 762 769 ··· 771 764 for (i = AH_RX_STOP_DMA_TIMEOUT / AH_TIME_QUANTUM; i != 0; i--) { 772 765 if ((REG_READ(ah, AR_CR) & AR_CR_RXE) == 0) 773 766 break; 767 + 768 + if (!AR_SREV_9300_20_OR_LATER(ah)) { 769 + mac_status = REG_READ(ah, AR_DMADBG_7) & 0x7f0; 770 + if (mac_status == 0x1c0 && mac_status == last_mac_status) { 771 + *reset = true; 772 + break; 773 + } 774 + 775 + last_mac_status = mac_status; 776 + } 777 + 774 778 udelay(AH_TIME_QUANTUM); 775 779 } 776 780 777 781 if (i == 0) { 778 782 ath_err(common, 779 - "DMA failed to stop in %d ms AR_CR=0x%08x AR_DIAG_SW=0x%08x\n", 783 + "DMA failed to stop in %d ms AR_CR=0x%08x AR_DIAG_SW=0x%08x DMADBG_7=0x%08x\n", 780 784 AH_RX_STOP_DMA_TIMEOUT / 1000, 781 785 REG_READ(ah, AR_CR), 782 - REG_READ(ah, AR_DIAG_SW)); 786 + REG_READ(ah, AR_DIAG_SW), 787 + REG_READ(ah, AR_DMADBG_7)); 783 788 return false; 784 789 } else { 785 790 return true;
+1 -1
drivers/net/wireless/ath/ath9k/mac.h
··· 695 695 void ath9k_hw_putrxbuf(struct ath_hw *ah, u32 rxdp); 696 696 void ath9k_hw_startpcureceive(struct ath_hw *ah, bool is_scanning); 697 697 void ath9k_hw_abortpcurecv(struct ath_hw *ah); 698 - bool ath9k_hw_stopdmarecv(struct ath_hw *ah); 698 + bool ath9k_hw_stopdmarecv(struct ath_hw *ah, bool *reset); 699 699 int ath9k_hw_beaconq_setup(struct ath_hw *ah); 700 700 701 701 /* Interrupt Handling */
+3 -3
drivers/net/wireless/ath/ath9k/recv.c
··· 486 486 bool ath_stoprecv(struct ath_softc *sc) 487 487 { 488 488 struct ath_hw *ah = sc->sc_ah; 489 - bool stopped; 489 + bool stopped, reset = false; 490 490 491 491 spin_lock_bh(&sc->rx.rxbuflock); 492 492 ath9k_hw_abortpcurecv(ah); 493 493 ath9k_hw_setrxfilter(ah, 0); 494 - stopped = ath9k_hw_stopdmarecv(ah); 494 + stopped = ath9k_hw_stopdmarecv(ah, &reset); 495 495 496 496 if (sc->sc_ah->caps.hw_caps & ATH9K_HW_CAP_EDMA) 497 497 ath_edma_stop_recv(sc); ··· 506 506 "confusing the DMA engine when we start RX up\n"); 507 507 ATH_DBG_WARN_ON_ONCE(!stopped); 508 508 } 509 - return stopped; 509 + return stopped || reset; 510 510 } 511 511 512 512 void ath_flushrecv(struct ath_softc *sc)