hey! Here is a preliminary Ambient Noise Imunity (ANI) implementation for ath5k. it works only for newer chips (MAC > 0x59) and i still have some little things to fix, but it works well for my test cases here, giving me around 24Mbps (compared to 15Mbps in my worst case without ANI) on my 5414 chipset (i only tested it on this chipset). I will continue to work on it, but i send this for other people to test and review.
For now i have just implemented practically the same algorithm which is used in the HAL and in ath9k, but i think there is a lot of room for improvement of the algorithm. Later... ;) I would especially like to ask people with performance issues to try this and see if it improves the situation. This also adds a possibility to manually control ANI, right now thru debugfs: for highest immunity = lowest sensitivity: echo ani-high > /sys/kernel/debug/ath5k/phy0/ani for lowest immunity = highest sensitivity: echo ani-low > /sys/kernel/debug/ath5k/phy0/ani automatically control immunity: echo ani-auto > /sys/kernel/debug/ath5k/phy0/ani to see the parameters in use: cat /sys/kernel/debug/ath5k/phy0/ani You can also fiddle with each of the 5 immunity parameters we have thru the ani file (see the source). If you test this, it would be good to compare manual settings vs. the automatic mode. Manual mode should also be possible to use on older 5212 hardware. Things to do / questions: - make it work on older HW (before 5212 is impossible) - get beacon average - fix algorithm for IBSS - why is CCK weak signal detection never disabled? - how should the ANI settings be controlled? iw? configs? sysfs??? - improve algorithm. - move everything to a tasklet As i said, i'll continue to work on it. Feedback is appreciated. bruno --- drivers/net/wireless/ath/ath5k/Makefile | 1 drivers/net/wireless/ath/ath5k/ani.c | 576 +++++++++++++++++++++++++++++++ drivers/net/wireless/ath/ath5k/ani.h | 43 ++ drivers/net/wireless/ath/ath5k/ath5k.h | 32 +- drivers/net/wireless/ath/ath5k/attach.c | 1 drivers/net/wireless/ath/ath5k/base.c | 58 ++- drivers/net/wireless/ath/ath5k/base.h | 23 + drivers/net/wireless/ath/ath5k/debug.c | 165 +++++++++ drivers/net/wireless/ath/ath5k/debug.h | 2 drivers/net/wireless/ath/ath5k/pcu.c | 38 +- drivers/net/wireless/ath/ath5k/phy.c | 7 drivers/net/wireless/ath/ath5k/reg.h | 36 +- 12 files changed, 919 insertions(+), 63 deletions(-) create mode 100644 drivers/net/wireless/ath/ath5k/ani.c create mode 100644 drivers/net/wireless/ath/ath5k/ani.h diff --git a/drivers/net/wireless/ath/ath5k/Makefile b/drivers/net/wireless/ath/ath5k/Makefile index 090dc6d..cc09595 100644 --- a/drivers/net/wireless/ath/ath5k/Makefile +++ b/drivers/net/wireless/ath/ath5k/Makefile @@ -12,5 +12,6 @@ ath5k-y += attach.o ath5k-y += base.o ath5k-y += led.o ath5k-y += rfkill.o +ath5k-y += ani.o ath5k-$(CONFIG_ATH5K_DEBUG) += debug.o obj-$(CONFIG_ATH5K) += ath5k.o diff --git a/drivers/net/wireless/ath/ath5k/ani.c b/drivers/net/wireless/ath/ath5k/ani.c new file mode 100644 index 0000000..c0ef5c7 --- /dev/null +++ b/drivers/net/wireless/ath/ath5k/ani.c @@ -0,0 +1,576 @@ +/* + * Copyright (c) 2010 Bruno Randolf <b...@einfach.org> + * + * 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 "ath5k.h" +#include "base.h" +#include "reg.h" +#include "debug.h" +#include "ani.h" + + +/*** ANI parameters ***/ + +void +ath5k_ani_set_noise_immunity(struct ath5k_hw *ah, bool on) +{ + const int sz[] = { -55, -62 }; + const int lo[] = { -64, -70 }; + const int hi[] = { -14, -12 }; + const int sg[] = { -78, -80 }; + + AR5K_REG_WRITE_BITS(ah, AR5K_PHY_DESIRED_SIZE, + AR5K_PHY_DESIRED_SIZE_TOT, sz[on]); + AR5K_REG_WRITE_BITS(ah, AR5K_PHY_AGCCOARSE, + AR5K_PHY_AGCCOARSE_LO, lo[on]); + AR5K_REG_WRITE_BITS(ah, AR5K_PHY_AGCCOARSE, + AR5K_PHY_AGCCOARSE_HI, hi[on]); + AR5K_REG_WRITE_BITS(ah, AR5K_PHY_SIG, + AR5K_PHY_SIG_FIRPWR, sg[on]); + + ah->ah_ani_state.noise_immunity = on; + ATH5K_DBG_UNLIMIT(ah->ah_sc, ATH5K_DEBUG_ANI, "turned %s", on ? "on" : "off"); +} + + +void +ath5k_ani_set_spur_immunity_level(struct ath5k_hw *ah, int level) +{ + const int val[] = { 2, 4, 6, 8, 10, 12, 14, 16 }; + int max = ATH5K_ANI_MAX_SPUR; /* XXX: depending on chip */ + + if (level > ARRAY_SIZE(val) || level > max) { + ATH5K_DBG_UNLIMIT(ah->ah_sc, ATH5K_DEBUG_ANI, "level too high %d", level); + return; + } + + AR5K_REG_WRITE_BITS(ah, AR5K_PHY_OFDM_SELFCORR, + AR5K_PHY_OFDM_SELFCORR_CYPWR_THR1, val[level]); + + ah->ah_ani_state.spur_level = level; + ATH5K_DBG_UNLIMIT(ah->ah_sc, ATH5K_DEBUG_ANI, "new level %d", level); +} + + +void +ath5k_ani_set_firstep_level(struct ath5k_hw *ah, int level) +{ + const int val[] = { 0, 4, 8 }; + + if (level > ARRAY_SIZE(val)) { + ATH5K_DBG_UNLIMIT(ah->ah_sc, ATH5K_DEBUG_ANI, "level too high %d", level); + return; + } + + AR5K_REG_WRITE_BITS(ah, AR5K_PHY_SIG, + AR5K_PHY_SIG_FIRSTEP, val[level]); + + ah->ah_ani_state.firstep_level = level; + ATH5K_DBG_UNLIMIT(ah->ah_sc, ATH5K_DEBUG_ANI, "new level %d", level); +} + + +void +ath5k_ani_set_ofdm_weak_signal_detection(struct ath5k_hw *ah, bool on) +{ + const int m1l[] = { 127, 50 }; + const int m2l[] = { 127, 40 }; + const int m1[] = { 127, 0x4d }; + const int m2[] = { 127, 0x40 }; + const int m2cnt[] = { 31, 16 }; + const int m2lcnt[] = { 63, 48 }; + + AR5K_REG_WRITE_BITS(ah, AR5K_PHY_WEAK_OFDM_LOW_THR, + AR5K_PHY_WEAK_OFDM_LOW_THR_M1, m1l[on]); + AR5K_REG_WRITE_BITS(ah, AR5K_PHY_WEAK_OFDM_LOW_THR, + AR5K_PHY_WEAK_OFDM_LOW_THR_M2, m2l[on]); + AR5K_REG_WRITE_BITS(ah, AR5K_PHY_WEAK_OFDM_HIGH_THR, + AR5K_PHY_WEAK_OFDM_HIGH_THR_M1, m1[on]); + AR5K_REG_WRITE_BITS(ah, AR5K_PHY_WEAK_OFDM_HIGH_THR, + AR5K_PHY_WEAK_OFDM_HIGH_THR_M2, m2[on]); + AR5K_REG_WRITE_BITS(ah, AR5K_PHY_WEAK_OFDM_HIGH_THR, + AR5K_PHY_WEAK_OFDM_HIGH_THR_M2_COUNT, m2cnt[on]); + AR5K_REG_WRITE_BITS(ah, AR5K_PHY_WEAK_OFDM_LOW_THR, + AR5K_PHY_WEAK_OFDM_LOW_THR_M2_COUNT, m2lcnt[on]); + + if (on) { + AR5K_REG_ENABLE_BITS(ah, AR5K_PHY_WEAK_OFDM_LOW_THR, + AR5K_PHY_WEAK_OFDM_LOW_THR_SELFCOR_EN); + } + else { + AR5K_REG_DISABLE_BITS(ah, AR5K_PHY_WEAK_OFDM_LOW_THR, + AR5K_PHY_WEAK_OFDM_LOW_THR_SELFCOR_EN); + } + + ah->ah_ani_state.ofdm_weak_sig = on; + ATH5K_DBG_UNLIMIT(ah->ah_sc, ATH5K_DEBUG_ANI, "turned %s", on ? "on" : "off"); +} + + +void +ath5k_ani_set_cck_weak_signal_detection(struct ath5k_hw *ah, bool on) +{ + const int val[] = { 8, 6 }; + AR5K_REG_WRITE_BITS(ah, AR5K_PHY_CCK_CROSSCORR, + AR5K_PHY_CCK_CROSSCORR_WEAK_SIG_THR, val[on]); + ah->ah_ani_state.cck_weak_sig = on; + ATH5K_DBG_UNLIMIT(ah->ah_sc, ATH5K_DEBUG_ANI, "turned %s", on ? "on" : "off"); +} + + +/*** ANI algorithm ***/ + +static void +ath5k_ani_ofdm_err_trigger(struct ath5k_hw *ah) +{ + struct ath5k_ani_state *as = &ah->ah_ani_state; + int rssi; + + ATH5K_DBG_UNLIMIT(ah->ah_sc, ATH5K_DEBUG_ANI, "OFDM error trigger"); + + /* first: enable noise immunity */ + if (as->noise_immunity == false) { + ath5k_ani_set_noise_immunity(ah, true); + return; + } + /* next: raise spur immunity level */ + if (as->spur_level < ATH5K_ANI_MAX_SPUR) { + ath5k_ani_set_spur_immunity_level(ah, as->spur_level + 1); + return; + } + + /* AP mode */ + if (ah->ah_sc->opmode == NL80211_IFTYPE_AP) { + if (as->firstep_level < ATH5K_ANI_MAX_FIRSTEP) + ath5k_ani_set_firstep_level(ah, as->firstep_level + 1); + return; + } + + /* STA and IBSS mode (TODO: right for IBSS???) */ + rssi = 45; //TODO + if (rssi > ATH5K_ANI_RSSI_THR_HIGH) + { + /* beacon RSSI is high: can turn off ODFM weak signal detection */ + ATH5K_DBG_UNLIMIT(ah->ah_sc, ATH5K_DEBUG_ANI, "beacon RSSI high"); + if (as->ofdm_weak_sig == true) { + ath5k_ani_set_ofdm_weak_signal_detection(ah, false); + ath5k_ani_set_spur_immunity_level(ah, 0); + return; + } + /* as a last resort: raise firstep level */ + if (as->firstep_level < ATH5K_ANI_MAX_FIRSTEP) { + ath5k_ani_set_firstep_level(ah, as->firstep_level + 1); + return; + } + } + else if (rssi > ATH5K_ANI_RSSI_THR_LOW) + { + /* beacon RSSI in mid range, we need OFDM weak signal detect, + * but can raise firstep level */ + ATH5K_DBG_UNLIMIT(ah->ah_sc, ATH5K_DEBUG_ANI, "beacon RSSI mid"); + if (as->ofdm_weak_sig == false) + ath5k_ani_set_ofdm_weak_signal_detection(ah, true); + if (as->firstep_level < ATH5K_ANI_MAX_FIRSTEP) + ath5k_ani_set_firstep_level(ah, as->firstep_level + 1); + return; + } + else if (ah->ah_current_channel->band == IEEE80211_BAND_2GHZ) + { + /* beacon RSSI is low. in B/G mode turn of OFDM weak sig and + * zero firstep level to maximize CCK sensitivity */ + ATH5K_DBG_UNLIMIT(ah->ah_sc, ATH5K_DEBUG_ANI, "beacon RSSI low, 2GHz"); + if (as->ofdm_weak_sig) + ath5k_ani_set_ofdm_weak_signal_detection(ah, false); + if (as->firstep_level > 0) + ath5k_ani_set_firstep_level(ah, 0); + } +} + + +static void +ath5k_ani_cck_err_trigger(struct ath5k_hw *ah) +{ + struct ath5k_ani_state *as = &ah->ah_ani_state; + int rssi; + + ATH5K_DBG_UNLIMIT(ah->ah_sc, ATH5K_DEBUG_ANI, "CCK error trigger"); + + /* first: enable noise immunity */ + if (as->noise_immunity == false) { + ath5k_ani_set_noise_immunity(ah, true); + return; + } + + /* AP mode */ + if (ah->ah_sc->opmode == NL80211_IFTYPE_AP) { + if (as->firstep_level < ATH5K_ANI_MAX_FIRSTEP) + ath5k_ani_set_firstep_level(ah, as->firstep_level + 1); + return; + } + + rssi = 45; //TODO + if (rssi > ATH5K_ANI_RSSI_THR_LOW) + { + /* beacon RSSI in mid and high range */ + ATH5K_DBG_UNLIMIT(ah->ah_sc, ATH5K_DEBUG_ANI, "beacon RSSI mid/high"); + if (as->firstep_level < ATH5K_ANI_MAX_FIRSTEP) + ath5k_ani_set_firstep_level(ah, as->firstep_level + 1); + } + else if (ah->ah_current_channel->band == IEEE80211_BAND_2GHZ) + { + /* beacon RSSI is low: in B/G mode, maximize CCK sensitivity */ + ATH5K_DBG_UNLIMIT(ah->ah_sc, ATH5K_DEBUG_ANI, "beacon RSSI low, 2GHz"); + if (as->firstep_level > 0) + ath5k_ani_set_firstep_level(ah, 0); + } + + /* TODO: why not?: + if (as->cck_weak_sig) { + ath5k_ani_set_cck_weak_signal_detection(ah, 0); + } + */ +} + + +static void +ath5k_ani_lower_immunity(struct ath5k_hw *ah) +{ + struct ath5k_ani_state *as = &ah->ah_ani_state; + int rssi; + + ATH5K_DBG_UNLIMIT(ah->ah_sc, ATH5K_DEBUG_ANI, "lower immunity"); + + /* AP mode */ + if (ah->ah_sc->opmode == NL80211_IFTYPE_AP) { + if (as->firstep_level > 0) + ath5k_ani_set_firstep_level(ah, as->firstep_level - 1); + return; + } + + rssi = 45; //TODO + if (rssi > ATH5K_ANI_RSSI_THR_HIGH) { + /* beacon signal is high, leave OFDM weak signal detection off + * (TODO: who said it's off???) */ + } + else if (rssi > ATH5K_ANI_RSSI_THR_LOW) + { + /* beacon RSSI is mid-range: turn on ODFM weak signal detection + * and next, lower firstep level */ + if (as->ofdm_weak_sig == false) { + ath5k_ani_set_ofdm_weak_signal_detection(ah, true); + return; + } + if (as->firstep_level > 0) { + ath5k_ani_set_firstep_level(ah, as->firstep_level - 1); + return; + } + } + else { + /* beacon signal is low; reduce firstep level */ + if (as->firstep_level > 0) { + ath5k_ani_set_firstep_level(ah, as->firstep_level - 1); + return; + } + } + + /* then lower spur level, step by step */ + if (as->spur_level > 0) { + ath5k_ani_set_spur_immunity_level(ah, as->spur_level - 1); + return; + } + + /* finally, turn off noise immunity */ + if (as->noise_immunity == true) { + ath5k_ani_set_noise_immunity(ah, false); + return; + } +} + + +/** + * ath5k_ani_period_restart - restart ANI period + * + * reset counters and reconfigure PHY error reporting + */ +static void +ath5k_ani_period_restart(struct ath5k_hw *ah) +{ + struct ath5k_statistics *stats = &ah->ah_sc->stats; + stats->ofdm_errors = 0; + stats->cck_errors = 0; + stats->listen_time = 0; + + ath5k_hw_reg_write(ah, ATH5K_PHYERR_CNT_MAX - ATH5K_ANI_OFDM_TRIG_HIGH, + AR5K_PHYERR_CNT1); + ath5k_hw_reg_write(ah, ATH5K_PHYERR_CNT_MAX - ATH5K_ANI_CCK_TRIG_HIGH, + AR5K_PHYERR_CNT2); + + /* not necessary on my HW: + ath5k_hw_reg_write(ah, AR5K_PHY_ERR_FIL_OFDM, AR5K_PHYERR_CNT1_MASK); + ath5k_hw_reg_write(ah, AR5K_PHY_ERR_FIL_CCK, AR5K_PHYERR_CNT2_MASK); + */ +} + + +/** + * ath5k_hw_ani_get_listen_time + * + * Return an approximation of the time spent "listening" in milliseconds by + * deducting the cycles spent TX'ing and RX'ing from the total cycle count + * since our last call. + * Keep profile count values in statistics. + * We assume no one else clears these registers! + */ +static int +ath5k_hw_ani_get_listen_time(struct ath5k_hw *ah) +{ + struct ath5k_statistics *stats = &ah->ah_sc->stats; + + /* freeze */ + ath5k_hw_reg_write(ah, AR5K_MIBC, AR5K_MIBC_FMC); + /* read */ + stats->pfc_cycles = ath5k_hw_reg_read(ah, AR5K_PROFCNT_CYCLE); + stats->pfc_rxclr = ath5k_hw_reg_read(ah, AR5K_PROFCNT_RXCLR); + stats->pfc_tx = ath5k_hw_reg_read(ah, AR5K_PROFCNT_TX); + stats->pfc_rx = ath5k_hw_reg_read(ah, AR5K_PROFCNT_RX); + /* clear */ + ath5k_hw_reg_write(ah, 0, AR5K_PROFCNT_TX); + ath5k_hw_reg_write(ah, 0, AR5K_PROFCNT_RX); + ath5k_hw_reg_write(ah, 0, AR5K_PROFCNT_RXCLR); + ath5k_hw_reg_write(ah, 0, AR5K_PROFCNT_CYCLE); + /* un-freeze */ + ath5k_hw_reg_write(ah, AR5K_MIBC, 0); + + if (stats->pfc_cycles == 0) + return 0; + + /* TODO: i think listen_time = 100 - st->pfc_rxclr*100/st->pfc_cycles (in percent) */ + + /* TODO: where does 44000 come from? (11g clock rate?) */ + return (stats->pfc_cycles - stats->pfc_rx - stats->pfc_tx) / 44000; +} + + +void +ath5k_ani_periodic_calibration(struct ath5k_hw *ah) +{ + struct ath5k_statistics *stats = &ah->ah_sc->stats; + unsigned int ofdm_err, cck_err, period; + int listen; + static unsigned long last_j; + + if (ah->ah_ani_mode != ATH5K_ANI_MODE_AUTO) + return; + + /* we are on a timer, but can't assume to be called exactly when we + * requested, therefore we need to keep track of passed time since the + * last call */ + period = jiffies_to_msecs(jiffies-last_j); + last_j = jiffies; + + listen = ath5k_hw_ani_get_listen_time(ah); /* since last call */ + stats->listen_time += listen; + + ofdm_err = ath5k_hw_reg_read(ah, AR5K_PHYERR_CNT1); + cck_err = ath5k_hw_reg_read(ah, AR5K_PHYERR_CNT2); + ofdm_err = ATH5K_ANI_OFDM_TRIG_HIGH - (ATH5K_PHYERR_CNT_MAX - ofdm_err); + cck_err = ATH5K_ANI_CCK_TRIG_HIGH - (ATH5K_PHYERR_CNT_MAX - cck_err); + stats->ofdm_errors += ofdm_err; + stats->cck_errors += cck_err; + + ATH5K_DBG_UNLIMIT(ah->ah_sc, ATH5K_DEBUG_ANI, + "listen %d (now %d) period %d", stats->listen_time, listen, period); + + /* We count OFDM and CCK errors in the time where we did not + * send or receive ("listen" time) and react accordingly */ + if (stats->listen_time > 5 * ATH5K_ANI_LISTEN_PERIOD) + { + /* more than 5 periods have passed - if we got relatively little + * OFDM or CCK errors we can lower immunity */ + ATH5K_DBG_UNLIMIT(ah->ah_sc, ATH5K_DEBUG_ANI, + ">5 periods: ofdm %d/%d cck %d/%d", + ofdm_err, stats->listen_time * ATH5K_ANI_OFDM_TRIG_LOW / 1000, + cck_err, stats->listen_time * ATH5K_ANI_CCK_TRIG_LOW / 1000); + + if (stats->ofdm_errors <= + stats->listen_time * ATH5K_ANI_OFDM_TRIG_LOW / 1000 && + stats->cck_errors <= + stats->listen_time * ATH5K_ANI_CCK_TRIG_LOW / 1000) { + + /* lower immunity! */ + ath5k_ani_lower_immunity(ah); + } + ath5k_ani_period_restart(ah); + } + else if (stats->listen_time > ATH5K_ANI_LISTEN_PERIOD) + { + /* more than one period has passed - if there were too many + * PHY errors we have to raise immunity */ + ATH5K_DBG_UNLIMIT(ah->ah_sc, ATH5K_DEBUG_ANI, + ">1 period: ofdm %d/%d cck %d/%d", + ofdm_err, stats->listen_time * ATH5K_ANI_OFDM_TRIG_HIGH / 1000, + cck_err, stats->listen_time * ATH5K_ANI_CCK_TRIG_HIGH / 1000); + + if (stats->ofdm_errors > + stats->listen_time * ATH5K_ANI_OFDM_TRIG_HIGH / 1000) { + + ATH5K_DBG_UNLIMIT(ah->ah_sc, ATH5K_DEBUG_ANI, "OFDM errors"); + ath5k_ani_ofdm_err_trigger(ah); + ath5k_ani_period_restart(ah); + } + else if (stats->cck_errors > + stats->listen_time * ATH5K_ANI_CCK_TRIG_HIGH / 1000) { + + ATH5K_DBG_UNLIMIT(ah->ah_sc, ATH5K_DEBUG_ANI, "CCK errors"); + ath5k_ani_cck_err_trigger(ah); + ath5k_ani_period_restart(ah); + } + } +} + + +/*** INTERRUPT HANDLER ***/ + +void +ath5k_ani_mib_intr(struct ath5k_hw *ah) +{ + unsigned int ofdm_err, cck_err; + + /* not in use but clear always */ + ath5k_hw_reg_write(ah, 0, AR5K_OFDM_FIL_CNT); + ath5k_hw_reg_write(ah, 0, AR5K_CCK_FIL_CNT); + + if (ah->ah_ani_mode != ATH5K_ANI_MODE_AUTO) + return; + + ofdm_err = ath5k_hw_reg_read(ah, AR5K_PHYERR_CNT1); + cck_err = ath5k_hw_reg_read(ah, AR5K_PHYERR_CNT2); + + if (ofdm_err & ATH5K_PHYERR_CNT_MAX) + ath5k_ani_ofdm_err_trigger(ah); + + if (cck_err & ATH5K_PHYERR_CNT_MAX) + ath5k_ani_cck_err_trigger(ah); + + /* reset the registers, if they caused the interrupt */ + if (cck_err & ATH5K_PHYERR_CNT_MAX || ofdm_err & ATH5K_PHYERR_CNT_MAX) + ath5k_ani_period_restart(ah); + + //printk("oe: %u\n", ofdm_err); + //printk("ce: %u\n", cck_err); +} + + +/*** INIT ***/ + +static void +ath5k_enable_phy_err_counters(struct ath5k_hw *ah) +{ + ath5k_hw_update_mib_counters(ah); /* clear all */ + ath5k_hw_reg_write(ah, AR5K_MIBC, 0); /* enable, just in case */ + + /* not in use */ + ath5k_hw_reg_write(ah, 0, AR5K_OFDM_FIL_CNT); + ath5k_hw_reg_write(ah, 0, AR5K_CCK_FIL_CNT); + + /* enable PHY error reporting for OFDM and CCK */ + ath5k_hw_reg_write(ah, ATH5K_PHYERR_CNT_MAX - ATH5K_ANI_OFDM_TRIG_HIGH, + AR5K_PHYERR_CNT1); + ath5k_hw_reg_write(ah, ATH5K_PHYERR_CNT_MAX - ATH5K_ANI_CCK_TRIG_HIGH, + AR5K_PHYERR_CNT2); + ath5k_hw_reg_write(ah, AR5K_PHY_ERR_FIL_OFDM, AR5K_PHYERR_CNT1_MASK); + ath5k_hw_reg_write(ah, AR5K_PHY_ERR_FIL_CCK, AR5K_PHYERR_CNT2_MASK); +} + + +static void +ath5k_disable_phy_err_counters(struct ath5k_hw *ah) +{ + /* not in use */ + ath5k_hw_reg_write(ah, 0, AR5K_OFDM_FIL_CNT); + ath5k_hw_reg_write(ah, 0, AR5K_CCK_FIL_CNT); + + /* disable PHY error reporting for OFDM and CCK */ + ath5k_hw_reg_write(ah, 0, AR5K_PHYERR_CNT1); + ath5k_hw_reg_write(ah, 0, AR5K_PHYERR_CNT2); + ath5k_hw_reg_write(ah, 0, AR5K_PHYERR_CNT1_MASK); + ath5k_hw_reg_write(ah, 0, AR5K_PHYERR_CNT2_MASK); +} + + +void +ath5k_ani_init(struct ath5k_hw *ah, enum ath5k_ani_mode mode) +{ + ah->ah_ani_mode = mode; + if (mode == ATH5K_ANI_MODE_OFF) { + ATH5K_INFO(ah->ah_sc, "ANI off"); + ath5k_disable_phy_err_counters(ah); + } + else if (mode == ATH5K_ANI_MODE_MANUAL_LOW) { + ATH5K_INFO(ah->ah_sc, "ANI manual low -> high sensitivity"); + ath5k_ani_set_noise_immunity(ah, false); + ath5k_ani_set_ofdm_weak_signal_detection(ah, true); + ath5k_ani_set_cck_weak_signal_detection(ah, true); + ath5k_ani_set_spur_immunity_level(ah, 0); + ath5k_ani_set_firstep_level(ah, 0); + ath5k_disable_phy_err_counters(ah); + } + else if (mode == ATH5K_ANI_MODE_MANUAL_HIGH) { + ATH5K_INFO(ah->ah_sc, "ANI manual high -> low sensitivity"); + ath5k_ani_set_noise_immunity(ah, true); + ath5k_ani_set_ofdm_weak_signal_detection(ah, false); + ath5k_ani_set_cck_weak_signal_detection(ah, false); + ath5k_ani_set_spur_immunity_level(ah, ATH5K_ANI_MAX_SPUR); + ath5k_ani_set_firstep_level(ah, ATH5K_ANI_MAX_FIRSTEP); + ath5k_disable_phy_err_counters(ah); + } + else if (mode == ATH5K_ANI_MODE_AUTO) { + ATH5K_INFO(ah->ah_sc, "ANI auto"); + ath5k_ani_set_noise_immunity(ah, false); + ath5k_ani_set_ofdm_weak_signal_detection(ah, true); + ath5k_ani_set_cck_weak_signal_detection(ah, true); + ath5k_ani_set_spur_immunity_level(ah, 0); + ath5k_ani_set_firstep_level(ah, 0); + ath5k_enable_phy_err_counters(ah); + + /* necessary on older HW: */ + //ath5k_hw_set_rx_filter(ah, ath5k_hw_get_rx_filter(ah) | AR5K_RX_FILTER_PHYERR); + } +} + + +/*** DEBUG ***/ + +void +ath5k_ani_print_counters(struct ath5k_hw *ah) +{ + // clears too + printk("ACK fail\t%d\n", ath5k_hw_reg_read(ah, AR5K_ACK_FAIL)); + printk("RTS fail\t%d\n", ath5k_hw_reg_read(ah, AR5K_RTS_FAIL)); + printk("RTS success\t%d\n", ath5k_hw_reg_read(ah, AR5K_RTS_OK)); + printk("FCS error\t%d\n", ath5k_hw_reg_read(ah, AR5K_FCS_FAIL)); + + // no clear + printk("tx\t%d\n", ath5k_hw_reg_read(ah, AR5K_PROFCNT_TX)); + printk("rx\t%d\n", ath5k_hw_reg_read(ah, AR5K_PROFCNT_RX)); + printk("busy\t%d\n", ath5k_hw_reg_read(ah, AR5K_PROFCNT_RXCLR)); + printk("cycles\t%d\n", ath5k_hw_reg_read(ah, AR5K_PROFCNT_CYCLE)); + + printk("AR5K_PHYERR_CNT1\t%d\n", ath5k_hw_reg_read(ah, AR5K_PHYERR_CNT1)); + printk("AR5K_PHYERR_CNT2\t%d\n", ath5k_hw_reg_read(ah, AR5K_PHYERR_CNT2)); + + printk("AR5K_OFDM_FIL_CNT\t%d\n", ath5k_hw_reg_read(ah, AR5K_OFDM_FIL_CNT)); + printk("AR5K_CCK_FIL_CNT\t%d\n", ath5k_hw_reg_read(ah, AR5K_CCK_FIL_CNT)); +} diff --git a/drivers/net/wireless/ath/ath5k/ani.h b/drivers/net/wireless/ath/ath5k/ani.h new file mode 100644 index 0000000..abeacd1 --- /dev/null +++ b/drivers/net/wireless/ath/ath5k/ani.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2010 Bruno Randolf <b...@einfach.org> + * + * 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. + */ +#ifndef ANI_H +#define ANI_H + +#define ATH5K_ANI_OFDM_TRIG_HIGH 500 +#define ATH5K_ANI_OFDM_TRIG_LOW 200 +#define ATH5K_ANI_CCK_TRIG_HIGH 200 +#define ATH5K_ANI_CCK_TRIG_LOW 100 +#define ATH5K_ANI_RSSI_THR_HIGH 40 +#define ATH5K_ANI_RSSI_THR_LOW 7 + +#define ATH5K_ANI_MAX_SPUR 2 /* XXX: depending on chip */ +#define ATH5K_ANI_MAX_FIRSTEP 2 + +#define ATH5K_ANI_LISTEN_PERIOD 100 + +void ath5k_ani_init(struct ath5k_hw *ah, enum ath5k_ani_mode mode); +void ath5k_ani_mib_intr(struct ath5k_hw *ah); +void ath5k_ani_periodic_calibration(struct ath5k_hw *ah); + +void ath5k_ani_set_noise_immunity(struct ath5k_hw *ah, bool on); +void ath5k_ani_set_spur_immunity_level(struct ath5k_hw *ah, int level); +void ath5k_ani_set_firstep_level(struct ath5k_hw *ah, int level); +void ath5k_ani_set_ofdm_weak_signal_detection(struct ath5k_hw *ah, bool on); +void ath5k_ani_set_cck_weak_signal_detection(struct ath5k_hw *ah, bool on); + +void ath5k_ani_print_counters(struct ath5k_hw *ah); + +#endif /* ANI_H */ diff --git a/drivers/net/wireless/ath/ath5k/ath5k.h b/drivers/net/wireless/ath/ath5k/ath5k.h index 365eccd..94a07d4 100644 --- a/drivers/net/wireless/ath/ath5k/ath5k.h +++ b/drivers/net/wireless/ath/ath5k/ath5k.h @@ -170,6 +170,8 @@ * Some tuneable values (these should be changeable by the user) * TODO: Make use of them and add more options OR use debug/configfs */ +#define ATH5K_TUNE_CALIBRATION_INTERVAL_FULL 10000 /* 10 sec */ +#define ATH5K_TUNE_CALIBRATION_INTERVAL_ANI 1000 /* 1 sec */ #define AR5K_TUNE_DMA_BEACON_RESP 2 #define AR5K_TUNE_SW_BEACON_RESP 10 #define AR5K_TUNE_ADDITIONAL_SWBA_BACKOFF 0 @@ -821,9 +823,9 @@ struct ath5k_athchan_2ghz { * @AR5K_INT_TXURN: received when we should increase the TX trigger threshold * We currently do increments on interrupt by * (AR5K_TUNE_MAX_TX_FIFO_THRES - current_trigger_level) / 2 - * @AR5K_INT_MIB: Indicates the Management Information Base counters should be - * checked. We should do this with ath5k_hw_update_mib_counters() but - * it seems we should also then do some noise immunity work. + * @AR5K_INT_MIB: Indicates the either Management Information Base counters or + * one of the PHY error counters reached the maximum value and should be + * read and cleared. * @AR5K_INT_RXPHY: RX PHY Error * @AR5K_INT_RXKCM: RX Key cache miss * @AR5K_INT_SWBA: SoftWare Beacon Alert - indicates its time to send a @@ -1013,6 +1015,21 @@ struct ath5k_nfcal_hist s16 nfval[ATH5K_NF_CAL_HIST_MAX]; /* last few noise floors */ }; +struct ath5k_ani_state { + /* state */ + bool noise_immunity; + bool ofdm_weak_sig; + bool cck_weak_sig; + int spur_level; + int firstep_level; +}; + +enum ath5k_ani_mode { + ATH5K_ANI_MODE_OFF = 0, + ATH5K_ANI_MODE_MANUAL_LOW = 1, + ATH5K_ANI_MODE_AUTO = 2, + ATH5K_ANI_MODE_MANUAL_HIGH = 3 +}; /***************************************\ HARDWARE ABSTRACTION LAYER STRUCTURE @@ -1123,12 +1140,12 @@ struct ath5k_hw { /* Calibration timestamp */ unsigned long ah_cal_tstamp; - /* Calibration interval (secs) */ - u8 ah_cal_intval; - /* Software interrupt mask */ u8 ah_swi_mask; + enum ath5k_ani_mode ah_ani_mode; /* ANI mode (off, auto, max) */ + struct ath5k_ani_state ah_ani_state; + /* * Function pointers */ @@ -1185,8 +1202,7 @@ int ath5k_hw_update_tx_triglevel(struct ath5k_hw *ah, bool increase); bool ath5k_hw_is_intr_pending(struct ath5k_hw *ah); int ath5k_hw_get_isr(struct ath5k_hw *ah, enum ath5k_int *interrupt_mask); enum ath5k_int ath5k_hw_set_imr(struct ath5k_hw *ah, enum ath5k_int new_mask); -void ath5k_hw_update_mib_counters(struct ath5k_hw *ah, - struct ieee80211_low_level_stats *stats); +void ath5k_hw_update_mib_counters(struct ath5k_hw *ah); /* EEPROM access functions */ int ath5k_eeprom_init(struct ath5k_hw *ah); diff --git a/drivers/net/wireless/ath/ath5k/attach.c b/drivers/net/wireless/ath/ath5k/attach.c index dd4099a..ea1bbf5 100644 --- a/drivers/net/wireless/ath/ath5k/attach.c +++ b/drivers/net/wireless/ath/ath5k/attach.c @@ -123,6 +123,7 @@ int ath5k_hw_attach(struct ath5k_softc *sc) ah->ah_limit_tx_retries = AR5K_INIT_TX_RETRY; ah->ah_software_retry = false; ah->ah_ant_mode = AR5K_ANTMODE_DEFAULT; + ah->ah_ani_mode = ATH5K_ANI_MODE_AUTO; /* * Find the mac version diff --git a/drivers/net/wireless/ath/ath5k/base.c b/drivers/net/wireless/ath/ath5k/base.c index 13bb8fc..3d65265 100644 --- a/drivers/net/wireless/ath/ath5k/base.c +++ b/drivers/net/wireless/ath/ath5k/base.c @@ -58,8 +58,8 @@ #include "base.h" #include "reg.h" #include "debug.h" +#include "ani.h" -static u8 ath5k_calinterval = 10; /* Calibrate PHY every 10 secs (TODO: Fixme) */ static int modparam_nohwcrypt; module_param_named(nohwcrypt, modparam_nohwcrypt, bool, S_IRUGO); MODULE_PARM_DESC(nohwcrypt, "Disable hardware encryption."); @@ -2036,6 +2036,10 @@ accept: if (sc->opmode == NL80211_IFTYPE_ADHOC) ath5k_check_ibss_tsf(sc, skb, rxs); + //TODO:??? + //if (ieee80211_is_beacon(fc)) + // ah->stats.last_beacon_rssi = rx_stats->rs_rssi; + ieee80211_rx(sc->hw, skb); bf->skb = next_skb; @@ -2103,7 +2107,7 @@ ath5k_tx_processq(struct ath5k_softc *sc, struct ath5k_txq *txq) info->status.rates[ts.ts_final_idx].count++; if (unlikely(ts.ts_status)) { - sc->ll_stats.dot11ACKFailureCount++; + sc->stats.ack_fail++; if (ts.ts_status & AR5K_TXERR_FILT) { info->flags |= IEEE80211_TX_STAT_TX_FILTERED; sc->stats.txerr_filt++; @@ -2506,9 +2510,6 @@ ath5k_init(struct ath5k_softc *sc) */ ath5k_stop_locked(sc); - /* Set PHY calibration interval */ - ah->ah_cal_intval = ath5k_calinterval; - /* * The basic interface to setting the hardware in a good * state is ``reset''. On return the hardware is known to @@ -2520,7 +2521,9 @@ ath5k_init(struct ath5k_softc *sc) sc->curband = &sc->sbands[sc->curchan->band]; sc->imask = AR5K_INT_RXOK | AR5K_INT_RXERR | AR5K_INT_RXEOL | AR5K_INT_RXORN | AR5K_INT_TXDESC | AR5K_INT_TXEOL | - AR5K_INT_FATAL | AR5K_INT_GLOBAL | AR5K_INT_SWI; + AR5K_INT_FATAL | AR5K_INT_GLOBAL | AR5K_INT_SWI | + AR5K_INT_MIB; + ret = ath5k_reset(sc, NULL); if (ret) goto done; @@ -2690,11 +2693,9 @@ ath5k_intr(int irq, void *dev_id) tasklet_schedule(&sc->calib); } if (status & AR5K_INT_MIB) { - /* - * These stats are also used for ANI i think - * so how about updating them more often ? - */ - ath5k_hw_update_mib_counters(ah, &sc->ll_stats); + sc->stats.mib_intr++; + ath5k_hw_update_mib_counters(ah); + ath5k_ani_mib_intr(ah); } if (status & AR5K_INT_GPIO) tasklet_schedule(&sc->rf_kill.toggleq); @@ -2702,8 +2703,13 @@ ath5k_intr(int irq, void *dev_id) } } while (ath5k_hw_is_intr_pending(ah) && --counter > 0); - if (unlikely(!counter)) + if (unlikely(!counter)) { ATH5K_WARN(sc, "too many interrupts, giving up for now\n"); + ATH5K_WARN(sc, "disabling MIB interrupt\n"); + sc->imask &= ~AR5K_INT_MIB; + ath5k_hw_set_imr(ah, sc->imask); + ath5k_ani_print_counters(ah); + } ath5k_hw_calibration_poll(ah); @@ -2727,13 +2733,21 @@ ath5k_tasklet_calibrate(unsigned long data) { struct ath5k_softc *sc = (void *)data; struct ath5k_hw *ah = sc->ah; + static unsigned long next_full; + + if (!next_full) + next_full = jiffies + + msecs_to_jiffies(ATH5K_TUNE_CALIBRATION_INTERVAL_FULL); - /* Only full calibration for now */ - if (ah->ah_swi_mask != AR5K_SWI_FULL_CALIBRATION) + if (time_is_after_eq_jiffies(next_full)) { + /* it's not time to do a full calibration, just do ani */ + ath5k_ani_periodic_calibration(ah); return; + } - /* Stop queues so that calibration - * doesn't interfere with tx */ + /* Full calibration */ + + /* Stop queues so that calibration doesn't interfere with tx */ ieee80211_stop_queues(sc->hw); ATH5K_DBG(sc, ATH5K_DEBUG_CALIBRATE, "channel %u/%x\n", @@ -2758,6 +2772,11 @@ ath5k_tasklet_calibrate(unsigned long data) /* Wake queues */ ieee80211_wake_queues(sc->hw); + /* always do ani calibration */ + ath5k_ani_periodic_calibration(ah); + + next_full = jiffies + + msecs_to_jiffies(ATH5K_TUNE_CALIBRATION_INTERVAL_FULL); } @@ -3221,9 +3240,12 @@ ath5k_get_stats(struct ieee80211_hw *hw, struct ath5k_hw *ah = sc->ah; /* Force update */ - ath5k_hw_update_mib_counters(ah, &sc->ll_stats); + ath5k_hw_update_mib_counters(ah); - memcpy(stats, &sc->ll_stats, sizeof(sc->ll_stats)); + stats->dot11ACKFailureCount = sc->stats.ack_fail; + stats->dot11RTSFailureCount = sc->stats.rts_fail; + stats->dot11RTSSuccessCount = sc->stats.rts_ok; + stats->dot11FCSErrorCount = sc->stats.fcs_error; return 0; } diff --git a/drivers/net/wireless/ath/ath5k/base.h b/drivers/net/wireless/ath/ath5k/base.h index 33f1d8b..9174523 100644 --- a/drivers/net/wireless/ath/ath5k/base.h +++ b/drivers/net/wireless/ath/ath5k/base.h @@ -107,8 +107,11 @@ struct ath5k_rfkill { /* statistics (only used for debugging now) */ struct ath5k_statistics { + /* antenna use */ unsigned int antenna_rx[5]; /* frames count per antenna RX */ unsigned int antenna_tx[5]; /* frames count per antenna TX */ + + /* frames & frameerrors */ unsigned int rx_all_count; /* all RX frames, including errors */ unsigned int tx_all_count; /* all TX frames, including errors */ unsigned int rxerr_crc; @@ -121,6 +124,25 @@ struct ath5k_statistics { unsigned int txerr_retry; unsigned int txerr_fifo; unsigned int txerr_filt; + + /* MIB counters */ + unsigned int ack_fail; + unsigned int rts_fail; + unsigned int rts_ok; + unsigned int fcs_error; + unsigned int beacons; + + /* profile counters from last ANI calibration */ + unsigned int pfc_tx; + unsigned int pfc_rx; + unsigned int pfc_rxclr; + unsigned int pfc_cycles; + unsigned int listen_time; /* made up */ + + unsigned int ofdm_errors; + unsigned int cck_errors; + + unsigned int mib_intr; }; #if CHAN_DEBUG @@ -135,7 +157,6 @@ struct ath5k_softc { struct pci_dev *pdev; /* for dma mapping */ void __iomem *iobase; /* address of the device */ struct mutex lock; /* dev-level lock */ - struct ieee80211_low_level_stats ll_stats; struct ieee80211_hw *hw; /* IEEE 802.11 common */ struct ieee80211_supported_band sbands[IEEE80211_NUM_BANDS]; struct ieee80211_channel channels[ATH_CHAN_MAX]; diff --git a/drivers/net/wireless/ath/ath5k/debug.c b/drivers/net/wireless/ath/ath5k/debug.c index bccd4a7..3151113 100644 --- a/drivers/net/wireless/ath/ath5k/debug.c +++ b/drivers/net/wireless/ath/ath5k/debug.c @@ -69,6 +69,7 @@ module_param_named(debug, ath5k_debug, uint, 0); #include <linux/seq_file.h> #include "reg.h" +#include "ani.h" static struct dentry *ath5k_global_debugfs; @@ -307,6 +308,7 @@ static const struct { { ATH5K_DEBUG_DUMP_TX, "dumptx", "print transmit skb content" }, { ATH5K_DEBUG_DUMPBANDS, "dumpbands", "dump bands" }, { ATH5K_DEBUG_TRACE, "trace", "trace function calls" }, + { ATH5K_DEBUG_ANI, "ani", "ambient noise immunity" }, { ATH5K_DEBUG_ANY, "all", "show all debug levels" }, }; @@ -565,6 +567,163 @@ static const struct file_operations fops_frameerrors = { }; +/* debugfs: ani */ + +static ssize_t read_file_ani(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ath5k_softc *sc = file->private_data; + struct ath5k_statistics *st = &sc->stats; + struct ath5k_ani_state *as = &sc->ah->ah_ani_state; + + char buf[700]; + unsigned int len = 0; + + len += snprintf(buf+len, sizeof(buf)-len, + "\nANI state\n--------------------------------------------\n"); + len += snprintf(buf+len, sizeof(buf)-len, "operating mode:\t\t\t"); + switch (sc->ah->ah_ani_mode) { + case ATH5K_ANI_MODE_OFF: + len += snprintf(buf+len, sizeof(buf)-len, "OFF\n"); break; + case ATH5K_ANI_MODE_MANUAL_LOW: + len += snprintf(buf+len, sizeof(buf)-len, "MANUAL LOW\n"); break; + case ATH5K_ANI_MODE_MANUAL_HIGH: + len += snprintf(buf+len, sizeof(buf)-len, "MANUAL HIGH\n"); break; + case ATH5K_ANI_MODE_AUTO: + len += snprintf(buf+len, sizeof(buf)-len, "AUTO\n"); break; + default: + len += snprintf(buf+len, sizeof(buf)-len, "??? (not good)\n"); break; + } + len += snprintf(buf+len, sizeof(buf)-len, "noise immunity:\t\t\t%s\n", + as->noise_immunity ? "on" : "off"); + len += snprintf(buf+len, sizeof(buf)-len, "OFDM weak signal detection:\t%s\n", + as->ofdm_weak_sig ? "on" : "off"); + len += snprintf(buf+len, sizeof(buf)-len, "CCK weak signal detection:\t%s\n", + as->cck_weak_sig ? "on" : "off"); + len += snprintf(buf+len, sizeof(buf)-len, "spur immunity level:\t\t%d\n", + as->spur_level); + len += snprintf(buf+len, sizeof(buf)-len, "firstep level:\t\t\t%d\n", + as->firstep_level); + + len += snprintf(buf+len, sizeof(buf)-len, "\nMIB INTERRUPTS: %u\n", + st->mib_intr); +#if 0 + ath5k_hw_update_mib_counters(sc->ah); + len += snprintf(buf+len, sizeof(buf)-len, + "\nMIB STATS\n---------------------\n"); + len += snprintf(buf+len, sizeof(buf)-len, "ACK fail\t%u\n", + st->ack_fail); + len += snprintf(buf+len, sizeof(buf)-len, "RTS fail\t%u\n", + st->rts_fail); + len += snprintf(buf+len, sizeof(buf)-len, "RTS success\t%u\n", + st->rts_ok); + len += snprintf(buf+len, sizeof(buf)-len, "FCS error\t%u\n", + st->fcs_error); + len += snprintf(buf+len, sizeof(buf)-len, "beacons\t\t%d\n", + st->beacons); +#endif + + len += snprintf(buf+len, sizeof(buf)-len, + "\nPROFCNT\n---------------------\n"); + len += snprintf(buf+len, sizeof(buf)-len, "tx\t%u\t(%d%%)\n", + st->pfc_tx, + st->pfc_cycles > 0 ? + st->pfc_tx*100/st->pfc_cycles : 0); + len += snprintf(buf+len, sizeof(buf)-len, "rx\t%u\t(%d%%)\n", + st->pfc_rx, + st->pfc_cycles > 0 ? + st->pfc_rx*100/st->pfc_cycles : 0); + len += snprintf(buf+len, sizeof(buf)-len, "busy\t%u\t(%d%%)\n", + st->pfc_rxclr, + st->pfc_cycles > 0 ? + st->pfc_rxclr*100/st->pfc_cycles : 0); + len += snprintf(buf+len, sizeof(buf)-len, "cycles\t%u\n", + st->pfc_cycles); + len += snprintf(buf+len, sizeof(buf)-len, "listen\t%d\t(%d%%)\n", + st->listen_time, st->listen_time/10); + + len += snprintf(buf+len, sizeof(buf)-len, + "\nPHY ERRORS\n---------------------\n"); + len += snprintf(buf+len, sizeof(buf)-len, "OFDM errors\t%u\n", + st->ofdm_errors); + len += snprintf(buf+len, sizeof(buf)-len, "CCK errors\t%u\n", + st->cck_errors); + len += snprintf(buf+len, sizeof(buf)-len, "AR5K_PHYERR_CNT1\t%u = %d\n", + ath5k_hw_reg_read(sc->ah, AR5K_PHYERR_CNT1), + ATH5K_ANI_OFDM_TRIG_HIGH - (ATH5K_PHYERR_CNT_MAX - + ath5k_hw_reg_read(sc->ah, AR5K_PHYERR_CNT1))); + len += snprintf(buf+len, sizeof(buf)-len, "AR5K_PHYERR_CNT2\t%u = %d\n", + ath5k_hw_reg_read(sc->ah, AR5K_PHYERR_CNT2), + ATH5K_ANI_CCK_TRIG_HIGH - (ATH5K_PHYERR_CNT_MAX - + ath5k_hw_reg_read(sc->ah, AR5K_PHYERR_CNT2))); + + return simple_read_from_buffer(user_buf, count, ppos, buf, len); +} + +static ssize_t write_file_ani(struct file *file, + const char __user *userbuf, + size_t count, loff_t *ppos) +{ + struct ath5k_softc *sc = file->private_data; + char buf[20]; + + if (copy_from_user(buf, userbuf, min(count, sizeof(buf)))) + return -EFAULT; + + if (strncmp(buf, "ani-high", 8) == 0) { + ath5k_ani_init(sc->ah, ATH5K_ANI_MODE_MANUAL_HIGH); + } + else if (strncmp(buf, "ani-low", 7) == 0) { + ath5k_ani_init(sc->ah, ATH5K_ANI_MODE_MANUAL_LOW); + } + else if (strncmp(buf, "ani-off", 7) == 0) { + ath5k_ani_init(sc->ah, ATH5K_ANI_MODE_OFF); + } + else if (strncmp(buf, "ani-auto", 8) == 0) { + ath5k_ani_init(sc->ah, ATH5K_ANI_MODE_AUTO); + } + else if (strncmp(buf, "noise-low", 8) == 0) { + ath5k_ani_set_noise_immunity(sc->ah, 0); + } + else if (strncmp(buf, "noise-high", 9) == 0) { + ath5k_ani_set_noise_immunity(sc->ah, 1); + } + else if (strncmp(buf, "spur-low", 8) == 0) { + ath5k_ani_set_spur_immunity_level(sc->ah, 0); + } + else if (strncmp(buf, "spur-high", 9) == 0) { + ath5k_ani_set_spur_immunity_level(sc->ah, 2); + } + else if (strncmp(buf, "fir-low", 7) == 0) { + ath5k_ani_set_firstep_level(sc->ah, 0); + } + else if (strncmp(buf, "fir-high", 8) == 0) { + ath5k_ani_set_firstep_level(sc->ah, 2); + } + else if (strncmp(buf, "ofdm-off", 8) == 0) { + ath5k_ani_set_cck_weak_signal_detection(sc->ah, 0); + } + else if (strncmp(buf, "ofdm-on", 7) == 0) { + ath5k_ani_set_cck_weak_signal_detection(sc->ah, 1); + } + else if (strncmp(buf, "cck-off", 7) == 0) { + ath5k_ani_set_cck_weak_signal_detection(sc->ah, 0); + } + else if (strncmp(buf, "cck-on", 6) == 0) { + ath5k_ani_set_cck_weak_signal_detection(sc->ah, 1); + } + + return count; +} + +static const struct file_operations fops_ani = { + .read = read_file_ani, + .write = write_file_ani, + .open = ath5k_debugfs_open, + .owner = THIS_MODULE, +}; + + /* init */ void @@ -603,6 +762,11 @@ ath5k_debug_init_device(struct ath5k_softc *sc) S_IWUSR | S_IRUSR, sc->debug.debugfs_phydir, sc, &fops_frameerrors); + + sc->debug.debugfs_ani = debugfs_create_file("ani", + S_IWUSR | S_IRUSR, + sc->debug.debugfs_phydir, sc, + &fops_ani); } void @@ -620,6 +784,7 @@ ath5k_debug_finish_device(struct ath5k_softc *sc) debugfs_remove(sc->debug.debugfs_reset); debugfs_remove(sc->debug.debugfs_antenna); debugfs_remove(sc->debug.debugfs_frameerrors); + debugfs_remove(sc->debug.debugfs_ani); debugfs_remove(sc->debug.debugfs_phydir); } diff --git a/drivers/net/wireless/ath/ath5k/debug.h b/drivers/net/wireless/ath/ath5k/debug.h index da24ff5..ddd5b3a 100644 --- a/drivers/net/wireless/ath/ath5k/debug.h +++ b/drivers/net/wireless/ath/ath5k/debug.h @@ -76,6 +76,7 @@ struct ath5k_dbg_info { struct dentry *debugfs_reset; struct dentry *debugfs_antenna; struct dentry *debugfs_frameerrors; + struct dentry *debugfs_ani; }; /** @@ -115,6 +116,7 @@ enum ath5k_debug_level { ATH5K_DEBUG_DUMP_TX = 0x00000200, ATH5K_DEBUG_DUMPBANDS = 0x00000400, ATH5K_DEBUG_TRACE = 0x00001000, + ATH5K_DEBUG_ANI = 0x00002000, ATH5K_DEBUG_ANY = 0xffffffff }; diff --git a/drivers/net/wireless/ath/ath5k/pcu.c b/drivers/net/wireless/ath/ath5k/pcu.c index c813046..4d7390a 100644 --- a/drivers/net/wireless/ath/ath5k/pcu.c +++ b/drivers/net/wireless/ath/ath5k/pcu.c @@ -30,6 +30,7 @@ #include "reg.h" #include "debug.h" #include "base.h" +#include "ani.h" /*******************\ * Generic functions * @@ -113,39 +114,25 @@ int ath5k_hw_set_opmode(struct ath5k_hw *ah, enum nl80211_iftype op_mode) } /** - * ath5k_hw_update - Update mib counters (mac layer statistics) + * ath5k_hw_update - Update MIB counters (mac layer statistics) * * @ah: The &struct ath5k_hw - * @stats: The &struct ieee80211_low_level_stats we use to track - * statistics on the driver * * Reads MIB counters from PCU and updates sw statistics. Must be * called after a MIB interrupt. + * + * Is called in interrupt context! */ -void ath5k_hw_update_mib_counters(struct ath5k_hw *ah, - struct ieee80211_low_level_stats *stats) +void ath5k_hw_update_mib_counters(struct ath5k_hw *ah) { - ATH5K_TRACE(ah->ah_sc); + struct ath5k_statistics *stats = &ah->ah_sc->stats; /* Read-And-Clear */ - stats->dot11ACKFailureCount += ath5k_hw_reg_read(ah, AR5K_ACK_FAIL); - stats->dot11RTSFailureCount += ath5k_hw_reg_read(ah, AR5K_RTS_FAIL); - stats->dot11RTSSuccessCount += ath5k_hw_reg_read(ah, AR5K_RTS_OK); - stats->dot11FCSErrorCount += ath5k_hw_reg_read(ah, AR5K_FCS_FAIL); - - /* XXX: Should we use this to track beacon count ? - * -we read it anyway to clear the register */ - ath5k_hw_reg_read(ah, AR5K_BEACON_CNT); - - /* Reset profile count registers on 5212*/ - if (ah->ah_version == AR5K_AR5212) { - ath5k_hw_reg_write(ah, 0, AR5K_PROFCNT_TX); - ath5k_hw_reg_write(ah, 0, AR5K_PROFCNT_RX); - ath5k_hw_reg_write(ah, 0, AR5K_PROFCNT_RXCLR); - ath5k_hw_reg_write(ah, 0, AR5K_PROFCNT_CYCLE); - } - - /* TODO: Handle ANI stats */ + stats->ack_fail += ath5k_hw_reg_read(ah, AR5K_ACK_FAIL); + stats->rts_fail += ath5k_hw_reg_read(ah, AR5K_RTS_FAIL); + stats->rts_ok += ath5k_hw_reg_read(ah, AR5K_RTS_OK); + stats->fcs_error += ath5k_hw_reg_read(ah, AR5K_FCS_FAIL); + stats->beacons += ath5k_hw_reg_read(ah, AR5K_BEACON_CNT); } /** @@ -421,12 +408,13 @@ void ath5k_hw_set_bssid_mask(struct ath5k_hw *ah, const u8 *mask) * (ACK etc). * * NOTE: RX DMA should be already enabled using ath5k_hw_start_rx_dma - * TODO: Init ANI here */ void ath5k_hw_start_rx_pcu(struct ath5k_hw *ah) { ATH5K_TRACE(ah->ah_sc); AR5K_REG_DISABLE_BITS(ah, AR5K_DIAG_SW, AR5K_DIAG_SW_DIS_RX); + + ath5k_ani_init(ah, ah->ah_ani_mode); } /** diff --git a/drivers/net/wireless/ath/ath5k/phy.c b/drivers/net/wireless/ath/ath5k/phy.c index cea3081..c044cb6 100644 --- a/drivers/net/wireless/ath/ath5k/phy.c +++ b/drivers/net/wireless/ath/ath5k/phy.c @@ -1102,13 +1102,18 @@ int ath5k_hw_channel(struct ath5k_hw *ah, struct ieee80211_channel *channel) PHY calibration \*****************/ +/** + * ath5k_hw_calibration_poll - fire software interrupt if we need to calibrate + * + * Called in Interrupt context, on every interrupt!!! + */ void ath5k_hw_calibration_poll(struct ath5k_hw *ah) { /* Calibration interval in jiffies */ unsigned long cal_intval; - cal_intval = msecs_to_jiffies(ah->ah_cal_intval * 1000); + cal_intval = msecs_to_jiffies(ATH5K_TUNE_CALIBRATION_INTERVAL_ANI); /* Initialize timestamp if needed */ if (!ah->ah_cal_tstamp) diff --git a/drivers/net/wireless/ath/ath5k/reg.h b/drivers/net/wireless/ath/ath5k/reg.h index 47f0493..45d62e9 100644 --- a/drivers/net/wireless/ath/ath5k/reg.h +++ b/drivers/net/wireless/ath/ath5k/reg.h @@ -212,10 +212,10 @@ * MIB control register */ #define AR5K_MIBC 0x0040 /* Register Address */ -#define AR5K_MIBC_COW 0x00000001 /* Warn test indicator */ +#define AR5K_MIBC_COW 0x00000001 /* Counter Overflow Warning */ #define AR5K_MIBC_FMC 0x00000002 /* Freeze MIB Counters */ -#define AR5K_MIBC_CMC 0x00000004 /* Clean MIB Counters */ -#define AR5K_MIBC_MCS 0x00000008 /* MIB counter strobe */ +#define AR5K_MIBC_CMC 0x00000004 /* Clear MIB Counters */ +#define AR5K_MIBC_MCS 0x00000008 /* MIB counter strobe, increment all */ /* * Timeout prescale register @@ -1516,7 +1516,14 @@ AR5K_NAV_5210 : AR5K_NAV_5211) /* - * RTS success register + * MIB counters: + * + * max value is 0xc000, if this is reached we get a MIB interrupt. + * they can be controlled via AR5K_MIBC and are cleared on read. + */ + +/* + * RTS success (MIB counter) */ #define AR5K_RTS_OK_5210 0x8090 #define AR5K_RTS_OK_5211 0x8088 @@ -1524,7 +1531,7 @@ AR5K_RTS_OK_5210 : AR5K_RTS_OK_5211) /* - * RTS failure register + * RTS failure (MIB counter) */ #define AR5K_RTS_FAIL_5210 0x8094 #define AR5K_RTS_FAIL_5211 0x808c @@ -1532,7 +1539,7 @@ AR5K_RTS_FAIL_5210 : AR5K_RTS_FAIL_5211) /* - * ACK failure register + * ACK failure (MIB counter) */ #define AR5K_ACK_FAIL_5210 0x8098 #define AR5K_ACK_FAIL_5211 0x8090 @@ -1540,7 +1547,7 @@ AR5K_ACK_FAIL_5210 : AR5K_ACK_FAIL_5211) /* - * FCS failure register + * FCS failure (MIB counter) */ #define AR5K_FCS_FAIL_5210 0x809c #define AR5K_FCS_FAIL_5211 0x8094 @@ -1667,11 +1674,17 @@ /* * Profile count registers + * + * These registers can be cleared and freezed with ATH5K_MIBC, but they do not + * generate a MIB interrupt. + * Instead of overflowing, they shift by one bit to the right. All registers + * shift together, i.e. when one reaches the max, all shift at the same time by + * one bit to the right. This way we should always get consistent values. */ #define AR5K_PROFCNT_TX 0x80ec /* Tx count */ #define AR5K_PROFCNT_RX 0x80f0 /* Rx count */ -#define AR5K_PROFCNT_RXCLR 0x80f4 /* Clear Rx count */ -#define AR5K_PROFCNT_CYCLE 0x80f8 /* Cycle count (?) */ +#define AR5K_PROFCNT_RXCLR 0x80f4 /* Busy count */ +#define AR5K_PROFCNT_CYCLE 0x80f8 /* Cycle counter */ /* * Quiet period control registers @@ -1758,7 +1771,7 @@ #define AR5K_CCK_FIL_CNT 0x8128 /* - * PHY Error Counters (?) + * PHY Error Counters (same masks as AR5K_PHY_ERR_FIL) */ #define AR5K_PHYERR_CNT1 0x812c #define AR5K_PHYERR_CNT1_MASK 0x8130 @@ -1766,6 +1779,9 @@ #define AR5K_PHYERR_CNT2 0x8134 #define AR5K_PHYERR_CNT2_MASK 0x8138 +/* if the PHY Error Counters reach this maximum, we get MIB interrupts */ +#define ATH5K_PHYERR_CNT_MAX 0x00c00000 + /* * TSF Threshold register (?) */ _______________________________________________ ath5k-devel mailing list ath5k-devel@lists.ath5k.org https://lists.ath5k.org/mailman/listinfo/ath5k-devel