diff options
| author | Florian Maurer | 2026-01-10 13:05:57 +0000 |
|---|---|---|
| committer | Hauke Mehrtens | 2026-01-19 00:16:27 +0000 |
| commit | 6a11ba0fd69213139e753373931006e7cf382b50 (patch) | |
| tree | d075bbf8781f87dcbf21bdafb538d5edcdc42c1f | |
| parent | 18771e11c926d7c7b21d8b8ecfc2485a65760ce4 (diff) | |
| download | openwrt-6a11ba0fd69213139e753373931006e7cf382b50.tar.gz | |
mac80211: ath9k: Add RX inactivity detection and reset chip
Some ath9k chips can, seemingly at random, end up in a state which can
be described as "deaf". No or nearly no interrupts are generated anymore
for incoming packets. Existing links either break down after a while and
new links will not be established.
This is merged upstream in
https://patchwork.kernel.org/project/linux-wireless/patch/20241106-ath9k-deaf-detection-v1-1-736a150d2425@redhat.com/
Signed-off-by: Florian Maurer <f.maurer@outlook.de>
Link: https://github.com/openwrt/openwrt/pull/21487
Signed-off-by: Hauke Mehrtens <hauke@hauke-m.de>
| -rw-r--r-- | package/kernel/mac80211/patches/ath9k/554-ath9k-add_rx_inactivity_for_deaf_detection.patch | 163 |
1 files changed, 163 insertions, 0 deletions
diff --git a/package/kernel/mac80211/patches/ath9k/554-ath9k-add_rx_inactivity_for_deaf_detection.patch b/package/kernel/mac80211/patches/ath9k/554-ath9k-add_rx_inactivity_for_deaf_detection.patch new file mode 100644 index 0000000000..9b01bcd5bd --- /dev/null +++ b/package/kernel/mac80211/patches/ath9k/554-ath9k-add_rx_inactivity_for_deaf_detection.patch @@ -0,0 +1,163 @@ +From: =?utf-8?q?Toke_H=C3=B8iland-J=C3=B8rgensen?= <toke@toke.dk> +Date: Wed, 06 Nov 2024 13:41:44 +0100 +Subject: [PATCH] ath9k: Add RX inactivity detection and reset chip when it + occurs + +Some ath9k chips can, seemingly at random, end up in a state which can +be described as "deaf". No or nearly no interrupts are generated anymore +for incoming packets. Existing links either break down after a while and +new links will not be established. + +The circumstances leading to this "deafness" is still unclear, but some +particular chips (especially 2-stream 11n SoCs, but also others) can go +'deaf' when running AP or mesh (or both) after some time. It's probably +a hardware issue, and doing a channel scan to trigger a chip +reset (which one normally can't do on an AP interface) recovers the +hardware. + +The only way the driver can detect this state, is by detecting if there +has been no RX activity for a while. In this case we can proactively +reset the chip (which only takes a small number of milliseconds, so +shouldn't interrupt things too much if it has been idle for several +seconds), which functions as a workaround. + +OpenWrt, and various derivatives, have been carrying versions of this +workaround for years, that were never upstreamed. One version[0], +written by Felix Fietkau, used a simple counter and only reset if there +was precisely zero RX activity for a long period of time. This had the +problem that in some cases a small number of interrupts would appear +even if the device was otherwise not responsive. For this reason, +another version[1], written by Simon Wunderlich and Sven Eckelmann, used +a time-based approach to calculate the average number of RX interrupts +over a longer (four-second) interval, and reset the chip when seeing +less than one interrupt per second over this period. However, that +version relied on debugfs counters to keep track of the number of +interrupts, which means it didn't work at all if debugfs was not +enabled. + +This patch unifies the two versions: it uses the same approach as Felix' +patch to count the number of RX handler invocations, but uses the same +time-based windowing approach as Simon and Sven's patch to still handle +the case where occasional interrupts appear but the device is otherwise +deaf. + +Since this is based on ideas by all three people, but not actually +directly derived from any of the patches, I'm including Suggested-by +tags from Simon, Sven and Felix below, which should hopefully serve as +proper credit. + +[0] https://patchwork.kernel.org/project/linux-wireless/patch/20170125163654.66431-3-nbd@nbd.name/ +[1] https://patchwork.kernel.org/project/linux-wireless/patch/20161117083614.19188-2-sven.eckelmann@open-mesh.com/ + +Suggested-by: Simon Wunderlich <sw@simonwunderlich.de> +Suggested-by: Sven Eckelmann <se@simonwunderlich.de> +Suggested-by: Felix Fietkau <nbd@nbd.name> +Signed-off-by: Toke Høiland-Jørgensen <toke@toke.dk> +Reviewed-by: Sven Eckelmann <se@simonwunderlich.de> +Acked-by: Simon Wunderlich <sw@simonwunderlich.de> +Tested-by: Issam Hamdi <ih@simonwunderlich.de> +Tested-by: Sven Eckelmann <se@simonwunderlich.de> +--- + drivers/net/wireless/ath/ath9k/ath9k.h | 2 ++ + drivers/net/wireless/ath/ath9k/debug.c | 1 + + drivers/net/wireless/ath/ath9k/debug.h | 1 + + drivers/net/wireless/ath/ath9k/link.c | 33 +++++++++++++++++++++++++++++++-- + drivers/net/wireless/ath/ath9k/main.c | 1 + + 5 files changed, 36 insertions(+), 2 deletions(-) + + +--- +base-commit: c33f9c2728d0ccc7472e6239346c0fb3de556e0f +change-id: 20241105-ath9k-deaf-detection-0e26bb3243a6 + +--- a/drivers/net/wireless/ath/ath9k/ath9k.h ++++ b/drivers/net/wireless/ath/ath9k/ath9k.h +@@ -1041,6 +1041,8 @@ struct ath_softc { + + u8 gtt_cnt; + u32 intrstatus; ++ u32 rx_active_check_time; ++ u32 rx_active_count; + u16 ps_flags; /* PS_* */ + bool ps_enabled; + bool ps_idle; +--- a/drivers/net/wireless/ath/ath9k/debug.c ++++ b/drivers/net/wireless/ath/ath9k/debug.c +@@ -805,6 +805,7 @@ static int read_file_reset(struct seq_fi + [RESET_TYPE_CALIBRATION] = "Calibration error", + [RESET_TX_DMA_ERROR] = "Tx DMA stop error", + [RESET_RX_DMA_ERROR] = "Rx DMA stop error", ++ [RESET_TYPE_RX_INACTIVE] = "Rx path inactive", + }; + int i; + +--- a/drivers/net/wireless/ath/ath9k/debug.h ++++ b/drivers/net/wireless/ath/ath9k/debug.h +@@ -53,6 +53,7 @@ enum ath_reset_type { + RESET_TYPE_CALIBRATION, + RESET_TX_DMA_ERROR, + RESET_RX_DMA_ERROR, ++ RESET_TYPE_RX_INACTIVE, + __RESET_TYPE_MAX + }; + +--- a/drivers/net/wireless/ath/ath9k/link.c ++++ b/drivers/net/wireless/ath/ath9k/link.c +@@ -50,7 +50,36 @@ reset: + "tx hung, resetting the chip\n"); + ath9k_queue_reset(sc, RESET_TYPE_TX_HANG); + return false; ++} ++ ++#define RX_INACTIVE_CHECK_INTERVAL (4 * MSEC_PER_SEC) ++ ++static bool ath_hw_rx_inactive_check(struct ath_softc *sc) ++{ ++ struct ath_common *common = ath9k_hw_common(sc->sc_ah); ++ u32 interval, count; ++ ++ interval = jiffies_to_msecs(jiffies - sc->rx_active_check_time); ++ count = sc->rx_active_count; + ++ if (interval < RX_INACTIVE_CHECK_INTERVAL) ++ return true; /* too soon to check */ ++ ++ sc->rx_active_count = 0; ++ sc->rx_active_check_time = jiffies; ++ ++ /* Need at least one interrupt per second, and we should only react if ++ * we are within a factor two of the expected interval ++ */ ++ if (interval > RX_INACTIVE_CHECK_INTERVAL * 2 || ++ count >= interval / MSEC_PER_SEC) ++ return true; ++ ++ ath_dbg(common, RESET, ++ "RX inactivity detected. Schedule chip reset\n"); ++ ath9k_queue_reset(sc, RESET_TYPE_RX_INACTIVE); ++ ++ return false; + } + + void ath_hw_check_work(struct work_struct *work) +@@ -58,8 +87,8 @@ void ath_hw_check_work(struct work_struc + struct ath_softc *sc = container_of(work, struct ath_softc, + hw_check_work.work); + +- if (!ath_hw_check(sc) || +- !ath_tx_complete_check(sc)) ++ if (!ath_hw_check(sc) || !ath_tx_complete_check(sc) || ++ !ath_hw_rx_inactive_check(sc)) + return; + + ieee80211_queue_delayed_work(sc->hw, &sc->hw_check_work, +--- a/drivers/net/wireless/ath/ath9k/main.c ++++ b/drivers/net/wireless/ath/ath9k/main.c +@@ -454,6 +454,7 @@ void ath9k_tasklet(struct tasklet_struct + ath_rx_tasklet(sc, 0, true); + + ath_rx_tasklet(sc, 0, false); ++ sc->rx_active_count++; + } + + if (status & ATH9K_INT_TX) { |