2012-06-04 20:23:37 +05:30
|
|
|
/*
|
|
|
|
* Copyright (c) 2012 Qualcomm Atheros, Inc.
|
|
|
|
*
|
|
|
|
* Permission to use, copy, modify, and/or distribute this software for any
|
|
|
|
* purpose with or without fee is hereby granted, provided that the above
|
|
|
|
* copyright notice and this permission notice appear in all copies.
|
|
|
|
*
|
|
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
|
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
|
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
|
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
|
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
|
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
|
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "ath9k.h"
|
|
|
|
|
|
|
|
/*
|
|
|
|
* TX polling - checks if the TX engine is stuck somewhere
|
|
|
|
* and issues a chip reset if so.
|
|
|
|
*/
|
2017-02-02 10:14:50 +01:00
|
|
|
static bool ath_tx_complete_check(struct ath_softc *sc)
|
2012-06-04 20:23:37 +05:30
|
|
|
{
|
|
|
|
struct ath_txq *txq;
|
|
|
|
int i;
|
2013-10-14 17:42:11 -07:00
|
|
|
|
2017-02-02 10:14:50 +01:00
|
|
|
if (sc->tx99_state)
|
|
|
|
return true;
|
2013-10-14 17:42:11 -07:00
|
|
|
|
2013-03-15 16:18:44 +01:00
|
|
|
for (i = 0; i < IEEE80211_NUM_ACS; i++) {
|
|
|
|
txq = sc->tx.txq_map[i];
|
|
|
|
|
|
|
|
ath_txq_lock(sc, txq);
|
|
|
|
if (txq->axq_depth) {
|
|
|
|
if (txq->axq_tx_inprogress) {
|
|
|
|
ath_txq_unlock(sc, txq);
|
2017-02-02 10:14:50 +01:00
|
|
|
goto reset;
|
2012-06-04 20:23:37 +05:30
|
|
|
}
|
2017-02-02 10:14:50 +01:00
|
|
|
|
|
|
|
txq->axq_tx_inprogress = true;
|
2012-06-04 20:23:37 +05:30
|
|
|
}
|
2013-08-26 11:47:22 +05:30
|
|
|
ath_txq_unlock(sc, txq);
|
2013-03-15 16:18:44 +01:00
|
|
|
}
|
2012-06-04 20:23:37 +05:30
|
|
|
|
2017-02-02 10:14:50 +01:00
|
|
|
return true;
|
|
|
|
|
|
|
|
reset:
|
|
|
|
ath_dbg(ath9k_hw_common(sc->sc_ah), RESET,
|
|
|
|
"tx hung, resetting the chip\n");
|
|
|
|
ath9k_queue_reset(sc, RESET_TYPE_TX_HANG);
|
|
|
|
return false;
|
wifi: 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>
Tested-by: Sven Eckelmann <se@simonwunderlich.de>
Reviewed-by: Sven Eckelmann <se@simonwunderlich.de>
Tested-by: Issam Hamdi <ih@simonwunderlich.de>
Acked-by: Simon Wunderlich <sw@simonwunderlich.de>
Link: https://patch.msgid.link/20241106-ath9k-deaf-detection-v1-1-736a150d2425@redhat.com
Signed-off-by: Jeff Johnson <quic_jjohnson@quicinc.com>
2024-11-06 13:41:44 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
#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;
|
2017-02-02 10:14:50 +01:00
|
|
|
|
wifi: 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>
Tested-by: Sven Eckelmann <se@simonwunderlich.de>
Reviewed-by: Sven Eckelmann <se@simonwunderlich.de>
Tested-by: Issam Hamdi <ih@simonwunderlich.de>
Acked-by: Simon Wunderlich <sw@simonwunderlich.de>
Link: https://patch.msgid.link/20241106-ath9k-deaf-detection-v1-1-736a150d2425@redhat.com
Signed-off-by: Jeff Johnson <quic_jjohnson@quicinc.com>
2024-11-06 13:41:44 +01:00
|
|
|
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;
|
2017-02-02 10:14:50 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void ath_hw_check_work(struct work_struct *work)
|
|
|
|
{
|
|
|
|
struct ath_softc *sc = container_of(work, struct ath_softc,
|
|
|
|
hw_check_work.work);
|
|
|
|
|
wifi: 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>
Tested-by: Sven Eckelmann <se@simonwunderlich.de>
Reviewed-by: Sven Eckelmann <se@simonwunderlich.de>
Tested-by: Issam Hamdi <ih@simonwunderlich.de>
Acked-by: Simon Wunderlich <sw@simonwunderlich.de>
Link: https://patch.msgid.link/20241106-ath9k-deaf-detection-v1-1-736a150d2425@redhat.com
Signed-off-by: Jeff Johnson <quic_jjohnson@quicinc.com>
2024-11-06 13:41:44 +01:00
|
|
|
if (!ath_hw_check(sc) || !ath_tx_complete_check(sc) ||
|
|
|
|
!ath_hw_rx_inactive_check(sc))
|
2012-06-04 20:23:43 +05:30
|
|
|
return;
|
2012-06-04 20:23:37 +05:30
|
|
|
|
2017-02-02 10:14:50 +01:00
|
|
|
ieee80211_queue_delayed_work(sc->hw, &sc->hw_check_work,
|
|
|
|
msecs_to_jiffies(ATH_HW_CHECK_POLL_INT));
|
2012-06-04 20:23:37 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Checks if the BB/MAC is hung.
|
|
|
|
*/
|
2013-12-24 10:44:25 +05:30
|
|
|
bool ath_hw_check(struct ath_softc *sc)
|
2012-06-04 20:23:37 +05:30
|
|
|
{
|
|
|
|
struct ath_common *common = ath9k_hw_common(sc->sc_ah);
|
2012-07-17 17:16:42 +05:30
|
|
|
enum ath_reset_type type;
|
2013-12-24 10:44:25 +05:30
|
|
|
bool is_alive;
|
2012-06-04 20:23:37 +05:30
|
|
|
|
|
|
|
ath9k_ps_wakeup(sc);
|
2013-12-24 10:44:25 +05:30
|
|
|
|
2012-06-04 20:23:37 +05:30
|
|
|
is_alive = ath9k_hw_check_alive(sc->sc_ah);
|
|
|
|
|
2013-12-24 10:44:25 +05:30
|
|
|
if (!is_alive) {
|
2012-06-04 20:23:37 +05:30
|
|
|
ath_dbg(common, RESET,
|
2013-12-24 10:44:25 +05:30
|
|
|
"HW hang detected, schedule chip reset\n");
|
2012-07-17 17:16:42 +05:30
|
|
|
type = RESET_TYPE_MAC_HANG;
|
2013-12-24 10:44:25 +05:30
|
|
|
ath9k_queue_reset(sc, type);
|
2012-06-04 20:23:37 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
ath9k_ps_restore(sc);
|
2013-12-24 10:44:25 +05:30
|
|
|
|
|
|
|
return is_alive;
|
2012-06-04 20:23:37 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2012-06-04 20:23:43 +05:30
|
|
|
* PLL-WAR for AR9485/AR9340
|
2012-06-04 20:23:37 +05:30
|
|
|
*/
|
2012-06-04 20:23:43 +05:30
|
|
|
static bool ath_hw_pll_rx_hang_check(struct ath_softc *sc, u32 pll_sqsum)
|
2012-06-04 20:23:37 +05:30
|
|
|
{
|
|
|
|
static int count;
|
|
|
|
struct ath_common *common = ath9k_hw_common(sc->sc_ah);
|
|
|
|
|
|
|
|
if (pll_sqsum >= 0x40000) {
|
|
|
|
count++;
|
|
|
|
if (count == 3) {
|
2012-06-04 20:23:43 +05:30
|
|
|
ath_dbg(common, RESET, "PLL WAR, resetting the chip\n");
|
2012-07-17 17:16:42 +05:30
|
|
|
ath9k_queue_reset(sc, RESET_TYPE_PLL_HANG);
|
2012-06-04 20:23:37 +05:30
|
|
|
count = 0;
|
2012-06-04 20:23:43 +05:30
|
|
|
return true;
|
2012-06-04 20:23:37 +05:30
|
|
|
}
|
2012-06-04 20:23:43 +05:30
|
|
|
} else {
|
2012-06-04 20:23:37 +05:30
|
|
|
count = 0;
|
2012-06-04 20:23:43 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
2012-06-04 20:23:37 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
void ath_hw_pll_work(struct work_struct *work)
|
|
|
|
{
|
2012-06-04 20:23:43 +05:30
|
|
|
u32 pll_sqsum;
|
2012-06-04 20:23:37 +05:30
|
|
|
struct ath_softc *sc = container_of(work, struct ath_softc,
|
|
|
|
hw_pll_work.work);
|
2014-02-27 11:40:46 +01:00
|
|
|
struct ath_common *common = ath9k_hw_common(sc->sc_ah);
|
2012-06-12 20:13:43 +05:30
|
|
|
/*
|
|
|
|
* ensure that the PLL WAR is executed only
|
|
|
|
* after the STA is associated (or) if the
|
|
|
|
* beaconing had started in interfaces that
|
|
|
|
* uses beacons.
|
|
|
|
*/
|
2014-02-27 11:40:46 +01:00
|
|
|
if (!test_bit(ATH_OP_BEACONS, &common->op_flags))
|
2012-06-12 20:13:43 +05:30
|
|
|
return;
|
2012-06-04 20:23:37 +05:30
|
|
|
|
2013-10-14 17:42:11 -07:00
|
|
|
if (sc->tx99_state)
|
|
|
|
return;
|
|
|
|
|
2012-06-04 20:23:43 +05:30
|
|
|
ath9k_ps_wakeup(sc);
|
|
|
|
pll_sqsum = ar9003_get_pll_sqsum_dvc(sc->sc_ah);
|
|
|
|
ath9k_ps_restore(sc);
|
|
|
|
if (ath_hw_pll_rx_hang_check(sc, pll_sqsum))
|
|
|
|
return;
|
|
|
|
|
|
|
|
ieee80211_queue_delayed_work(sc->hw, &sc->hw_pll_work,
|
|
|
|
msecs_to_jiffies(ATH_PLL_WORK_INTERVAL));
|
2012-06-04 20:23:37 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* PA Pre-distortion.
|
|
|
|
*/
|
|
|
|
static void ath_paprd_activate(struct ath_softc *sc)
|
|
|
|
{
|
|
|
|
struct ath_hw *ah = sc->sc_ah;
|
2012-12-10 07:22:33 +05:30
|
|
|
struct ath_common *common = ath9k_hw_common(ah);
|
2012-06-04 20:23:37 +05:30
|
|
|
struct ath9k_hw_cal_data *caldata = ah->caldata;
|
|
|
|
int chain;
|
|
|
|
|
2013-09-11 16:36:31 +05:30
|
|
|
if (!caldata || !test_bit(PAPRD_DONE, &caldata->cal_flags)) {
|
2012-12-10 07:22:33 +05:30
|
|
|
ath_dbg(common, CALIBRATE, "Failed to activate PAPRD\n");
|
2012-06-04 20:23:37 +05:30
|
|
|
return;
|
2012-12-10 07:22:33 +05:30
|
|
|
}
|
2012-06-04 20:23:37 +05:30
|
|
|
|
|
|
|
ar9003_paprd_enable(ah, false);
|
|
|
|
for (chain = 0; chain < AR9300_MAX_CHAINS; chain++) {
|
|
|
|
if (!(ah->txchainmask & BIT(chain)))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
ar9003_paprd_populate_single_table(ah, caldata, chain);
|
|
|
|
}
|
|
|
|
|
2012-12-10 07:22:33 +05:30
|
|
|
ath_dbg(common, CALIBRATE, "Activating PAPRD\n");
|
2012-06-04 20:23:37 +05:30
|
|
|
ar9003_paprd_enable(ah, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool ath_paprd_send_frame(struct ath_softc *sc, struct sk_buff *skb, int chain)
|
|
|
|
{
|
|
|
|
struct ieee80211_hw *hw = sc->hw;
|
|
|
|
struct ieee80211_tx_info *tx_info = IEEE80211_SKB_CB(skb);
|
|
|
|
struct ath_hw *ah = sc->sc_ah;
|
|
|
|
struct ath_common *common = ath9k_hw_common(ah);
|
|
|
|
struct ath_tx_control txctl;
|
2015-05-14 18:56:16 +02:00
|
|
|
unsigned long time_left;
|
2012-06-04 20:23:37 +05:30
|
|
|
|
|
|
|
memset(&txctl, 0, sizeof(txctl));
|
2012-11-21 18:13:10 +05:30
|
|
|
txctl.txq = sc->tx.txq_map[IEEE80211_AC_BE];
|
2012-06-04 20:23:37 +05:30
|
|
|
|
|
|
|
memset(tx_info, 0, sizeof(*tx_info));
|
2014-06-11 16:17:52 +05:30
|
|
|
tx_info->band = sc->cur_chandef.chan->band;
|
2012-06-04 20:23:37 +05:30
|
|
|
tx_info->flags |= IEEE80211_TX_CTL_NO_ACK;
|
|
|
|
tx_info->control.rates[0].idx = 0;
|
|
|
|
tx_info->control.rates[0].count = 1;
|
|
|
|
tx_info->control.rates[0].flags = IEEE80211_TX_RC_MCS;
|
|
|
|
tx_info->control.rates[1].idx = -1;
|
|
|
|
|
|
|
|
init_completion(&sc->paprd_complete);
|
|
|
|
txctl.paprd = BIT(chain);
|
|
|
|
|
|
|
|
if (ath_tx_start(hw, skb, &txctl) != 0) {
|
|
|
|
ath_dbg(common, CALIBRATE, "PAPRD TX failed\n");
|
|
|
|
dev_kfree_skb_any(skb);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
time_left = wait_for_completion_timeout(&sc->paprd_complete,
|
|
|
|
msecs_to_jiffies(ATH_PAPRD_TIMEOUT));
|
|
|
|
|
|
|
|
if (!time_left)
|
|
|
|
ath_dbg(common, CALIBRATE,
|
|
|
|
"Timeout waiting for paprd training on TX chain %d\n",
|
|
|
|
chain);
|
|
|
|
|
|
|
|
return !!time_left;
|
|
|
|
}
|
|
|
|
|
|
|
|
void ath_paprd_calibrate(struct work_struct *work)
|
|
|
|
{
|
|
|
|
struct ath_softc *sc = container_of(work, struct ath_softc, paprd_work);
|
|
|
|
struct ieee80211_hw *hw = sc->hw;
|
|
|
|
struct ath_hw *ah = sc->sc_ah;
|
|
|
|
struct ieee80211_hdr *hdr;
|
|
|
|
struct sk_buff *skb = NULL;
|
|
|
|
struct ath9k_hw_cal_data *caldata = ah->caldata;
|
|
|
|
struct ath_common *common = ath9k_hw_common(ah);
|
|
|
|
int ftype;
|
|
|
|
int chain_ok = 0;
|
|
|
|
int chain;
|
|
|
|
int len = 1800;
|
2012-08-27 17:00:05 +02:00
|
|
|
int ret;
|
2012-06-04 20:23:37 +05:30
|
|
|
|
2013-09-11 16:36:31 +05:30
|
|
|
if (!caldata ||
|
|
|
|
!test_bit(PAPRD_PACKET_SENT, &caldata->cal_flags) ||
|
|
|
|
test_bit(PAPRD_DONE, &caldata->cal_flags)) {
|
2012-12-10 07:22:33 +05:30
|
|
|
ath_dbg(common, CALIBRATE, "Skipping PAPRD calibration\n");
|
2012-06-04 20:23:37 +05:30
|
|
|
return;
|
2012-12-10 07:22:33 +05:30
|
|
|
}
|
2012-06-04 20:23:37 +05:30
|
|
|
|
|
|
|
ath9k_ps_wakeup(sc);
|
|
|
|
|
|
|
|
if (ar9003_paprd_init_table(ah) < 0)
|
|
|
|
goto fail_paprd;
|
|
|
|
|
|
|
|
skb = alloc_skb(len, GFP_KERNEL);
|
|
|
|
if (!skb)
|
|
|
|
goto fail_paprd;
|
|
|
|
|
|
|
|
skb_put(skb, len);
|
|
|
|
memset(skb->data, 0, len);
|
|
|
|
hdr = (struct ieee80211_hdr *)skb->data;
|
|
|
|
ftype = IEEE80211_FTYPE_DATA | IEEE80211_STYPE_NULLFUNC;
|
|
|
|
hdr->frame_control = cpu_to_le16(ftype);
|
|
|
|
hdr->duration_id = cpu_to_le16(10);
|
|
|
|
memcpy(hdr->addr1, hw->wiphy->perm_addr, ETH_ALEN);
|
|
|
|
memcpy(hdr->addr2, hw->wiphy->perm_addr, ETH_ALEN);
|
|
|
|
memcpy(hdr->addr3, hw->wiphy->perm_addr, ETH_ALEN);
|
|
|
|
|
|
|
|
for (chain = 0; chain < AR9300_MAX_CHAINS; chain++) {
|
|
|
|
if (!(ah->txchainmask & BIT(chain)))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
chain_ok = 0;
|
|
|
|
ar9003_paprd_setup_gain_table(ah, chain);
|
|
|
|
|
|
|
|
ath_dbg(common, CALIBRATE,
|
|
|
|
"Sending PAPRD training frame on chain %d\n", chain);
|
|
|
|
if (!ath_paprd_send_frame(sc, skb, chain))
|
|
|
|
goto fail_paprd;
|
|
|
|
|
|
|
|
if (!ar9003_paprd_is_done(ah)) {
|
|
|
|
ath_dbg(common, CALIBRATE,
|
|
|
|
"PAPRD not yet done on chain %d\n", chain);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2012-08-27 17:00:05 +02:00
|
|
|
ret = ar9003_paprd_create_curve(ah, caldata, chain);
|
|
|
|
if (ret == -EINPROGRESS) {
|
|
|
|
ath_dbg(common, CALIBRATE,
|
|
|
|
"PAPRD curve on chain %d needs to be re-trained\n",
|
|
|
|
chain);
|
|
|
|
break;
|
|
|
|
} else if (ret) {
|
2012-06-04 20:23:37 +05:30
|
|
|
ath_dbg(common, CALIBRATE,
|
|
|
|
"PAPRD create curve failed on chain %d\n",
|
2012-06-04 20:23:43 +05:30
|
|
|
chain);
|
2012-06-04 20:23:37 +05:30
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
chain_ok = 1;
|
|
|
|
}
|
|
|
|
kfree_skb(skb);
|
|
|
|
|
|
|
|
if (chain_ok) {
|
2013-09-11 16:36:31 +05:30
|
|
|
set_bit(PAPRD_DONE, &caldata->cal_flags);
|
2012-06-04 20:23:37 +05:30
|
|
|
ath_paprd_activate(sc);
|
|
|
|
}
|
|
|
|
|
|
|
|
fail_paprd:
|
|
|
|
ath9k_ps_restore(sc);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* ANI performs periodic noise floor calibration
|
|
|
|
* that is used to adjust and optimize the chip performance. This
|
|
|
|
* takes environmental changes (location, temperature) into account.
|
|
|
|
* When the task is complete, it reschedules itself depending on the
|
|
|
|
* appropriate interval that was calculated.
|
|
|
|
*/
|
2017-10-24 02:29:54 -07:00
|
|
|
void ath_ani_calibrate(struct timer_list *t)
|
2012-06-04 20:23:37 +05:30
|
|
|
{
|
2025-05-09 07:51:14 +02:00
|
|
|
struct ath_common *common = timer_container_of(common, t, ani.timer);
|
2023-11-22 20:31:03 +02:00
|
|
|
struct ath_softc *sc = common->priv;
|
2012-06-04 20:23:37 +05:30
|
|
|
struct ath_hw *ah = sc->sc_ah;
|
|
|
|
bool longcal = false;
|
|
|
|
bool shortcal = false;
|
|
|
|
bool aniflag = false;
|
|
|
|
unsigned int timestamp = jiffies_to_msecs(jiffies);
|
|
|
|
u32 cal_interval, short_cal_interval, long_cal_interval;
|
|
|
|
unsigned long flags;
|
|
|
|
|
2013-09-11 16:36:31 +05:30
|
|
|
if (ah->caldata && test_bit(NFCAL_INTF, &ah->caldata->cal_flags))
|
2012-06-04 20:23:37 +05:30
|
|
|
long_cal_interval = ATH_LONG_CALINTERVAL_INT;
|
|
|
|
else
|
|
|
|
long_cal_interval = ATH_LONG_CALINTERVAL;
|
|
|
|
|
|
|
|
short_cal_interval = (ah->opmode == NL80211_IFTYPE_AP) ?
|
|
|
|
ATH_AP_SHORT_CALINTERVAL : ATH_STA_SHORT_CALINTERVAL;
|
|
|
|
|
|
|
|
/* Only calibrate if awake */
|
2012-10-10 23:03:02 +05:30
|
|
|
if (sc->sc_ah->power_mode != ATH9K_PM_AWAKE) {
|
|
|
|
if (++ah->ani_skip_count >= ATH_ANI_MAX_SKIP_COUNT) {
|
|
|
|
spin_lock_irqsave(&sc->sc_pm_lock, flags);
|
|
|
|
sc->ps_flags |= PS_WAIT_FOR_ANI;
|
|
|
|
spin_unlock_irqrestore(&sc->sc_pm_lock, flags);
|
|
|
|
}
|
2012-06-04 20:23:37 +05:30
|
|
|
goto set_timer;
|
2012-10-10 23:03:02 +05:30
|
|
|
}
|
|
|
|
ah->ani_skip_count = 0;
|
|
|
|
spin_lock_irqsave(&sc->sc_pm_lock, flags);
|
|
|
|
sc->ps_flags &= ~PS_WAIT_FOR_ANI;
|
|
|
|
spin_unlock_irqrestore(&sc->sc_pm_lock, flags);
|
2012-06-04 20:23:37 +05:30
|
|
|
|
|
|
|
ath9k_ps_wakeup(sc);
|
|
|
|
|
|
|
|
/* Long calibration runs independently of short calibration. */
|
|
|
|
if ((timestamp - common->ani.longcal_timer) >= long_cal_interval) {
|
|
|
|
longcal = true;
|
|
|
|
common->ani.longcal_timer = timestamp;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Short calibration applies only while caldone is false */
|
|
|
|
if (!common->ani.caldone) {
|
|
|
|
if ((timestamp - common->ani.shortcal_timer) >= short_cal_interval) {
|
|
|
|
shortcal = true;
|
|
|
|
common->ani.shortcal_timer = timestamp;
|
|
|
|
common->ani.resetcal_timer = timestamp;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if ((timestamp - common->ani.resetcal_timer) >=
|
|
|
|
ATH_RESTART_CALINTERVAL) {
|
|
|
|
common->ani.caldone = ath9k_hw_reset_calvalid(ah);
|
|
|
|
if (common->ani.caldone)
|
|
|
|
common->ani.resetcal_timer = timestamp;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Verify whether we must check ANI */
|
2013-06-03 09:19:26 +05:30
|
|
|
if ((timestamp - common->ani.checkani_timer) >= ah->config.ani_poll_interval) {
|
2012-06-04 20:23:37 +05:30
|
|
|
aniflag = true;
|
|
|
|
common->ani.checkani_timer = timestamp;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Call ANI routine if necessary */
|
|
|
|
if (aniflag) {
|
2017-09-18 22:59:19 +03:00
|
|
|
spin_lock_irqsave(&common->cc_lock, flags);
|
2012-06-04 20:23:37 +05:30
|
|
|
ath9k_hw_ani_monitor(ah, ah->curchan);
|
|
|
|
ath_update_survey_stats(sc);
|
2017-09-18 22:59:19 +03:00
|
|
|
spin_unlock_irqrestore(&common->cc_lock, flags);
|
2012-06-04 20:23:37 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
/* Perform calibration if necessary */
|
|
|
|
if (longcal || shortcal) {
|
2014-10-25 17:19:30 +02:00
|
|
|
int ret = ath9k_hw_calibrate(ah, ah->curchan, ah->rxchainmask,
|
|
|
|
longcal);
|
|
|
|
if (ret < 0) {
|
|
|
|
common->ani.caldone = 0;
|
|
|
|
ath9k_queue_reset(sc, RESET_TYPE_CALIBRATION);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
common->ani.caldone = ret;
|
2012-06-04 20:23:37 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
ath_dbg(common, ANI,
|
|
|
|
"Calibration @%lu finished: %s %s %s, caldone: %s\n",
|
|
|
|
jiffies,
|
|
|
|
longcal ? "long" : "", shortcal ? "short" : "",
|
|
|
|
aniflag ? "ani" : "", common->ani.caldone ? "true" : "false");
|
|
|
|
|
|
|
|
ath9k_ps_restore(sc);
|
|
|
|
|
|
|
|
set_timer:
|
|
|
|
/*
|
|
|
|
* Set timer interval based on previous results.
|
|
|
|
* The interval must be the shortest necessary to satisfy ANI,
|
|
|
|
* short calibration and long calibration.
|
|
|
|
*/
|
|
|
|
cal_interval = ATH_LONG_CALINTERVAL;
|
2013-06-03 09:19:26 +05:30
|
|
|
cal_interval = min(cal_interval, (u32)ah->config.ani_poll_interval);
|
2012-06-04 20:23:37 +05:30
|
|
|
if (!common->ani.caldone)
|
|
|
|
cal_interval = min(cal_interval, (u32)short_cal_interval);
|
|
|
|
|
|
|
|
mod_timer(&common->ani.timer, jiffies + msecs_to_jiffies(cal_interval));
|
2012-12-10 07:22:34 +05:30
|
|
|
|
2012-12-10 07:22:37 +05:30
|
|
|
if (ar9003_is_paprd_enabled(ah) && ah->caldata) {
|
2013-09-11 16:36:31 +05:30
|
|
|
if (!test_bit(PAPRD_DONE, &ah->caldata->cal_flags)) {
|
2012-06-04 20:23:37 +05:30
|
|
|
ieee80211_queue_work(sc->hw, &sc->paprd_work);
|
2012-12-10 07:22:34 +05:30
|
|
|
} else if (!ah->paprd_table_write_done) {
|
|
|
|
ath9k_ps_wakeup(sc);
|
2012-06-04 20:23:37 +05:30
|
|
|
ath_paprd_activate(sc);
|
2012-12-10 07:22:34 +05:30
|
|
|
ath9k_ps_restore(sc);
|
|
|
|
}
|
2012-06-04 20:23:37 +05:30
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-07-17 17:16:29 +05:30
|
|
|
void ath_start_ani(struct ath_softc *sc)
|
2012-06-04 20:23:37 +05:30
|
|
|
{
|
2012-07-17 17:16:29 +05:30
|
|
|
struct ath_hw *ah = sc->sc_ah;
|
|
|
|
struct ath_common *common = ath9k_hw_common(ah);
|
2012-06-04 20:23:37 +05:30
|
|
|
unsigned long timestamp = jiffies_to_msecs(jiffies);
|
|
|
|
|
2012-07-17 17:16:29 +05:30
|
|
|
if (common->disable_ani ||
|
2014-02-27 11:40:46 +01:00
|
|
|
!test_bit(ATH_OP_ANI_RUN, &common->op_flags) ||
|
2014-06-11 16:17:49 +05:30
|
|
|
sc->cur_chan->offchannel)
|
2012-06-04 20:23:37 +05:30
|
|
|
return;
|
|
|
|
|
|
|
|
common->ani.longcal_timer = timestamp;
|
|
|
|
common->ani.shortcal_timer = timestamp;
|
|
|
|
common->ani.checkani_timer = timestamp;
|
|
|
|
|
2012-07-17 17:16:29 +05:30
|
|
|
ath_dbg(common, ANI, "Starting ANI\n");
|
2012-06-04 20:23:37 +05:30
|
|
|
mod_timer(&common->ani.timer,
|
|
|
|
jiffies + msecs_to_jiffies((u32)ah->config.ani_poll_interval));
|
|
|
|
}
|
|
|
|
|
2012-07-17 17:16:29 +05:30
|
|
|
void ath_stop_ani(struct ath_softc *sc)
|
|
|
|
{
|
|
|
|
struct ath_common *common = ath9k_hw_common(sc->sc_ah);
|
|
|
|
|
|
|
|
ath_dbg(common, ANI, "Stopping ANI\n");
|
2025-04-05 10:17:26 +02:00
|
|
|
timer_delete_sync(&common->ani.timer);
|
2012-07-17 17:16:29 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
void ath_check_ani(struct ath_softc *sc)
|
|
|
|
{
|
|
|
|
struct ath_hw *ah = sc->sc_ah;
|
2014-02-27 11:40:46 +01:00
|
|
|
struct ath_common *common = ath9k_hw_common(sc->sc_ah);
|
2014-06-11 16:18:02 +05:30
|
|
|
struct ath_beacon_config *cur_conf = &sc->cur_chan->beacon;
|
2012-07-17 17:16:29 +05:30
|
|
|
|
|
|
|
/*
|
|
|
|
* Check for the various conditions in which ANI has to
|
|
|
|
* be stopped.
|
|
|
|
*/
|
|
|
|
if (ah->opmode == NL80211_IFTYPE_ADHOC) {
|
|
|
|
if (!cur_conf->enable_beacon)
|
|
|
|
goto stop_ani;
|
|
|
|
} else if (ah->opmode == NL80211_IFTYPE_AP) {
|
|
|
|
if (!cur_conf->enable_beacon) {
|
|
|
|
/*
|
|
|
|
* Disable ANI only when there are no
|
|
|
|
* associated stations.
|
|
|
|
*/
|
2014-02-27 11:40:46 +01:00
|
|
|
if (!test_bit(ATH_OP_PRIM_STA_VIF, &common->op_flags))
|
2012-07-17 17:16:29 +05:30
|
|
|
goto stop_ani;
|
|
|
|
}
|
|
|
|
} else if (ah->opmode == NL80211_IFTYPE_STATION) {
|
2014-02-27 11:40:46 +01:00
|
|
|
if (!test_bit(ATH_OP_PRIM_STA_VIF, &common->op_flags))
|
2012-07-17 17:16:29 +05:30
|
|
|
goto stop_ani;
|
|
|
|
}
|
|
|
|
|
2014-02-27 11:40:46 +01:00
|
|
|
if (!test_bit(ATH_OP_ANI_RUN, &common->op_flags)) {
|
|
|
|
set_bit(ATH_OP_ANI_RUN, &common->op_flags);
|
2012-07-17 17:16:29 +05:30
|
|
|
ath_start_ani(sc);
|
|
|
|
}
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
stop_ani:
|
2014-02-27 11:40:46 +01:00
|
|
|
clear_bit(ATH_OP_ANI_RUN, &common->op_flags);
|
2012-07-17 17:16:29 +05:30
|
|
|
ath_stop_ani(sc);
|
|
|
|
}
|
|
|
|
|
2012-06-04 20:23:37 +05:30
|
|
|
void ath_update_survey_nf(struct ath_softc *sc, int channel)
|
|
|
|
{
|
|
|
|
struct ath_hw *ah = sc->sc_ah;
|
|
|
|
struct ath9k_channel *chan = &ah->channels[channel];
|
|
|
|
struct survey_info *survey = &sc->survey[channel];
|
|
|
|
|
|
|
|
if (chan->noisefloor) {
|
|
|
|
survey->filled |= SURVEY_INFO_NOISE_DBM;
|
2013-10-11 14:09:54 +02:00
|
|
|
survey->noise = ath9k_hw_getchan_noise(ah, chan,
|
|
|
|
chan->noisefloor);
|
2012-06-04 20:23:37 +05:30
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Updates the survey statistics and returns the busy time since last
|
|
|
|
* update in %, if the measurement duration was long enough for the
|
|
|
|
* result to be useful, -1 otherwise.
|
|
|
|
*/
|
|
|
|
int ath_update_survey_stats(struct ath_softc *sc)
|
|
|
|
{
|
|
|
|
struct ath_hw *ah = sc->sc_ah;
|
|
|
|
struct ath_common *common = ath9k_hw_common(ah);
|
|
|
|
int pos = ah->curchan - &ah->channels[0];
|
|
|
|
struct survey_info *survey = &sc->survey[pos];
|
|
|
|
struct ath_cycle_counters *cc = &common->cc_survey;
|
|
|
|
unsigned int div = common->clockrate * 1000;
|
|
|
|
int ret = 0;
|
|
|
|
|
|
|
|
if (!ah->curchan)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
if (ah->power_mode == ATH9K_PM_AWAKE)
|
|
|
|
ath_hw_cycle_counters_update(common);
|
|
|
|
|
|
|
|
if (cc->cycles > 0) {
|
2014-11-14 16:35:34 +01:00
|
|
|
survey->filled |= SURVEY_INFO_TIME |
|
|
|
|
SURVEY_INFO_TIME_BUSY |
|
|
|
|
SURVEY_INFO_TIME_RX |
|
|
|
|
SURVEY_INFO_TIME_TX;
|
|
|
|
survey->time += cc->cycles / div;
|
|
|
|
survey->time_busy += cc->rx_busy / div;
|
|
|
|
survey->time_rx += cc->rx_frame / div;
|
|
|
|
survey->time_tx += cc->tx_frame / div;
|
2012-06-04 20:23:37 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
if (cc->cycles < div)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
if (cc->cycles > 0)
|
|
|
|
ret = cc->rx_busy * 100 / cc->cycles;
|
|
|
|
|
|
|
|
memset(cc, 0, sizeof(*cc));
|
|
|
|
|
|
|
|
ath_update_survey_nf(sc, pos);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|