And recover chip from the rx stuck.
This is a workaround from Atheros for a receive hang in their part.
It uses various status monitoring to detect that the receiver has
failed, and resets the chip. There is activity still afoot to find
the root cause to this problem, and this change is only meant to
alleviate the immediate problem, since throughput still suffers
before the part enters this state.
Signed-off-by: Paul Stewart <pstew@chromium.org>
BUG=chrome-os-partner:4886
TEST=Manual: Bench-top re-enactment of congested environment + in-field
testing with client machines
Change-Id: I7acfe51ca56195cf8f201446580dee9ae9804bac
Reviewed-on: https://gerrit.chromium.org/gerrit/15861
Reviewed-by: Gary Morain <gmorain@chromium.org>
Tested-by: Paul Stewart <pstew@chromium.org>
Commit-Ready: Paul Stewart <pstew@chromium.org>
spinlock_t cc_lock;
struct ath_cycle_counters cc_ani;
struct ath_cycle_counters cc_survey;
+ struct ath_cycle_counters cc_rxpoll;
struct ath_regulatory regulatory;
struct ath_regulatory reg_world_copy;
{ 2, 2, 1 }, /* lvl 2 */
{ 3, 2, 1 }, /* lvl 3 (default) */
{ 4, 3, 1 }, /* lvl 4 */
- { 5, 4, 1 }, /* lvl 5 */
- { 6, 5, 1 }, /* lvl 6 */
- { 7, 6, 1 }, /* lvl 7 */
- { 7, 7, 1 }, /* lvl 8 */
- { 7, 8, 0 } /* lvl 9 */
+ { 4, 3, 0 }, /* lvl 5 */
+ { 5, 4, 0 }, /* lvl 6 */
+ { 6, 5, 0 }, /* lvl 7 */
+ { 7, 6, 0 }, /* lvl 8 */
+ { 7, 7, 0 }, /* lvl 9 */
};
#define ATH9K_ANI_OFDM_NUM_LEVEL \
ARRAY_SIZE(ofdm_level_table)
ATH9K_ANI_FIRSTEP_LEVEL,
entry_ofdm->fir_step_level);
- if ((ah->opmode != NL80211_IFTYPE_STATION &&
- ah->opmode != NL80211_IFTYPE_ADHOC) ||
- aniState->noiseFloor <= aniState->rssiThrHigh) {
- if (aniState->ofdmWeakSigDetectOff)
- /* force on ofdm weak sig detect */
- ath9k_hw_ani_control(ah,
- ATH9K_ANI_OFDM_WEAK_SIGNAL_DETECTION,
- true);
- else if (aniState->ofdmWeakSigDetectOff ==
- entry_ofdm->ofdm_weak_signal_on)
- ath9k_hw_ani_control(ah,
+ if ((aniState->noiseFloor >= aniState->rssiThrHigh) &&
+ (!aniState->ofdmWeakSigDetectOff !=
+ entry_ofdm->ofdm_weak_signal_on))
+ ath9k_hw_ani_control(ah,
ATH9K_ANI_OFDM_WEAK_SIGNAL_DETECTION,
entry_ofdm->ofdm_weak_signal_on);
- }
}
static void ath9k_hw_ani_ofdm_err_trigger(struct ath_hw *ah)
}
aniState->listenTime += listenTime;
+ aniState->totallistenTime += listenTime;
ath9k_hw_update_mibstats(ah, &ah->ah_mibStats);
aniState->listenTime;
ath_dbg(common, ANI,
- "listenTime=%d OFDM:%d errs=%d/s CCK:%d errs=%d/s ofdm_turn=%d\n",
+ "listenTime=%d totallistenTime= %d OFDM:%d errs=%d/s "
+ "CCK:%d errs=%d/s ofdm_turn=%d\n",
aniState->listenTime,
+ aniState->totallistenTime,
aniState->ofdmNoiseImmunityLevel,
ofdmPhyErrRate, aniState->cckNoiseImmunityLevel,
cckPhyErrRate, aniState->ofdmsTurn);
- if (aniState->listenTime > 5 * ah->aniperiod) {
+ if ((aniState->totallistenTime > 5 * ah->aniperiod) &&
+ (aniState->totallistenTime != aniState->listenTime)) {
if (ofdmPhyErrRate <= ah->config.ofdm_trig_low &&
cckPhyErrRate <= ah->config.cck_trig_low) {
ath9k_hw_ani_lower_immunity(ah);
aniState->ofdmsTurn = !aniState->ofdmsTurn;
}
+ aniState->totallistenTime = 0;
ath9k_ani_restart(ah);
} else if (aniState->listenTime > ah->aniperiod) {
/* check to see if need to raise immunity */
u8 cckWeakSigThreshold;
bool update_ani;
u32 listenTime;
+ u32 totallistenTime;
int32_t rssiThrLow;
int32_t rssiThrHigh;
u32 noiseFloor;
* on == 0 means more noise imm
*/
u32 on = param ? 1 : 0;
- /*
- * make register setting for default
- * (weak sig detect ON) come from INI file
- */
- int m1ThreshLow = on ?
- aniState->iniDef.m1ThreshLow : m1ThreshLow_off;
- int m2ThreshLow = on ?
- aniState->iniDef.m2ThreshLow : m2ThreshLow_off;
- int m1Thresh = on ?
- aniState->iniDef.m1Thresh : m1Thresh_off;
- int m2Thresh = on ?
- aniState->iniDef.m2Thresh : m2Thresh_off;
- int m2CountThr = on ?
- aniState->iniDef.m2CountThr : m2CountThr_off;
- int m2CountThrLow = on ?
- aniState->iniDef.m2CountThrLow : m2CountThrLow_off;
- int m1ThreshLowExt = on ?
- aniState->iniDef.m1ThreshLowExt : m1ThreshLowExt_off;
- int m2ThreshLowExt = on ?
- aniState->iniDef.m2ThreshLowExt : m2ThreshLowExt_off;
- int m1ThreshExt = on ?
- aniState->iniDef.m1ThreshExt : m1ThreshExt_off;
- int m2ThreshExt = on ?
- aniState->iniDef.m2ThreshExt : m2ThreshExt_off;
-
- REG_RMW_FIELD(ah, AR_PHY_SFCORR_LOW,
- AR_PHY_SFCORR_LOW_M1_THRESH_LOW,
- m1ThreshLow);
- REG_RMW_FIELD(ah, AR_PHY_SFCORR_LOW,
- AR_PHY_SFCORR_LOW_M2_THRESH_LOW,
- m2ThreshLow);
- REG_RMW_FIELD(ah, AR_PHY_SFCORR,
- AR_PHY_SFCORR_M1_THRESH, m1Thresh);
- REG_RMW_FIELD(ah, AR_PHY_SFCORR,
- AR_PHY_SFCORR_M2_THRESH, m2Thresh);
- REG_RMW_FIELD(ah, AR_PHY_SFCORR,
- AR_PHY_SFCORR_M2COUNT_THR, m2CountThr);
- REG_RMW_FIELD(ah, AR_PHY_SFCORR_LOW,
- AR_PHY_SFCORR_LOW_M2COUNT_THR_LOW,
- m2CountThrLow);
-
- REG_RMW_FIELD(ah, AR_PHY_SFCORR_EXT,
- AR_PHY_SFCORR_EXT_M1_THRESH_LOW, m1ThreshLowExt);
- REG_RMW_FIELD(ah, AR_PHY_SFCORR_EXT,
- AR_PHY_SFCORR_EXT_M2_THRESH_LOW, m2ThreshLowExt);
- REG_RMW_FIELD(ah, AR_PHY_SFCORR_EXT,
- AR_PHY_SFCORR_EXT_M1_THRESH, m1ThreshExt);
- REG_RMW_FIELD(ah, AR_PHY_SFCORR_EXT,
- AR_PHY_SFCORR_EXT_M2_THRESH, m2ThreshExt);
-
if (on)
REG_SET_BIT(ah, AR_PHY_SFCORR_LOW,
AR_PHY_SFCORR_LOW_USE_SELF_CORR_LOW);
void ath_reset_work(struct work_struct *work);
void ath_hw_check(struct work_struct *work);
void ath_hw_pll_work(struct work_struct *work);
+void ath_rx_poll_work(unsigned long data);
void ath_paprd_calibrate(struct work_struct *work);
void ath_ani_calibrate(unsigned long data);
void ath_start_ani(struct ath_common *common);
struct ath_beacon_config cur_beacon_conf;
struct delayed_work tx_complete_work;
struct delayed_work hw_pll_work;
+ struct timer_list rx_poll_timer;
#ifdef CONFIG_ATH9K_BTCOEX_SUPPORT
struct ath_btcoex btcoex;
struct ath_ant_comb ant_comb;
u8 ant_tx, ant_rx;
+ atomic_t stop_rx_poll;
};
void ath9k_tasklet(unsigned long data);
void ar9003_hw_bb_watchdog_read(struct ath_hw *ah);
void ar9003_hw_bb_watchdog_dbg_info(struct ath_hw *ah);
void ar9003_hw_disable_phy_restart(struct ath_hw *ah);
+void ar9003_hw_dump_ani_reg(struct ath_hw *ah);
void ar9003_paprd_enable(struct ath_hw *ah, bool val);
void ar9003_paprd_populate_single_table(struct ath_hw *ah,
struct ath9k_hw_cal_data *caldata,
void ar9003_hw_attach_ops(struct ath_hw *ah);
void ar9002_hw_load_ani_reg(struct ath_hw *ah, struct ath9k_channel *chan);
+void ar9003_hw_dump_txdesc(struct ath_hw *ah);
+bool ath9k_hw_detect_mac_hang(struct ath_hw *ah);
/*
* ANI work can be shared between all families but a next
* generation implementation of ANI will be used only for AR9003 only
INIT_WORK(&sc->hw_check_work, ath_hw_check);
INIT_WORK(&sc->paprd_work, ath_paprd_calibrate);
INIT_DELAYED_WORK(&sc->hw_pll_work, ath_hw_pll_work);
+ setup_timer(&sc->rx_poll_timer, ath_rx_poll_work, (unsigned long)sc);
/* Register with mac80211 */
error = ieee80211_register_hw(hw);
static void __ath_cancel_work(struct ath_softc *sc)
{
+ struct ath_hw *ah = sc->sc_ah;
+ struct ath_common *common = ath9k_hw_common(ah);
+
+ if (sc->sc_flags & SC_OP_INVALID)
+ return -EIO;
+
+ sc->hw_busy_count = 0;
+
+ del_timer_sync(&common->ani.timer);
+ del_timer_sync(&sc->rx_poll_timer);
cancel_work_sync(&sc->paprd_work);
cancel_work_sync(&sc->hw_check_work);
+ cancel_work_sync(&sc->hw_reset_work);
cancel_delayed_work_sync(&sc->tx_complete_work);
cancel_delayed_work_sync(&sc->hw_pll_work);
}
static void ath_cancel_work(struct ath_softc *sc)
{
__ath_cancel_work(sc);
- cancel_work_sync(&sc->hw_reset_work);
}
static bool ath_prepare_reset(struct ath_softc *sc, bool retry_tx, bool flush)
if (sc->sc_flags & SC_OP_BEACONS)
ath_set_beacon(sc);
+ if (sc->sc_flags & SC_OP_PRIM_STA_VIF)
+ mod_timer(&sc->rx_poll_timer,
+ jiffies + msecs_to_jiffies(10));
ieee80211_queue_delayed_work(sc->hw, &sc->tx_complete_work, 0);
ieee80211_queue_delayed_work(sc->hw, &sc->hw_pll_work, HZ/2);
if (!common->disable_ani)
ath_dbg(common, RX_STUCK, "IMR %08x IER %08x intr_cnt %d\n",
REG_READ(ah, AR_IMR), REG_READ(ah, AR_IER),
atomic_read(&ah->intr_ref_cnt));
+
ar9003_hw_dump_txdesc(ah);
REG_SET_BIT(ah, AR_DIAG_SW, 0x8080000);
{
int r;
+ sc->hw_busy_count = 0;
+
+ ath9k_debug_samp_bb_mac(sc);
+ /* Stop ANI */
+ del_timer_sync(&common->ani.timer);
+ del_timer_sync(&sc->rx_poll_timer);
+
ath9k_ps_wakeup(sc);
- r = ath_reset_internal(sc, NULL, retry_tx);
+ ieee80211_stop_queues(hw);
+
+ ath9k_hw_disable_interrupts(ah);
+ ath_drain_all_txq(sc, retry_tx);
+
+ ath_stoprecv(sc);
+ ath_flushrecv(sc);
+
+ r = ath9k_hw_reset(ah, sc->sc_ah->curchan, ah->caldata, false);
+ if (r)
+ ath_err(common,
+ "Unable to reset hardware; reset status %d\n", r);
+
+ if (ath_startrecv(sc) != 0)
+ ath_err(common, "Unable to start recv logic\n");
+
+ /*
+ * We may be doing a reset in response to a request
+ * that changes the channel so update any state that
+ * might change as a result.
+ */
+ ath9k_cmn_update_txpow(ah, sc->curtxpow,
+ sc->config.txpowlimit, &sc->curtxpow);
+
+ if ((sc->sc_flags & SC_OP_BEACONS) || !(sc->sc_flags & (SC_OP_OFFCHANNEL)))
+ ath_set_beacon(sc); /* restart beacons */
+
+ if (sc->sc_flags & SC_OP_PRIM_STA_VIF)
+ mod_timer(&sc->rx_poll_timer, jiffies + msecs_to_jiffies(300));
+
+ ath9k_hw_set_interrupts(ah, ah->imask);
+ ath9k_hw_enable_interrupts(ah);
if (retry_tx) {
int i;
if (!common->disable_ani) {
sc->sc_flags |= SC_OP_ANI_RUN;
ath_start_ani(common);
+ atomic_set(&sc->stop_rx_poll, 0);
+ mod_timer(&sc->rx_poll_timer,
+ jiffies + msecs_to_jiffies(300));
}
}
/* Stop ANI */
sc->sc_flags &= ~SC_OP_ANI_RUN;
del_timer_sync(&common->ani.timer);
+ del_timer_sync(&sc->rx_poll_timer);
memset(&sc->caldata, 0, sizeof(sc->caldata));
}
}
memset(rxs, 0, sizeof(struct ieee80211_rx_status));
+ if (rs.is_mybeacon) {
+ atomic_set(&sc->stop_rx_poll, 1);
+ mod_timer(&sc->rx_poll_timer,
+ jiffies + msecs_to_jiffies(300));
+ }
+
rxs->mactime = (tsf & ~0xffffffffULL) | rs.rs_tstamp;
if (rs.rs_tstamp > tsf_lower &&
unlikely(rs.rs_tstamp - tsf_lower > 0x10000000))
common->cc_survey.rx_busy += busy;
common->cc_survey.rx_frame += rx;
common->cc_survey.tx_frame += tx;
+
+ common->cc_rxpoll.cycles += cycles;
+ common->cc_rxpoll.rx_busy += busy;
+ common->cc_rxpoll.rx_frame += rx;
+ common->cc_rxpoll.tx_frame += tx;
}
EXPORT_SYMBOL(ath_hw_cycle_counters_update);