2010-03-25 05:49:06

by Bruno Randolf

[permalink] [raw]
Subject: [PATCH 00/10] ANI for ath5k

This is ANI for ath5k. I have looked ath both the HAL and ath9k sources, while
implementing this, so the basic algorithm is the same. A notable difference is
that i do everything in a tasklet, while ath9k and HAL do larger parts in the
interrupt handler. Also i have only one routine for raising immunity, while
ath9k/HAL have separate triggers for CCK and OFDM errors.

Notes/Questions:

- HAL and ath9k use ANI status per channel. This might be useful if we often
change channel, but this is not the case, as far as i can see. Any other
reason for that?

- HAL and ath9k use only different 2 values in a 5 value array of noise
immunity levels (making it essentially an on/off setting) while there are 5
levels mentioned in the ANI documents. I guess there must be a reason for
this - No?

- Why is CCK weak signal detection never used (neither turned on or off)?

- There is some confusion which initial values to use. The HAL uses all 0 and
ofdm weak signal detection enabled. ath9k has all 0 for AP mode (including
ofdm weak sig off), while using ofdm weak sig on and a spur level of 7 for
STA mode.

- Also there is some inconsistency about which parameters to control in AP
mode: ath9k only use spur and firstep in 2GHz bands and no ANI control at all
in 5GHz!? the HAL uses noise and spur in both bands, but no fir control. for
now, i use noise, spur and fir in AP mode.

- In some situations it is good to be able to manually control sesitivity (e.g.
tuning long-distance links; not trusting the ANI algo in general ;)), so i
would like to export ANI controls to userspace. Right now this is possible
thru debugfs, but this is just for testing. How should the ANI settings be
controlled properly? iw? configs? sysfs? What's the interface of choice
nowadays?

TODO / Ideas:

- Improve beacon averaging algorithm for IBSS: Maybe we should keep track of
the average RSSI on a per-node basis and use the minimum of those in order
not to loose far away nodes? The other option would be to limit the
parameters we control similar to AP mode.

- Older HW (srev < 0x59) use rx filter and reports phy errors as they appear in
the rx descriptors. All is in place for this to work, but i never saw the
"right" errors (OFDM and CCK timing), even though i'm pretty sure i should get
them. This needs to be tested and debugged more, but i have to focus on newer
hardware now.

- Maybe we could compare the "busy time" to the "listen time" and this way
recognize if we have many false detects (- this is how i originally found out
that i need to work on ANI).

I hope that this will solve thruput problems! For me it nearly doubled thruput
in a noisy 2Ghz band. :)

bruno

---

Bruno Randolf (10):
ath5k: remove static calibration interval variable
ath5k: remove the use of SWI interrupt
ath5k: optimize ath5k_hw_calibration_poll
ath5k: move ath5k_hw_calibration_poll to base.c
ath5k: keep beacon RSSI average
ath5k: initialize default noise floor
ath5k: simplify MIB counters
ath5k: update phy errors codes
ath5k: add capability flag for phyerror counters
ath5k: Adaptive Noise Immunity (ANI) Implementation


drivers/net/wireless/ath/ath5k/Makefile | 1
drivers/net/wireless/ath/ath5k/ani.c | 741 +++++++++++++++++++++++++++++++
drivers/net/wireless/ath/ath5k/ani.h | 104 ++++
drivers/net/wireless/ath/ath5k/ath5k.h | 68 ++-
drivers/net/wireless/ath/ath5k/attach.c | 2
drivers/net/wireless/ath/ath5k/base.c | 97 +++-
drivers/net/wireless/ath/ath5k/base.h | 20 +
drivers/net/wireless/ath/ath5k/caps.c | 6
drivers/net/wireless/ath/ath5k/debug.c | 170 +++++++
drivers/net/wireless/ath/ath5k/debug.h | 2
drivers/net/wireless/ath/ath5k/desc.c | 1
drivers/net/wireless/ath/ath5k/desc.h | 35 +
drivers/net/wireless/ath/ath5k/pcu.c | 40 +-
drivers/net/wireless/ath/ath5k/phy.c | 22 -
drivers/net/wireless/ath/ath5k/reg.h | 36 +-
15 files changed, 1237 insertions(+), 108 deletions(-)
create mode 100644 drivers/net/wireless/ath/ath5k/ani.c
create mode 100644 drivers/net/wireless/ath/ath5k/ani.h

--
Signature


2010-03-26 01:21:15

by Derek Smithies

[permalink] [raw]
Subject: Re: [ath5k-devel] [PATCH 00/10] ANI for ath5k


On Fri, 26 Mar 2010, Bruno Randolf wrote:

>>>> - Improve beacon averaging algorithm for IBSS:
>>> is to turn ani off. which means the issue you raised else where of
>>> providing reasonable userland controls (not via debugfs) becomes
>>> important.
>>
>> If ANI helps IBSS so much then why not just default to turning it
>> off within the driver?
>
> i would say in most standard cases, IBSS nodes are close together or at a
> similar distance, so it does make sense to use ANI by default.
Nope - I disagree. You have made a dangerous assumption, to asume
that
IBSS node are close together or at a similar distance

if you have a network of ibss nodes, and then look at the rate tables of
the different nodes (minstrel is great here - it gives a fair and accurte
represetation of the best rate possible to other nodes) you see there is
some variation in the optimal rates between nodes.Which suggests that
using some automatic ani algorithm in an ibss network will foreever lower
performance for those nodes at the edge of the network.

>
> as well, we all agree that there should be userland controls. the questions is
> just about the interface to use.

Definately - need userland controls. The default for IBSS should have
ANI off. If the user wants to "futz" with it, they can. With ANI off for
IBSS, you ensure that on startup, the network has the greatest chance of
working.

Derek.
--
Derek Smithies Ph.D.
IndraNet Technologies Ltd.
ph +64 3 365 6485
Web: http://www.indranet-technologies.com/

"How did you make it work??"
"Oh, the usual, get everything right".

2010-03-26 20:34:25

by Derek Smithies

[permalink] [raw]
Subject: Re: [ath5k-devel] [PATCH 00/10] ANI for ath5k


On Fri, 26 Mar 2010, Bruno Randolf wrote:
>
> yeah, we all know how great minstrel is... really.
Thanks for the praise on Minstrel.
Felix has done, and is doing, some good work on getting it into the
kernel. And of course, his current work with minstrel in 802.11 N

>
> i said in most *standard* cases - the standard case for IBSS is still 3 or 5
> guys sitting in a room, or close by, wanting to exchange some data. we should
> have good performance for that, therefore ANI on by default.
It is, essentially, wishful thinking to assume one can have automatic ANI
in IBSS mode.

Given that we are designing a code base to have general use, we have to
design code that will work for the most number of people "out of the box",
and they tune it for their situation.
To get IBSS to work best "out of the box" (i.e. with no tuning) for the
most number of people you have to have ANI off

I had a siution with a IBSS network I am running here, with nodes all
indoors. OK, they are all madwifi based, but that simply means they have
an ANI algorithm.
At one end of the building, there were four nodes. At the far end of
the building, there was one node.
When the remote node talked to the any of the other four, rates were
always asymmetrical. TCP throughput in one direction was half of TCP
throughput in the other direction.
Why the assymmetry? Cause the four nodes had wound their ANI levels down.
By copying sensitivity setting code from the open sourced HAL, and with
ANI off, the measured TCP throughput rates became much more symmetrical
(same in both directions).

I have used madwifi for a number of years now. In networks installed
outdoors, everything worked fine (initially). As the days went by,
measured throughputs dropped.
The drop in throughput was sometimes caused by ramping,
(ticket 1154, also known as transmission delay)
sometimes by the rate algorithm failing (sample etc is quite poor)
but also by the nodes lowering sensitivy levels.
There was sometimes a considerable delay before nodes would join and
communicate on a network (this could have net80211 code, or ani, or ??)

> again - i'm not willing to discuss this based on guesses and assumptions.
Me too. asumptions are bad - code based on assumptions is bad.

So the question is then:
What is the evidence for ANI helping in IBSS mode?
- only when all nodes are an equal distance away from each other - you
have a very small network (probably 2 nodes, ANI will be great here)

> if you have test results showing that a specific ANI setting actually
> prevented a node from joining an IBSS, i'm happy to resume this discussion.
Ok, I don't (definatively) have proof of this.
I do have proof that automatic ANI in an IBSS network will lead to
assymetric TCP throughputs.

Derek.
--
Derek Smithies Ph.D.
IndraNet Technologies Ltd.
Email: [email protected]
ph +64 3 365 6485
Web: http://www.indranet-technologies.com/


2010-03-25 05:49:32

by Bruno Randolf

[permalink] [raw]
Subject: [PATCH 05/10] ath5k: keep beacon RSSI average

Keep an exponentially weighted moving average of the beacon RSSI in our BSS.
It will be used by the ANI implementation.

The averaging algorithm is copied from rt2x00, Thanks :)

Signed-off-by: Bruno Randolf <[email protected]>
---
drivers/net/wireless/ath/ath5k/ath5k.h | 35 ++++++++++++++++++++++++++++++++
drivers/net/wireless/ath/ath5k/base.c | 21 +++++++++++++++++++
2 files changed, 56 insertions(+), 0 deletions(-)

diff --git a/drivers/net/wireless/ath/ath5k/ath5k.h b/drivers/net/wireless/ath/ath5k/ath5k.h
index 6334294..cefd28d 100644
--- a/drivers/net/wireless/ath/ath5k/ath5k.h
+++ b/drivers/net/wireless/ath/ath5k/ath5k.h
@@ -992,6 +992,15 @@ struct ath5k_nfcal_hist
s16 nfval[ATH5K_NF_CAL_HIST_MAX]; /* last few noise floors */
};

+/**
+ * struct avg_val - Helper structure for average calculation
+ * @avg: contains the actual average value
+ * @avg_weight: is used internally during calculation to prevent rounding errors
+ */
+struct ath5k_avg_val {
+ int avg;
+ int avg_weight;
+};

/***************************************\
HARDWARE ABSTRACTION LAYER STRUCTURE
@@ -1096,6 +1105,9 @@ struct ath5k_hw {

struct ath5k_nfcal_hist ah_nfcal_hist;

+ /* average beacon RSSI in our BSS (used by ANI) */
+ struct ath5k_avg_val ah_beacon_rssi_avg;
+
/* noise floor from last periodic calibration */
s32 ah_noise_floor;

@@ -1305,4 +1317,27 @@ static inline u32 ath5k_hw_bitswap(u32 val, unsigned int bits)
return retval;
}

+#define AVG_SAMPLES 8
+#define AVG_FACTOR 1000
+
+/**
+ * ath5k_moving_average - Exponentially weighted moving average
+ * @avg: average structure
+ * @val: current value
+ *
+ * This implementation make use of a struct ath5k_avg_val to prevent rounding
+ * errors.
+ */
+static inline struct ath5k_avg_val
+ath5k_moving_average(const struct ath5k_avg_val avg, const int val)
+{
+ struct ath5k_avg_val new;
+ new.avg_weight = avg.avg_weight ?
+ (((avg.avg_weight * ((AVG_SAMPLES) - 1)) +
+ (val * (AVG_FACTOR))) / (AVG_SAMPLES)) :
+ (val * (AVG_FACTOR));
+ new.avg = new.avg_weight / (AVG_FACTOR);
+ return new;
+}
+
#endif
diff --git a/drivers/net/wireless/ath/ath5k/base.c b/drivers/net/wireless/ath/ath5k/base.c
index f60d84f..ba2fad2 100644
--- a/drivers/net/wireless/ath/ath5k/base.c
+++ b/drivers/net/wireless/ath/ath5k/base.c
@@ -1803,6 +1803,25 @@ ath5k_check_ibss_tsf(struct ath5k_softc *sc, struct sk_buff *skb,
}
}

+static void
+ath5k_update_beacon_rssi(struct ath5k_softc *sc, struct sk_buff *skb, int rssi)
+{
+ struct ieee80211_mgmt *mgmt = (struct ieee80211_mgmt *)skb->data;
+ struct ath5k_hw *ah = sc->ah;
+ struct ath_common *common = ath5k_hw_common(ah);
+
+ /* only beacons from our BSSID */
+ if (!ieee80211_is_beacon(mgmt->frame_control) ||
+ memcmp(mgmt->bssid, common->curbssid, ETH_ALEN) != 0)
+ return;
+
+ ah->ah_beacon_rssi_avg = ath5k_moving_average(ah->ah_beacon_rssi_avg,
+ rssi);
+
+ /* in IBSS mode we should keep RSSI statistics per neighbour */
+ /* le16_to_cpu(mgmt->u.beacon.capab_info) & WLAN_CAPABILITY_IBSS */
+}
+
/*
* Compute padding position. skb must contains an IEEE 802.11 frame
*/
@@ -2022,6 +2041,8 @@ accept:

ath5k_debug_dump_skb(sc, skb, "RX ", 0);

+ ath5k_update_beacon_rssi(sc, skb, rs.rs_rssi);
+
/* check beacons in IBSS mode */
if (sc->opmode == NL80211_IFTYPE_ADHOC)
ath5k_check_ibss_tsf(sc, skb, rxs);


2010-03-26 00:44:32

by Luis R. Rodriguez

[permalink] [raw]
Subject: Re: [ath5k-devel] [PATCH 00/10] ANI for ath5k

On Thu, Mar 25, 2010 at 05:27:57PM -0700, Bruno Randolf wrote:
> On Friday 26 March 2010 06:13:11 Luis R. Rodriguez wrote:
> > > > - Improve beacon averaging algorithm for IBSS: Maybe we should keep
> > > > track of
> > > >
> > > > the average RSSI on a per-node basis and use the minimum of those in
> > > > order not to loose far away nodes? The other option would be to limit
> > > > the parameters we control similar to AP mode.
> > >
> > > We have talked about this before. I thought that we agreed
> > > that adhoc nodes may
> > >
> > > a)be activated at any time
> > > b)be positioned at an unknown distance apart
> > >
> > > it therefore makes no sense to average the RSSI over the current nodes.
> > > If all the current nodes in the network are close by, the ani algorithm
> > > will wind the sensitivity down. Consequently, if a remote node is
> > > started, it will not be able to connect. Yet, the remote node should
> > > have been able to connect as the slot times etc were long enough,
> > > and the link budget is fine...
>
> i'm not sure how big the effect of ANI is - would it really shut the remote
> node out completely? or would just performance degrade? also beacons are sent
> at the lowest rate, so they will travel far. this needs to be tested! it
> doesnt make sense to discuss this based on hypothetical assumptions.
>
> > > So yes, the only way to
> > >
> > > > - Improve beacon averaging algorithm for IBSS:
> > > is to turn ani off. which means the issue you raised else where of
> > > providing reasonable userland controls (not via debugfs) becomes
> > > important.
> >
> > If ANI helps IBSS so much then why not just default to turning it
> > off within the driver?
>
> i would say in most standard cases, IBSS nodes are close together or at a
> similar distance, so it does make sense to use ANI by default.
>
> as well, we all agree that there should be userland controls. the questions is
> just about the interface to use.

ANI is specific to Atheros cards so debugfs would do it. If we want to
have a more rigit API we could use a configfs entry for ath9k.

Luis

2010-03-26 01:32:41

by Luis R. Rodriguez

[permalink] [raw]
Subject: Re: [ath5k-devel] [PATCH 00/10] ANI for ath5k

On Thu, Mar 25, 2010 at 06:21:50PM -0700, Derek Smithies wrote:
>
> On Fri, 26 Mar 2010, Bruno Randolf wrote:
>
> >>>> - Improve beacon averaging algorithm for IBSS:
> >>> is to turn ani off. which means the issue you raised else where of
> >>> providing reasonable userland controls (not via debugfs) becomes
> >>> important.
> >>
> >> If ANI helps IBSS so much then why not just default to turning it
> >> off within the driver?
> >
> > i would say in most standard cases, IBSS nodes are close together or at a
> > similar distance, so it does make sense to use ANI by default.
> Nope - I disagree. You have made a dangerous assumption, to asume
> that
> IBSS node are close together or at a similar distance
>
> if you have a network of ibss nodes, and then look at the rate tables of
> the different nodes (minstrel is great here - it gives a fair and accurte
> represetation of the best rate possible to other nodes) you see there is
> some variation in the optimal rates between nodes.Which suggests that
> using some automatic ani algorithm in an ibss network will foreever lower
> performance for those nodes at the edge of the network.

Can't we use the coverage class for this stuff? Only thing is it would need
to be set. Do we even send the coverage class IE ? I don't think we do, that
part of the country IE and last time I reviewed the country IE building
on hostapd it didn't have it. IBSS is handled on mac80211 though so...
we'd have to add it there. I guess its a lot of work, but would help
with this no?

> > as well, we all agree that there should be userland controls. the questions is
> > just about the interface to use.
>
> Definately - need userland controls. The default for IBSS should have
> ANI off. If the user wants to "futz" with it, they can. With ANI off for
> IBSS, you ensure that on startup, the network has the greatest chance of
> working.

Luis

2010-03-25 05:49:26

by Bruno Randolf

[permalink] [raw]
Subject: [PATCH 04/10] ath5k: move ath5k_hw_calibration_poll to base.c

From: Bruno Randolf <[email protected]>

It's not a phy related funtion; It has more to do with the interrupt handler
and tasklet scheduling, so it belongs to base.c.

Signed-off-by: Bruno Randolf <[email protected]>
---
drivers/net/wireless/ath/ath5k/ath5k.h | 1 -
drivers/net/wireless/ath/ath5k/base.c | 15 ++++++++++++++-
drivers/net/wireless/ath/ath5k/phy.c | 13 -------------
3 files changed, 14 insertions(+), 15 deletions(-)

diff --git a/drivers/net/wireless/ath/ath5k/ath5k.h b/drivers/net/wireless/ath/ath5k/ath5k.h
index 3f56d9e..6334294 100644
--- a/drivers/net/wireless/ath/ath5k/ath5k.h
+++ b/drivers/net/wireless/ath/ath5k/ath5k.h
@@ -1254,7 +1254,6 @@ int ath5k_hw_channel(struct ath5k_hw *ah, struct ieee80211_channel *channel);
void ath5k_hw_init_nfcal_hist(struct ath5k_hw *ah);
int ath5k_hw_phy_calibrate(struct ath5k_hw *ah,
struct ieee80211_channel *channel);
-void ath5k_hw_calibration_poll(struct ath5k_hw *ah);
/* Spur mitigation */
bool ath5k_hw_chan_has_spur_noise(struct ath5k_hw *ah,
struct ieee80211_channel *channel);
diff --git a/drivers/net/wireless/ath/ath5k/base.c b/drivers/net/wireless/ath/ath5k/base.c
index 539a6d5..f60d84f 100644
--- a/drivers/net/wireless/ath/ath5k/base.c
+++ b/drivers/net/wireless/ath/ath5k/base.c
@@ -2625,6 +2625,19 @@ ath5k_stop_hw(struct ath5k_softc *sc)
return ret;
}

+static void
+ath5k_intr_calibration_poll(struct ath5k_hw *ah)
+{
+ if (time_is_before_eq_jiffies(ah->ah_cal_next_full)) {
+ ah->ah_cal_next_full = jiffies +
+ msecs_to_jiffies(ATH5K_TUNE_CALIBRATION_INTERVAL_FULL);
+ tasklet_schedule(&ah->ah_sc->calib);
+ }
+ /* we could use SWI to generate enough interrupts to meet our
+ * calibration interval requirements, if necessary:
+ * AR5K_REG_ENABLE_BITS(ah, AR5K_CR, AR5K_CR_SWI); */
+}
+
static irqreturn_t
ath5k_intr(int irq, void *dev_id)
{
@@ -2689,7 +2702,7 @@ ath5k_intr(int irq, void *dev_id)
if (unlikely(!counter))
ATH5K_WARN(sc, "too many interrupts, giving up for now\n");

- ath5k_hw_calibration_poll(ah);
+ ath5k_intr_calibration_poll(ah);

return IRQ_HANDLED;
}
diff --git a/drivers/net/wireless/ath/ath5k/phy.c b/drivers/net/wireless/ath/ath5k/phy.c
index 60241ad..7f09c59 100644
--- a/drivers/net/wireless/ath/ath5k/phy.c
+++ b/drivers/net/wireless/ath/ath5k/phy.c
@@ -1102,19 +1102,6 @@ int ath5k_hw_channel(struct ath5k_hw *ah, struct ieee80211_channel *channel)
PHY calibration
\*****************/

-void
-ath5k_hw_calibration_poll(struct ath5k_hw *ah)
-{
- if (time_is_before_eq_jiffies(ah->ah_cal_next_full)) {
- ah->ah_cal_next_full = jiffies +
- msecs_to_jiffies(ATH5K_TUNE_CALIBRATION_INTERVAL_FULL);
- tasklet_schedule(&ah->ah_sc->calib);
- }
- /* we could use SWI to generate enough interrupts to meet our
- * calibration interval requirements, if necessary:
- * AR5K_REG_ENABLE_BITS(ah, AR5K_CR, AR5K_CR_SWI); */
-}
-
static int sign_extend(int val, const int nbits)
{
int order = BIT(nbits-1);


2010-03-25 21:10:20

by Derek Smithies

[permalink] [raw]
Subject: Re: [ath5k-devel] [PATCH 00/10] ANI for ath5k

Hi,
On Thu, 25 Mar 2010, Bruno Randolf wrote:
> This is ANI for ath5k.
Fantastic!!
I have used your initial ani patch set here, and it did help througput
etc.
>
> - Improve beacon averaging algorithm for IBSS: Maybe we should keep track of
> the average RSSI on a per-node basis and use the minimum of those in order
> not to loose far away nodes? The other option would be to limit the
> parameters we control similar to AP mode.

We have talked about this before. I thought that we agreed
that adhoc nodes may
a)be activated at any time
b)be positioned at an unknown distance apart

it therefore makes no sense to average the RSSI over the current nodes.
If all the current nodes in the network are close by, the ani algorithm
will wind the sensitivity down. Consequently, if a remote node is
started, it will not be able to connect. Yet, the remote node should
have been able to connect as the slot times etc were long enough,
and the link budget is fine...

So yes, the only way to
> - Improve beacon averaging algorithm for IBSS:
is to turn ani off. which means the issue you raised else where of
providing reasonable userland controls (not via debugfs) becomes
important.


Derek.

---
Derek Smithies Ph.D.
IndraNet Technologies Ltd.
ph +64 3 365 6485
Web: http://www.indranet-technologies.com/

"How did you make it work??"
"Oh, the usual, get everything right".

2010-03-25 05:49:21

by Bruno Randolf

[permalink] [raw]
Subject: [PATCH 03/10] ath5k: optimize ath5k_hw_calibration_poll

Optimize ath5k_hw_calibration_poll() since it is called on every singe
interrupt.

Signed-off-by: Bruno Randolf <[email protected]>
---
drivers/net/wireless/ath/ath5k/ath5k.h | 2 +-
drivers/net/wireless/ath/ath5k/phy.c | 20 ++++++--------------
2 files changed, 7 insertions(+), 15 deletions(-)

diff --git a/drivers/net/wireless/ath/ath5k/ath5k.h b/drivers/net/wireless/ath/ath5k/ath5k.h
index d84ccde..3f56d9e 100644
--- a/drivers/net/wireless/ath/ath5k/ath5k.h
+++ b/drivers/net/wireless/ath/ath5k/ath5k.h
@@ -1100,7 +1100,7 @@ struct ath5k_hw {
s32 ah_noise_floor;

/* Calibration timestamp */
- unsigned long ah_cal_tstamp;
+ unsigned long ah_cal_next_full;

/* Calibration mask */
u8 ah_cal_mask;
diff --git a/drivers/net/wireless/ath/ath5k/phy.c b/drivers/net/wireless/ath/ath5k/phy.c
index cbdc511..60241ad 100644
--- a/drivers/net/wireless/ath/ath5k/phy.c
+++ b/drivers/net/wireless/ath/ath5k/phy.c
@@ -1105,22 +1105,14 @@ int ath5k_hw_channel(struct ath5k_hw *ah, struct ieee80211_channel *channel)
void
ath5k_hw_calibration_poll(struct ath5k_hw *ah)
{
- /* Calibration interval in jiffies */
- unsigned long cal_intval;
-
- cal_intval = msecs_to_jiffies(ATH5K_TUNE_CALIBRATION_INTERVAL_FULL);
-
- /* Initialize timestamp if needed */
- if (!ah->ah_cal_tstamp)
- ah->ah_cal_tstamp = jiffies;
-
- /* For now we always do full calibration
- * Mark software interrupt mask and fire software
- * interrupt (bit gets auto-cleared) */
- if (time_is_before_eq_jiffies(ah->ah_cal_tstamp + cal_intval)) {
- ah->ah_cal_tstamp = jiffies;
+ if (time_is_before_eq_jiffies(ah->ah_cal_next_full)) {
+ ah->ah_cal_next_full = jiffies +
+ msecs_to_jiffies(ATH5K_TUNE_CALIBRATION_INTERVAL_FULL);
tasklet_schedule(&ah->ah_sc->calib);
}
+ /* we could use SWI to generate enough interrupts to meet our
+ * calibration interval requirements, if necessary:
+ * AR5K_REG_ENABLE_BITS(ah, AR5K_CR, AR5K_CR_SWI); */
}

static int sign_extend(int val, const int nbits)


2010-03-25 05:49:37

by Bruno Randolf

[permalink] [raw]
Subject: [PATCH 06/10] ath5k: initialize default noise floor

Initialize noise floor variable with a default of -95. This was used
uninitialized in the signal strength (RSSI -> dBm) conversion until the first
noise floor calibration was completed.

Signed-off-by: Bruno Randolf <[email protected]>
---
drivers/net/wireless/ath/ath5k/attach.c | 1 +
1 files changed, 1 insertions(+), 0 deletions(-)

diff --git a/drivers/net/wireless/ath/ath5k/attach.c b/drivers/net/wireless/ath/ath5k/attach.c
index dd4099a..f80d3d5 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_noise_floor = -95; /* until first NF calibration is run */

/*
* Find the mac version


2010-03-25 05:49:15

by Bruno Randolf

[permalink] [raw]
Subject: [PATCH 02/10] ath5k: remove the use of SWI interrupt

We don't need to generate a software interrupt (SWI) just to schedule a tasklet
- we can just schedule the tasklet directly.

Rename constants, names, etc to reflect the fact that we don't use SWI any more.

Also move the flag handling into the tasklet and prepare it to behave correctly
when there are multiple flags present.

Signed-off-by: Bruno Randolf <[email protected]>
---
drivers/net/wireless/ath/ath5k/ath5k.h | 12 ++++++------
drivers/net/wireless/ath/ath5k/base.c | 11 +++--------
drivers/net/wireless/ath/ath5k/phy.c | 3 +--
3 files changed, 10 insertions(+), 16 deletions(-)

diff --git a/drivers/net/wireless/ath/ath5k/ath5k.h b/drivers/net/wireless/ath/ath5k/ath5k.h
index a3019ee..d84ccde 100644
--- a/drivers/net/wireless/ath/ath5k/ath5k.h
+++ b/drivers/net/wireless/ath/ath5k/ath5k.h
@@ -890,10 +890,10 @@ enum ath5k_int {
AR5K_INT_NOCARD = 0xffffffff
};

-/* Software interrupts used for calibration */
-enum ath5k_software_interrupt {
- AR5K_SWI_FULL_CALIBRATION = 0x01,
- AR5K_SWI_SHORT_CALIBRATION = 0x02,
+/* mask which calibration is active at the moment */
+enum ath5k_calibration_mask {
+ AR5K_CALIBRATION_FULL = 0x01,
+ AR5K_CALIBRATION_SHORT = 0x02,
};

/*
@@ -1102,8 +1102,8 @@ struct ath5k_hw {
/* Calibration timestamp */
unsigned long ah_cal_tstamp;

- /* Software interrupt mask */
- u8 ah_swi_mask;
+ /* Calibration mask */
+ u8 ah_cal_mask;

/*
* Function pointers
diff --git a/drivers/net/wireless/ath/ath5k/base.c b/drivers/net/wireless/ath/ath5k/base.c
index 39d5800..539a6d5 100644
--- a/drivers/net/wireless/ath/ath5k/base.c
+++ b/drivers/net/wireless/ath/ath5k/base.c
@@ -2507,7 +2507,7 @@ 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;
ret = ath5k_reset(sc, NULL);
if (ret)
goto done;
@@ -2673,9 +2673,6 @@ ath5k_intr(int irq, void *dev_id)
if (status & AR5K_INT_BMISS) {
/* TODO */
}
- if (status & AR5K_INT_SWI) {
- tasklet_schedule(&sc->calib);
- }
if (status & AR5K_INT_MIB) {
/*
* These stats are also used for ANI i think
@@ -2716,8 +2713,7 @@ ath5k_tasklet_calibrate(unsigned long data)
struct ath5k_hw *ah = sc->ah;

/* Only full calibration for now */
- if (ah->ah_swi_mask != AR5K_SWI_FULL_CALIBRATION)
- return;
+ ah->ah_cal_mask |= AR5K_CALIBRATION_FULL;

/* Stop queues so that calibration
* doesn't interfere with tx */
@@ -2740,11 +2736,10 @@ ath5k_tasklet_calibrate(unsigned long data)
ieee80211_frequency_to_channel(
sc->curchan->center_freq));

- ah->ah_swi_mask = 0;
-
/* Wake queues */
ieee80211_wake_queues(sc->hw);

+ ah->ah_cal_mask &= ~AR5K_CALIBRATION_FULL;
}


diff --git a/drivers/net/wireless/ath/ath5k/phy.c b/drivers/net/wireless/ath/ath5k/phy.c
index 07f59b1..cbdc511 100644
--- a/drivers/net/wireless/ath/ath5k/phy.c
+++ b/drivers/net/wireless/ath/ath5k/phy.c
@@ -1119,8 +1119,7 @@ ath5k_hw_calibration_poll(struct ath5k_hw *ah)
* interrupt (bit gets auto-cleared) */
if (time_is_before_eq_jiffies(ah->ah_cal_tstamp + cal_intval)) {
ah->ah_cal_tstamp = jiffies;
- ah->ah_swi_mask = AR5K_SWI_FULL_CALIBRATION;
- AR5K_REG_ENABLE_BITS(ah, AR5K_CR, AR5K_CR_SWI);
+ tasklet_schedule(&ah->ah_sc->calib);
}
}



2010-03-26 00:28:07

by Bruno Randolf

[permalink] [raw]
Subject: Re: [ath5k-devel] [PATCH 00/10] ANI for ath5k

On Friday 26 March 2010 06:13:11 Luis R. Rodriguez wrote:
> > > - Improve beacon averaging algorithm for IBSS: Maybe we should keep
> > > track of
> > >
> > > the average RSSI on a per-node basis and use the minimum of those in
> > > order not to loose far away nodes? The other option would be to limit
> > > the parameters we control similar to AP mode.
> >
> > We have talked about this before. I thought that we agreed
> > that adhoc nodes may
> >
> > a)be activated at any time
> > b)be positioned at an unknown distance apart
> >
> > it therefore makes no sense to average the RSSI over the current nodes.
> > If all the current nodes in the network are close by, the ani algorithm
> > will wind the sensitivity down. Consequently, if a remote node is
> > started, it will not be able to connect. Yet, the remote node should
> > have been able to connect as the slot times etc were long enough,
> > and the link budget is fine...

i'm not sure how big the effect of ANI is - would it really shut the remote
node out completely? or would just performance degrade? also beacons are sent
at the lowest rate, so they will travel far. this needs to be tested! it
doesnt make sense to discuss this based on hypothetical assumptions.

> > So yes, the only way to
> >
> > > - Improve beacon averaging algorithm for IBSS:
> > is to turn ani off. which means the issue you raised else where of
> > providing reasonable userland controls (not via debugfs) becomes
> > important.
>
> If ANI helps IBSS so much then why not just default to turning it
> off within the driver?

i would say in most standard cases, IBSS nodes are close together or at a
similar distance, so it does make sense to use ANI by default.

as well, we all agree that there should be userland controls. the questions is
just about the interface to use.

bruno

2010-03-29 02:26:57

by Bruno Randolf

[permalink] [raw]
Subject: Re: [ath5k-devel] [PATCH 10/10] ath5k: Adaptive Noise Immunity (ANI) Implementation

On Monday 29 March 2010 11:02:12 Bob Copeland wrote:
> On Thu, Mar 25, 2010 at 02:49:52PM +0900, Bruno Randolf wrote:
> > + } else if (strncmp(buf, "noise-low", 8) == 0) {
> > + ath5k_ani_set_noise_immunity_level(sc->ah, 0);
> > + } else if (strncmp(buf, "noise-high", 9) == 0) {
>
> The number of characters here is off-by-one

ah, thanks!

> I also tried setting noise-high while in auto mode, but then the ani
> debug file still showed the values at 0, is that to be expected?

in auto mode, the manual settings may be changed at any time, according to the
algorithm. if you don't want this to happen, you can echo ani-off, which will
just stop the automatic mode and keep the last values it used, or echo sens-
high or sens-low which will also stop the auto mode, plus set the min/max
values.

> On my hardware in my location, I don't really notice much of a difference
> with/without ANI, but I definitely don't get any MIB storms, so it
> gets my thumbs up. I can post my ani file if you're interested.

so you have decent truput with and without ani? that probably means not much
interference (OFDM and CCK error counters should be low).

bruno

2010-03-27 05:18:32

by Bruno Randolf

[permalink] [raw]
Subject: Re: [ath5k-devel] [PATCH 00/10] ANI for ath5k

On Saturday 27 March 2010 05:34:21 Derek Smithies wrote:
> It is, essentially, wishful thinking to assume one can have automatic ANI
> in IBSS mode.
>
> Given that we are designing a code base to have general use, we have to
> design code that will work for the most number of people "out of the box",
> and they tune it for their situation.
> To get IBSS to work best "out of the box" (i.e. with no tuning) for the
> most number of people you have to have ANI off
>
> I had a siution with a IBSS network I am running here, with nodes all
> indoors. OK, they are all madwifi based, but that simply means they have
> an ANI algorithm.
> At one end of the building, there were four nodes. At the far end of
> the building, there was one node.
> When the remote node talked to the any of the other four, rates were
> always asymmetrical. TCP throughput in one direction was half of TCP
> throughput in the other direction.
> Why the assymmetry? Cause the four nodes had wound their ANI levels down.
> By copying sensitivity setting code from the open sourced HAL, and with
> ANI off, the measured TCP throughput rates became much more symmetrical
> (same in both directions).
>
> I have used madwifi for a number of years now. In networks installed
> outdoors, everything worked fine (initially). As the days went by,
> measured throughputs dropped.
> The drop in throughput was sometimes caused by ramping,
> (ticket 1154, also known as transmission delay)
> sometimes by the rate algorithm failing (sample etc is quite poor)
> but also by the nodes lowering sensitivy levels.
> There was sometimes a considerable delay before nodes would join and
> communicate on a network (this could have net80211 code, or ani, or ??)
>
> > again - i'm not willing to discuss this based on guesses and assumptions.
>
> Me too. asumptions are bad - code based on assumptions is bad.
>
> So the question is then:
> What is the evidence for ANI helping in IBSS mode?
> - only when all nodes are an equal distance away from each other - you
> have a very small network (probably 2 nodes, ANI will be great here)
>
> > if you have test results showing that a specific ANI setting actually
> > prevented a node from joining an IBSS, i'm happy to resume this
> > discussion.
>
> Ok, I don't (definatively) have proof of this.
> I do have proof that automatic ANI in an IBSS network will lead to
> assymetric TCP throughputs.

still i think that you are too fast in your conclusion that ANI needs to be
completely disabled for IBSS mode in ath5k - based on some strange experience
you had with madwifi. madwifi and the HAL do a lot of crazy things, and we
don't even know how ANI was implemented exactly in the HAL you used for
testing (was it the same like the open source version? was there a bug?).

* maybe thruput would be good enough if we considered the minimum beacon
RSSI?

* maybe we should avoid to tune one specific parameter in IBSS? (like AP for
example, only changes noise immunity level, firstep level and spur level)

* maybe there would be another way to improve or fix the algorithm?

would it be possible that you could reproduce this situation with ath5k? if
yes, could you find out which ANI parameter you had to change to improve
thruput?

bruno

2010-03-25 21:13:13

by Luis R. Rodriguez

[permalink] [raw]
Subject: Re: [ath5k-devel] [PATCH 00/10] ANI for ath5k

On Thu, Mar 25, 2010 at 02:10:55PM -0700, Derek Smithies wrote:
> Hi,
> On Thu, 25 Mar 2010, Bruno Randolf wrote:
> > This is ANI for ath5k.
> Fantastic!!
> I have used your initial ani patch set here, and it did help througput
> etc.
> >
> > - Improve beacon averaging algorithm for IBSS: Maybe we should keep track of
> > the average RSSI on a per-node basis and use the minimum of those in order
> > not to loose far away nodes? The other option would be to limit the
> > parameters we control similar to AP mode.
>
> We have talked about this before. I thought that we agreed
> that adhoc nodes may
> a)be activated at any time
> b)be positioned at an unknown distance apart
>
> it therefore makes no sense to average the RSSI over the current nodes.
> If all the current nodes in the network are close by, the ani algorithm
> will wind the sensitivity down. Consequently, if a remote node is
> started, it will not be able to connect. Yet, the remote node should
> have been able to connect as the slot times etc were long enough,
> and the link budget is fine...
>
> So yes, the only way to
> > - Improve beacon averaging algorithm for IBSS:
> is to turn ani off. which means the issue you raised else where of
> providing reasonable userland controls (not via debugfs) becomes
> important.

If ANI helps IBSS so much then why not just default to turning it
off within the driver?

Luis

2010-03-26 00:53:57

by Bruno Randolf

[permalink] [raw]
Subject: Re: [ath5k-devel] [PATCH 00/10] ANI for ath5k

On Friday 26 March 2010 09:44:31 Luis R. Rodriguez wrote:
> > as well, we all agree that there should be userland controls. the
> > questions is just about the interface to use.
>
> ANI is specific to Atheros cards so debugfs would do it. If we want to
> have a more rigit API we could use a configfs entry for ath9k.

debugfs definetly is the wrong place! users might compile the module with
debugging disabled (for performance reasons, e.g.).

are you sure about configfs? as far as i read about it it's for the dynamic
creation of kernel objects, which is not what we are doing here. or did the
rules change in the mean time?

bruno

2010-03-25 05:49:59

by Bruno Randolf

[permalink] [raw]
Subject: [PATCH 10/10] ath5k: Adaptive Noise Immunity (ANI) Implementation

This is an Adaptive Noise Imunity (ANI) implementation for ath5k. I have looked
at both ath9k and HAL sources (they are nearly the same), and even though i
have implemented some things differently, the basic algorithm is practically
the same, for now. I hope that this can serve as a clean start to improve the
algorithm later.

This also adds a possibility to manually control ANI settings, right now only
thru a debugfs file:
* set lowest sensitivity (=highest noise immunity):
echo sens-low > /sys/kernel/debug/ath5k/phy0/ani
* set highest sensitivity (=lowest noise immunity):
echo sens-high > /sys/kernel/debug/ath5k/phy0/ani
* automatically control immunity (default):
echo ani-on > /sys/kernel/debug/ath5k/phy0/ani
* to see the parameters in use and watch them change:
cat /sys/kernel/debug/ath5k/phy0/ani

Manually setting sensitivity will turn the automatic control off. You can also
control each of the five immunity parameters (noise immunity, spur immunity,
firstep, ofdm weak signal detection, cck weak signal detection) manually thru
the debugfs file.

This is tested on AR5414 and nearly doubles the thruput in a noisy 2GHz band.

Signed-off-by: Bruno Randolf <[email protected]>
---
drivers/net/wireless/ath/ath5k/Makefile | 1
drivers/net/wireless/ath/ath5k/ani.c | 741 +++++++++++++++++++++++++++++++
drivers/net/wireless/ath/ath5k/ani.h | 104 ++++
drivers/net/wireless/ath/ath5k/ath5k.h | 9
drivers/net/wireless/ath/ath5k/attach.c | 1
drivers/net/wireless/ath/ath5k/base.c | 32 +
drivers/net/wireless/ath/ath5k/base.h | 6
drivers/net/wireless/ath/ath5k/debug.c | 162 +++++++
drivers/net/wireless/ath/ath5k/debug.h | 2
drivers/net/wireless/ath/ath5k/desc.c | 1
drivers/net/wireless/ath/ath5k/pcu.c | 1
drivers/net/wireless/ath/ath5k/reg.h | 36 +-
12 files changed, 1080 insertions(+), 16 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..f1ca015
--- /dev/null
+++ b/drivers/net/wireless/ath/ath5k/ani.c
@@ -0,0 +1,741 @@
+/*
+ * Copyright (C) 2010 Bruno Randolf <[email protected]>
+ *
+ * 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"
+
+/**
+ * DOC: Basic ANI Operation
+ *
+ * Adaptive Noise Immunity (ANI) controls five noise immunity parameters
+ * depending on the amount of interference in the environment, increasing
+ * or reducing sensitivity as necessary.
+ *
+ * The parameters are:
+ * - "noise immunity"
+ * - "spur immunity"
+ * - "firstep level"
+ * - "OFDM weak signal detection"
+ * - "CCK weak signal detection"
+ *
+ * Basically we look at the amount of ODFM and CCK timing errors we get and then
+ * raise or lower immunity accordingly by setting one or more of these
+ * parameters.
+ * Newer chipsets have PHY error counters in hardware which will generate a MIB
+ * interrupt when they overflow. Older hardware has too enable PHY error frames
+ * by setting a RX flag and then count every single PHY error. When a specified
+ * threshold of errors has been reached we will raise immunity.
+ * Also we regularly check the amount of errors and lower or raise immunity as
+ * necessary.
+ */
+
+
+/*** ANI parameter control ***/
+
+/**
+ * ath5k_ani_set_noise_immunity_level() - Set noise immunity level
+ *
+ * @level: level between 0 and @ATH5K_ANI_MAX_NOISE_IMM_LVL
+ */
+void
+ath5k_ani_set_noise_immunity_level(struct ath5k_hw *ah, int level)
+{
+ /* TODO:
+ * ANI documents suggest the following five levels to use, but the HAL
+ * and ath9k use only use the last two levels, making this
+ * essentially an on/off option. There *may* be a reason for this (???),
+ * so i stick with the HAL version for now...
+ */
+#if 0
+ const s8 hi[] = { -18, -18, -16, -14, -12 };
+ const s8 lo[] = { -52, -56, -60, -64, -70 };
+ const s8 sz[] = { -34, -41, -48, -55, -62 };
+ const s8 fr[] = { -70, -72, -75, -78, -80 };
+#else
+ const s8 sz[] = { -55, -62 };
+ const s8 lo[] = { -64, -70 };
+ const s8 hi[] = { -14, -12 };
+ const s8 fr[] = { -78, -80 };
+#endif
+ if (level < 0 || level > ARRAY_SIZE(sz)) {
+ ATH5K_DBG_UNLIMIT(ah->ah_sc, ATH5K_DEBUG_ANI,
+ "level out of range %d", level);
+ return;
+ }
+
+ AR5K_REG_WRITE_BITS(ah, AR5K_PHY_DESIRED_SIZE,
+ AR5K_PHY_DESIRED_SIZE_TOT, sz[level]);
+ AR5K_REG_WRITE_BITS(ah, AR5K_PHY_AGCCOARSE,
+ AR5K_PHY_AGCCOARSE_LO, lo[level]);
+ AR5K_REG_WRITE_BITS(ah, AR5K_PHY_AGCCOARSE,
+ AR5K_PHY_AGCCOARSE_HI, hi[level]);
+ AR5K_REG_WRITE_BITS(ah, AR5K_PHY_SIG,
+ AR5K_PHY_SIG_FIRPWR, fr[level]);
+
+ ah->ah_sc->ani_state.noise_imm_level = level;
+ ATH5K_DBG_UNLIMIT(ah->ah_sc, ATH5K_DEBUG_ANI, "new level %d", level);
+}
+
+
+/**
+ * ath5k_ani_set_spur_immunity_level() - Set spur immunity level
+ *
+ * @level: level between 0 and @max_spur_level (the maximum level is dependent
+ * on the chip revision).
+ */
+void
+ath5k_ani_set_spur_immunity_level(struct ath5k_hw *ah, int level)
+{
+ const int val[] = { 2, 4, 6, 8, 10, 12, 14, 16 };
+
+ if (level < 0 || level > ARRAY_SIZE(val) ||
+ level > ah->ah_sc->ani_state.max_spur_level) {
+ ATH5K_DBG_UNLIMIT(ah->ah_sc, ATH5K_DEBUG_ANI,
+ "level out of range %d", level);
+ return;
+ }
+
+ AR5K_REG_WRITE_BITS(ah, AR5K_PHY_OFDM_SELFCORR,
+ AR5K_PHY_OFDM_SELFCORR_CYPWR_THR1, val[level]);
+
+ ah->ah_sc->ani_state.spur_level = level;
+ ATH5K_DBG_UNLIMIT(ah->ah_sc, ATH5K_DEBUG_ANI, "new level %d", level);
+}
+
+
+/**
+ * ath5k_ani_set_firstep_level() - Set "firstep" level
+ *
+ * @level: level between 0 and @ATH5K_ANI_MAX_FIRSTEP_LVL
+ */
+void
+ath5k_ani_set_firstep_level(struct ath5k_hw *ah, int level)
+{
+ const int val[] = { 0, 4, 8 };
+
+ if (level < 0 || level > ARRAY_SIZE(val)) {
+ ATH5K_DBG_UNLIMIT(ah->ah_sc, ATH5K_DEBUG_ANI,
+ "level out of range %d", level);
+ return;
+ }
+
+ AR5K_REG_WRITE_BITS(ah, AR5K_PHY_SIG,
+ AR5K_PHY_SIG_FIRSTEP, val[level]);
+
+ ah->ah_sc->ani_state.firstep_level = level;
+ ATH5K_DBG_UNLIMIT(ah->ah_sc, ATH5K_DEBUG_ANI, "new level %d", level);
+}
+
+
+/**
+ * ath5k_ani_set_ofdm_weak_signal_detection() - Control OFDM weak signal
+ * detection
+ *
+ * @on: turn on or off
+ */
+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_sc->ani_state.ofdm_weak_sig = on;
+ ATH5K_DBG_UNLIMIT(ah->ah_sc, ATH5K_DEBUG_ANI, "turned %s",
+ on ? "on" : "off");
+}
+
+
+/**
+ * ath5k_ani_set_cck_weak_signal_detection() - control CCK weak signal detection
+ *
+ * @on: turn on or 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_sc->ani_state.cck_weak_sig = on;
+ ATH5K_DBG_UNLIMIT(ah->ah_sc, ATH5K_DEBUG_ANI, "turned %s",
+ on ? "on" : "off");
+}
+
+
+/*** ANI algorithm ***/
+
+/**
+ * ath5k_ani_raise_immunity() - Increase noise immunity
+ *
+ * @ofdm_trigger: If this is true we are called because of too many OFDM errors,
+ * the algorithm will tune more parameters then.
+ *
+ * Try to raise noise immunity (=decrease sensitivity) in several steps
+ * depending on the average RSSI of the beacons we received.
+ */
+static void
+ath5k_ani_raise_immunity(struct ath5k_hw *ah, struct ath5k_ani_state *as,
+ bool ofdm_trigger)
+{
+ int rssi = ah->ah_beacon_rssi_avg.avg;
+
+ ATH5K_DBG_UNLIMIT(ah->ah_sc, ATH5K_DEBUG_ANI, "raise immunity (%s)",
+ ofdm_trigger ? "ODFM" : "CCK");
+
+ /* first: raise noise immunity */
+ if (as->noise_imm_level < ATH5K_ANI_MAX_NOISE_IMM_LVL) {
+ ath5k_ani_set_noise_immunity_level(ah, as->noise_imm_level + 1);
+ return;
+ }
+
+ /* only OFDM: raise spur immunity level */
+ if (ofdm_trigger &&
+ as->spur_level < ah->ah_sc->ani_state.max_spur_level) {
+ 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_LVL)
+ ath5k_ani_set_firstep_level(ah, as->firstep_level + 1);
+ return;
+ }
+
+ /* STA and IBSS mode */
+
+ /* TODO: for IBSS mode it would be better to keep a beacon RSSI average
+ * per each neighbour node and use the minimum of these, to make sure we
+ * don't shut out a remote node by raising immunity too high. */
+
+ if (rssi > ATH5K_ANI_RSSI_THR_HIGH) {
+ ATH5K_DBG_UNLIMIT(ah->ah_sc, ATH5K_DEBUG_ANI,
+ "beacon RSSI high");
+ /* only OFDM: beacon RSSI is high, we can disable ODFM weak
+ * signal detection */
+ if (ofdm_trigger && 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 or CCK: raise firstep level */
+ if (as->firstep_level < ATH5K_ANI_MAX_FIRSTEP_LVL) {
+ 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 (ofdm_trigger && as->ofdm_weak_sig == false)
+ ath5k_ani_set_ofdm_weak_signal_detection(ah, true);
+ if (as->firstep_level < ATH5K_ANI_MAX_FIRSTEP_LVL)
+ 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 signal
+ * detect and zero firstep level to maximize CCK sensitivity */
+ ATH5K_DBG_UNLIMIT(ah->ah_sc, ATH5K_DEBUG_ANI,
+ "beacon RSSI low, 2GHz");
+ if (ofdm_trigger && as->ofdm_weak_sig == true)
+ ath5k_ani_set_ofdm_weak_signal_detection(ah, false);
+ if (as->firstep_level > 0)
+ ath5k_ani_set_firstep_level(ah, 0);
+ return;
+ }
+
+ /* TODO: why not?:
+ if (as->cck_weak_sig == true) {
+ ath5k_ani_set_cck_weak_signal_detection(ah, false);
+ }
+ */
+}
+
+
+/**
+ * ath5k_ani_lower_immunity() - Decrease noise immunity
+ *
+ * Try to lower noise immunity (=increase sensitivity) in several steps
+ * depending on the average RSSI of the beacons we received.
+ */
+static void
+ath5k_ani_lower_immunity(struct ath5k_hw *ah, struct ath5k_ani_state *as)
+{
+ int rssi = ah->ah_beacon_rssi_avg.avg;
+
+ ATH5K_DBG_UNLIMIT(ah->ah_sc, ATH5K_DEBUG_ANI, "lower immunity");
+
+ if (ah->ah_sc->opmode == NL80211_IFTYPE_AP) {
+ /* AP mode */
+ if (as->firstep_level > 0) {
+ ath5k_ani_set_firstep_level(ah, as->firstep_level - 1);
+ return;
+ }
+ } else {
+ /* STA and IBSS mode (see TODO above) */
+ if (rssi > ATH5K_ANI_RSSI_THR_HIGH) {
+ /* beacon signal is high, leave OFDM weak signal
+ * detection off or it may oscillate
+ * 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: only reduce firstep level */
+ if (as->firstep_level > 0) {
+ ath5k_ani_set_firstep_level(ah,
+ as->firstep_level - 1);
+ return;
+ }
+ }
+ }
+
+ /* all modes */
+ if (as->spur_level > 0) {
+ ath5k_ani_set_spur_immunity_level(ah, as->spur_level - 1);
+ return;
+ }
+
+ /* finally, reduce noise immunity */
+ if (as->noise_imm_level > 0) {
+ ath5k_ani_set_noise_immunity_level(ah, as->noise_imm_level - 1);
+ return;
+ }
+}
+
+
+/**
+ * ath5k_hw_ani_get_listen_time() - Calculate time spent listening
+ *
+ * Return an approximation of the time spent "listening" in milliseconds (ms)
+ * since the last call of this function by deducting the cycles spent
+ * transmitting and receiving from the total cycle count.
+ * Save profile count values for debugging/statistics and because we might want
+ * to use them later.
+ *
+ * We assume no one else clears these registers!
+ */
+static int
+ath5k_hw_ani_get_listen_time(struct ath5k_hw *ah, struct ath5k_ani_state *as)
+{
+ int listen;
+
+ /* freeze */
+ ath5k_hw_reg_write(ah, AR5K_MIBC_FMC, AR5K_MIBC);
+ /* read */
+ as->pfc_cycles = ath5k_hw_reg_read(ah, AR5K_PROFCNT_CYCLE);
+ as->pfc_busy = ath5k_hw_reg_read(ah, AR5K_PROFCNT_RXCLR);
+ as->pfc_tx = ath5k_hw_reg_read(ah, AR5K_PROFCNT_TX);
+ as->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, 0, AR5K_MIBC);
+
+ /* TODO: where does 44000 come from? (11g clock rate?) */
+ listen = (as->pfc_cycles - as->pfc_rx - as->pfc_tx) / 44000;
+
+ if (as->pfc_cycles == 0 || listen < 0)
+ return 0;
+ return listen;
+}
+
+
+/**
+ * ath5k_ani_save_and_clear_phy_errors() - Clear and save PHY error counters
+ *
+ * Clear the PHY error counters as soon as possible, since this might be called
+ * from a MIB interrupt and we want to make sure we don't get interrupted again.
+ * Add the count of CCK and OFDM errors to our internal state, so it can be used
+ * by the algorithm later.
+ *
+ * Will be called from interrupt and tasklet context.
+ * Returns 0 if both counters are zero.
+ */
+static int
+ath5k_ani_save_and_clear_phy_errors(struct ath5k_hw *ah,
+ struct ath5k_ani_state *as)
+{
+ unsigned int ofdm_err, cck_err;
+
+ if (!ah->ah_capabilities.cap_has_phyerr_counters)
+ return 0;
+
+ ofdm_err = ath5k_hw_reg_read(ah, AR5K_PHYERR_CNT1);
+ cck_err = ath5k_hw_reg_read(ah, AR5K_PHYERR_CNT2);
+
+ /* reset counters first, we might be in a hurry (interrupt) */
+ 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);
+
+ 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);
+
+ /* sometimes both can be zero, especially when there is a superfluous
+ * second interrupt. detect that here and return an error. */
+ if (ofdm_err <= 0 && cck_err <= 0)
+ return 0;
+
+ /* avoid negative values should one of the registers overflow */
+ if (ofdm_err > 0) {
+ as->ofdm_errors += ofdm_err;
+ as->sum_ofdm_errors += ofdm_err;
+ }
+ if (cck_err > 0) {
+ as->cck_errors += cck_err;
+ as->sum_cck_errors += cck_err;
+ }
+ return 1;
+}
+
+
+/**
+ * ath5k_ani_period_restart() - Restart ANI period
+ *
+ * Just reset counters, so they are clear for the next "ani period".
+ */
+static void
+ath5k_ani_period_restart(struct ath5k_hw *ah, struct ath5k_ani_state *as)
+{
+ /* keep last values for debugging */
+ as->last_ofdm_errors = as->ofdm_errors;
+ as->last_cck_errors = as->cck_errors;
+ as->last_listen = as->listen_time;
+
+ as->ofdm_errors = 0;
+ as->cck_errors = 0;
+ as->listen_time = 0;
+}
+
+
+/**
+ * ath5k_ani_calibration() - The main ANI calibration function
+ *
+ * We count OFDM and CCK errors relative to the time where we did not send or
+ * receive ("listen" time) and raise or lower immunity accordingly.
+ * This is called regularly (every second) from the calibration timer, but also
+ * when an error threshold has been reached.
+ *
+ * In order to synchronize access from different contexts, this should be
+ * called only indirectly by scheduling the ANI tasklet!
+ */
+void
+ath5k_ani_calibration(struct ath5k_hw *ah)
+{
+ struct ath5k_ani_state *as = &ah->ah_sc->ani_state;
+ int listen, ofdm_high, ofdm_low, cck_high, cck_low;
+
+ if (as->ani_mode != ATH5K_ANI_MODE_AUTO)
+ return;
+
+ /* get listen time since last call and add it to the counter because we
+ * might not have restarted the "ani period" last time */
+ listen = ath5k_hw_ani_get_listen_time(ah, as);
+ as->listen_time += listen;
+
+ ath5k_ani_save_and_clear_phy_errors(ah, as);
+
+ ofdm_high = as->listen_time * ATH5K_ANI_OFDM_TRIG_HIGH / 1000;
+ cck_high = as->listen_time * ATH5K_ANI_CCK_TRIG_HIGH / 1000;
+ ofdm_low = as->listen_time * ATH5K_ANI_OFDM_TRIG_LOW / 1000;
+ cck_low = as->listen_time * ATH5K_ANI_CCK_TRIG_LOW / 1000;
+
+ ATH5K_DBG_UNLIMIT(ah->ah_sc, ATH5K_DEBUG_ANI,
+ "listen %d (now %d)", as->listen_time, listen);
+ ATH5K_DBG_UNLIMIT(ah->ah_sc, ATH5K_DEBUG_ANI,
+ "check high ofdm %d/%d cck %d/%d",
+ as->ofdm_errors, ofdm_high, as->cck_errors, cck_high);
+
+ if (as->ofdm_errors > ofdm_high || as->cck_errors > cck_high) {
+ /* too many PHY errors - we have to raise immunity */
+ bool ofdm_flag = as->ofdm_errors > ofdm_high ? true : false;
+ ath5k_ani_raise_immunity(ah, as, ofdm_flag);
+ ath5k_ani_period_restart(ah, as);
+
+ } else if (as->listen_time > 5 * ATH5K_ANI_LISTEN_PERIOD) {
+ /* If more than 5 (TODO: why 5?) periods have passed and we got
+ * relatively little errors we can try to lower immunity */
+ ATH5K_DBG_UNLIMIT(ah->ah_sc, ATH5K_DEBUG_ANI,
+ "check low ofdm %d/%d cck %d/%d",
+ as->ofdm_errors, ofdm_low, as->cck_errors, cck_low);
+
+ if (as->ofdm_errors <= ofdm_low && as->cck_errors <= cck_low)
+ ath5k_ani_lower_immunity(ah, as);
+
+ ath5k_ani_period_restart(ah, as);
+ }
+}
+
+
+/*** INTERRUPT HANDLER ***/
+
+/**
+ * ath5k_ani_mib_intr() - Interrupt handler for ANI MIB counters
+ *
+ * Just read & reset the registers quickly, so they don't generate more
+ * interrupts, save the counters and schedule the tasklet to decide whether
+ * to raise immunity or not.
+ *
+ * We just need to handle PHY error counters, ath5k_hw_update_mib_counters()
+ * should take care of all "normal" MIB interrupts.
+ */
+void
+ath5k_ani_mib_intr(struct ath5k_hw *ah)
+{
+ struct ath5k_ani_state *as = &ah->ah_sc->ani_state;
+
+ /* nothing to do here if HW does not have PHY error counters - they
+ * can't be the reason for the MIB interrupt then */
+ if (!ah->ah_capabilities.cap_has_phyerr_counters)
+ return;
+
+ /* not in use but clear anyways */
+ ath5k_hw_reg_write(ah, 0, AR5K_OFDM_FIL_CNT);
+ ath5k_hw_reg_write(ah, 0, AR5K_CCK_FIL_CNT);
+
+ if (ah->ah_sc->ani_state.ani_mode != ATH5K_ANI_MODE_AUTO)
+ return;
+
+ /* if one of the errors triggered, we can get a superfluous second
+ * interrupt, even though we have already reset the register. the
+ * function detects that so we can return early */
+ if (ath5k_ani_save_and_clear_phy_errors(ah, as) == 0)
+ return;
+
+ if (as->ofdm_errors > ATH5K_ANI_OFDM_TRIG_HIGH ||
+ as->cck_errors > ATH5K_ANI_CCK_TRIG_HIGH)
+ tasklet_schedule(&ah->ah_sc->ani_tasklet);
+}
+
+
+/**
+ * ath5k_ani_phy_error_report() - Used by older HW to report PHY errors
+ *
+ * This is used by hardware without PHY error counters to report PHY errors
+ * on a frame-by-frame basis, instead of the interrupt.
+ */
+void
+ath5k_ani_phy_error_report(struct ath5k_hw *ah,
+ enum ath5k_phy_error_code phyerr)
+{
+ struct ath5k_ani_state *as = &ah->ah_sc->ani_state;
+
+ if (phyerr == AR5K_RX_PHY_ERROR_OFDM_TIMING) {
+ as->ofdm_errors++;
+ if (as->ofdm_errors > ATH5K_ANI_OFDM_TRIG_HIGH)
+ tasklet_schedule(&ah->ah_sc->ani_tasklet);
+ } else if (phyerr == AR5K_RX_PHY_ERROR_CCK_TIMING) {
+ as->cck_errors++;
+ if (as->cck_errors > ATH5K_ANI_CCK_TRIG_HIGH)
+ tasklet_schedule(&ah->ah_sc->ani_tasklet);
+ }
+}
+
+
+/*** INIT ***/
+
+/**
+ * ath5k_enable_phy_err_counters() - Enable PHY error counters
+ *
+ * Enable PHY error counters for OFDM and CCK timing errors.
+ */
+static void
+ath5k_enable_phy_err_counters(struct ath5k_hw *ah)
+{
+ 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);
+
+ /* not in use */
+ ath5k_hw_reg_write(ah, 0, AR5K_OFDM_FIL_CNT);
+ ath5k_hw_reg_write(ah, 0, AR5K_CCK_FIL_CNT);
+}
+
+
+/**
+ * ath5k_disable_phy_err_counters() - Disable PHY error counters
+ *
+ * Disable PHY error counters for OFDM and CCK timing errors.
+ */
+static void
+ath5k_disable_phy_err_counters(struct ath5k_hw *ah)
+{
+ 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);
+
+ /* not in use */
+ ath5k_hw_reg_write(ah, 0, AR5K_OFDM_FIL_CNT);
+ ath5k_hw_reg_write(ah, 0, AR5K_CCK_FIL_CNT);
+}
+
+
+/**
+ * ath5k_ani_init() - Initialize ANI
+ * @mode: Which mode to use (auto, manual high, manual low, off)
+ *
+ * Initialize ANI according to mode.
+ */
+void
+ath5k_ani_init(struct ath5k_hw *ah, enum ath5k_ani_mode mode)
+{
+ /* ANI is only possible on 5212 and newer */
+ if (ah->ah_version < AR5K_AR5212)
+ return;
+
+ /* older hardware has more spur levels than newer */
+ if (ah->ah_mac_srev < AR5K_SREV_AR2414)
+ ah->ah_sc->ani_state.max_spur_level = 7;
+ else
+ ah->ah_sc->ani_state.max_spur_level = 2;
+
+ /* initial values for our ani parameters */
+ if (mode == ATH5K_ANI_MODE_OFF) {
+ ATH5K_DBG_UNLIMIT(ah->ah_sc, ATH5K_DEBUG_ANI, "ANI off\n");
+ } else if (mode == ATH5K_ANI_MODE_MANUAL_LOW) {
+ ATH5K_DBG_UNLIMIT(ah->ah_sc, ATH5K_DEBUG_ANI,
+ "ANI manual low -> high sensitivity\n");
+ ath5k_ani_set_noise_immunity_level(ah, 0);
+ ath5k_ani_set_spur_immunity_level(ah, 0);
+ ath5k_ani_set_firstep_level(ah, 0);
+ ath5k_ani_set_ofdm_weak_signal_detection(ah, true);
+ ath5k_ani_set_cck_weak_signal_detection(ah, true);
+ } else if (mode == ATH5K_ANI_MODE_MANUAL_HIGH) {
+ ATH5K_DBG_UNLIMIT(ah->ah_sc, ATH5K_DEBUG_ANI,
+ "ANI manual high -> low sensitivity\n");
+ ath5k_ani_set_noise_immunity_level(ah,
+ ATH5K_ANI_MAX_NOISE_IMM_LVL);
+ ath5k_ani_set_spur_immunity_level(ah,
+ ah->ah_sc->ani_state.max_spur_level);
+ ath5k_ani_set_firstep_level(ah, ATH5K_ANI_MAX_FIRSTEP_LVL);
+ ath5k_ani_set_ofdm_weak_signal_detection(ah, false);
+ ath5k_ani_set_cck_weak_signal_detection(ah, false);
+ } else if (mode == ATH5K_ANI_MODE_AUTO) {
+ ATH5K_DBG_UNLIMIT(ah->ah_sc, ATH5K_DEBUG_ANI, "ANI auto\n");
+ ath5k_ani_set_noise_immunity_level(ah, 0);
+ ath5k_ani_set_spur_immunity_level(ah, 0);
+ ath5k_ani_set_firstep_level(ah, 0);
+ ath5k_ani_set_ofdm_weak_signal_detection(ah, true);
+ ath5k_ani_set_cck_weak_signal_detection(ah, false);
+ }
+
+ /* newer hardware has PHY error counter registers which we can use to
+ * get OFDM and CCK error counts. older hardware has to set rxfilter and
+ * report every single PHY error by calling ath5k_ani_phy_error_report()
+ */
+ if (mode == ATH5K_ANI_MODE_AUTO) {
+ if (ah->ah_capabilities.cap_has_phyerr_counters)
+ ath5k_enable_phy_err_counters(ah);
+ else
+ ath5k_hw_set_rx_filter(ah, ath5k_hw_get_rx_filter(ah) |
+ AR5K_RX_FILTER_PHYERR);
+ } else {
+ if (ah->ah_capabilities.cap_has_phyerr_counters)
+ ath5k_disable_phy_err_counters(ah);
+ else
+ ath5k_hw_set_rx_filter(ah, ath5k_hw_get_rx_filter(ah) &
+ ~AR5K_RX_FILTER_PHYERR);
+ }
+
+ ah->ah_sc->ani_state.ani_mode = mode;
+}
+
+
+/*** DEBUG ***/
+
+#ifdef CONFIG_ATH5K_DEBUG
+
+void
+ath5k_ani_print_counters(struct ath5k_hw *ah)
+{
+ /* clears too */
+ printk(KERN_NOTICE "ACK fail\t%d\n",
+ ath5k_hw_reg_read(ah, AR5K_ACK_FAIL));
+ printk(KERN_NOTICE "RTS fail\t%d\n",
+ ath5k_hw_reg_read(ah, AR5K_RTS_FAIL));
+ printk(KERN_NOTICE "RTS success\t%d\n",
+ ath5k_hw_reg_read(ah, AR5K_RTS_OK));
+ printk(KERN_NOTICE "FCS error\t%d\n",
+ ath5k_hw_reg_read(ah, AR5K_FCS_FAIL));
+
+ /* no clear */
+ printk(KERN_NOTICE "tx\t%d\n",
+ ath5k_hw_reg_read(ah, AR5K_PROFCNT_TX));
+ printk(KERN_NOTICE "rx\t%d\n",
+ ath5k_hw_reg_read(ah, AR5K_PROFCNT_RX));
+ printk(KERN_NOTICE "busy\t%d\n",
+ ath5k_hw_reg_read(ah, AR5K_PROFCNT_RXCLR));
+ printk(KERN_NOTICE "cycles\t%d\n",
+ ath5k_hw_reg_read(ah, AR5K_PROFCNT_CYCLE));
+
+ printk(KERN_NOTICE "AR5K_PHYERR_CNT1\t%d\n",
+ ath5k_hw_reg_read(ah, AR5K_PHYERR_CNT1));
+ printk(KERN_NOTICE "AR5K_PHYERR_CNT2\t%d\n",
+ ath5k_hw_reg_read(ah, AR5K_PHYERR_CNT2));
+ printk(KERN_NOTICE "AR5K_OFDM_FIL_CNT\t%d\n",
+ ath5k_hw_reg_read(ah, AR5K_OFDM_FIL_CNT));
+ printk(KERN_NOTICE "AR5K_CCK_FIL_CNT\t%d\n",
+ ath5k_hw_reg_read(ah, AR5K_CCK_FIL_CNT));
+}
+
+#endif
diff --git a/drivers/net/wireless/ath/ath5k/ani.h b/drivers/net/wireless/ath/ath5k/ani.h
new file mode 100644
index 0000000..55cf26d
--- /dev/null
+++ b/drivers/net/wireless/ath/ath5k/ani.h
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2010 Bruno Randolf <[email protected]>
+ *
+ * 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
+
+/* these thresholds are relative to the ATH5K_ANI_LISTEN_PERIOD */
+#define ATH5K_ANI_LISTEN_PERIOD 100
+#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
+
+/* average beacon RSSI thresholds */
+#define ATH5K_ANI_RSSI_THR_HIGH 40
+#define ATH5K_ANI_RSSI_THR_LOW 7
+
+/* maximum availabe levels */
+#define ATH5K_ANI_MAX_FIRSTEP_LVL 2
+#define ATH5K_ANI_MAX_NOISE_IMM_LVL 1
+
+
+/**
+ * enum ath5k_ani_mode - mode for ANI / noise sensitivity
+ *
+ * @ATH5K_ANI_MODE_OFF: Turn ANI off. This can be useful to just stop the ANI
+ * algorithm after it has been on auto mode.
+ * ATH5K_ANI_MODE_MANUAL_LOW: Manually set all immunity parameters to low,
+ * maximizing sensitivity. ANI will not run.
+ * ATH5K_ANI_MODE_MANUAL_HIGH: Manually set all immunity parameters to high,
+ * minimizing sensitivity. ANI will not run.
+ * ATH5K_ANI_MODE_AUTO: Automatically control immunity parameters based on the
+ * amount of OFDM and CCK frame errors (default).
+ */
+enum ath5k_ani_mode {
+ ATH5K_ANI_MODE_OFF = 0,
+ ATH5K_ANI_MODE_MANUAL_LOW = 1,
+ ATH5K_ANI_MODE_MANUAL_HIGH = 2,
+ ATH5K_ANI_MODE_AUTO = 3
+};
+
+
+/**
+ * struct ath5k_ani_state - ANI state and associated counters
+ *
+ * @max_spur_level: the maximum spur level is chip dependent
+ */
+struct ath5k_ani_state {
+ enum ath5k_ani_mode ani_mode;
+
+ /* state */
+ int noise_imm_level;
+ int spur_level;
+ int firstep_level;
+ bool ofdm_weak_sig;
+ bool cck_weak_sig;
+
+ int max_spur_level;
+
+ /* used by the algorithm */
+ unsigned int listen_time;
+ unsigned int ofdm_errors;
+ unsigned int cck_errors;
+
+ /* debug/statistics only: numbers from last ANI calibration */
+ unsigned int pfc_tx;
+ unsigned int pfc_rx;
+ unsigned int pfc_busy;
+ unsigned int pfc_cycles;
+ unsigned int last_listen;
+ unsigned int last_ofdm_errors;
+ unsigned int last_cck_errors;
+ unsigned int sum_ofdm_errors;
+ unsigned int sum_cck_errors;
+};
+
+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_calibration(struct ath5k_hw *ah);
+void ath5k_ani_phy_error_report(struct ath5k_hw *ah,
+ enum ath5k_phy_error_code phyerr);
+
+/* for manual control */
+void ath5k_ani_set_noise_immunity_level(struct ath5k_hw *ah, int level);
+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 f578c1e..2785946 100644
--- a/drivers/net/wireless/ath/ath5k/ath5k.h
+++ b/drivers/net/wireless/ath/ath5k/ath5k.h
@@ -203,6 +203,7 @@
#define AR5K_TUNE_DEFAULT_TXPOWER 25
#define AR5K_TUNE_TPC_TXPOWER false
#define ATH5K_TUNE_CALIBRATION_INTERVAL_FULL 10000 /* 10 sec */
+#define ATH5K_TUNE_CALIBRATION_INTERVAL_ANI 1000 /* 1 sec */

#define AR5K_INIT_CARR_SENSE_EN 1

@@ -800,9 +801,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
@@ -894,6 +895,7 @@ enum ath5k_int {
enum ath5k_calibration_mask {
AR5K_CALIBRATION_FULL = 0x01,
AR5K_CALIBRATION_SHORT = 0x02,
+ AR5K_CALIBRATION_ANI = 0x04,
};

/*
@@ -1115,6 +1117,7 @@ struct ath5k_hw {

/* Calibration timestamp */
unsigned long ah_cal_next_full;
+ unsigned long ah_cal_next_ani;

/* Calibration mask */
u8 ah_cal_mask;
diff --git a/drivers/net/wireless/ath/ath5k/attach.c b/drivers/net/wireless/ath/ath5k/attach.c
index f80d3d5..dcf7c30 100644
--- a/drivers/net/wireless/ath/ath5k/attach.c
+++ b/drivers/net/wireless/ath/ath5k/attach.c
@@ -124,6 +124,7 @@ int ath5k_hw_attach(struct ath5k_softc *sc)
ah->ah_software_retry = false;
ah->ah_ant_mode = AR5K_ANTMODE_DEFAULT;
ah->ah_noise_floor = -95; /* until first NF calibration is run */
+ sc->ani_state.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 a1c0dcb..c085a06 100644
--- a/drivers/net/wireless/ath/ath5k/base.c
+++ b/drivers/net/wireless/ath/ath5k/base.c
@@ -58,6 +58,7 @@
#include "base.h"
#include "reg.h"
#include "debug.h"
+#include "ani.h"

static int modparam_nohwcrypt;
module_param_named(nohwcrypt, modparam_nohwcrypt, bool, S_IRUGO);
@@ -363,6 +364,7 @@ static void ath5k_beacon_send(struct ath5k_softc *sc);
static void ath5k_beacon_config(struct ath5k_softc *sc);
static void ath5k_beacon_update_timers(struct ath5k_softc *sc, u64 bc_tsf);
static void ath5k_tasklet_beacon(unsigned long data);
+static void ath5k_tasklet_ani(unsigned long data);

static inline u64 ath5k_extend_tsf(struct ath5k_hw *ah, u32 rstamp)
{
@@ -828,6 +830,7 @@ ath5k_attach(struct pci_dev *pdev, struct ieee80211_hw *hw)
tasklet_init(&sc->restq, ath5k_tasklet_reset, (unsigned long)sc);
tasklet_init(&sc->calib, ath5k_tasklet_calibrate, (unsigned long)sc);
tasklet_init(&sc->beacontq, ath5k_tasklet_beacon, (unsigned long)sc);
+ tasklet_init(&sc->ani_tasklet, ath5k_tasklet_ani, (unsigned long)sc);

ret = ath5k_eeprom_read_mac(ah, mac);
if (ret) {
@@ -2530,7 +2533,8 @@ 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_FATAL | AR5K_INT_GLOBAL | AR5K_INT_MIB;
+
ret = ath5k_reset(sc, NULL);
if (ret)
goto done;
@@ -2642,6 +2646,7 @@ ath5k_stop_hw(struct ath5k_softc *sc)
tasklet_kill(&sc->restq);
tasklet_kill(&sc->calib);
tasklet_kill(&sc->beacontq);
+ tasklet_kill(&sc->ani_tasklet);

ath5k_rfkill_hw_stop(sc->ah);

@@ -2651,7 +2656,14 @@ ath5k_stop_hw(struct ath5k_softc *sc)
static void
ath5k_intr_calibration_poll(struct ath5k_hw *ah)
{
- if (time_is_before_eq_jiffies(ah->ah_cal_next_full)) {
+ if (time_is_before_eq_jiffies(ah->ah_cal_next_ani) &&
+ !(ah->ah_cal_mask & AR5K_CALIBRATION_FULL)) {
+ /* run ANI only when full calibration is not active */
+ ah->ah_cal_next_ani = jiffies +
+ msecs_to_jiffies(ATH5K_TUNE_CALIBRATION_INTERVAL_ANI);
+ tasklet_schedule(&ah->ah_sc->ani_tasklet);
+
+ } else if (time_is_before_eq_jiffies(ah->ah_cal_next_full)) {
ah->ah_cal_next_full = jiffies +
msecs_to_jiffies(ATH5K_TUNE_CALIBRATION_INTERVAL_FULL);
tasklet_schedule(&ah->ah_sc->calib);
@@ -2710,7 +2722,9 @@ ath5k_intr(int irq, void *dev_id)
/* TODO */
}
if (status & AR5K_INT_MIB) {
+ 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);
@@ -2775,6 +2789,18 @@ ath5k_tasklet_calibrate(unsigned long data)
}


+static void
+ath5k_tasklet_ani(unsigned long data)
+{
+ struct ath5k_softc *sc = (void *)data;
+ struct ath5k_hw *ah = sc->ah;
+
+ ah->ah_cal_mask |= AR5K_CALIBRATION_ANI;
+ ath5k_ani_calibration(ah);
+ ah->ah_cal_mask &= ~AR5K_CALIBRATION_ANI;
+}
+
+
/********************\
* Mac80211 functions *
\********************/
@@ -2874,6 +2900,8 @@ ath5k_reset(struct ath5k_softc *sc, struct ieee80211_channel *chan)
goto err;
}

+ ath5k_ani_init(ah, ah->ah_sc->ani_state.ani_mode);
+
/*
* Change channels and update the h/w rate map if we're switching;
* e.g. 11a to 11b/g.
diff --git a/drivers/net/wireless/ath/ath5k/base.h b/drivers/net/wireless/ath/ath5k/base.h
index a572a48..53a5651 100644
--- a/drivers/net/wireless/ath/ath5k/base.h
+++ b/drivers/net/wireless/ath/ath5k/base.h
@@ -50,6 +50,7 @@

#include "ath5k.h"
#include "debug.h"
+#include "ani.h"

#include "../regd.h"
#include "../ath.h"
@@ -132,6 +133,8 @@ struct ath5k_statistics {
unsigned int rts_ok;
unsigned int fcs_error;
unsigned int beacons;
+
+ unsigned int mib_intr;
};

#if CHAN_DEBUG
@@ -221,6 +224,9 @@ struct ath5k_softc {
bool enable_beacon; /* true if beacons are on */

struct ath5k_statistics stats;
+
+ struct ath5k_ani_state ani_state;
+ struct tasklet_struct ani_tasklet; /* ANI calibration */
};

#define ath5k_hw_hasbssidmask(_ah) \
diff --git a/drivers/net/wireless/ath/ath5k/debug.c b/drivers/net/wireless/ath/ath5k/debug.c
index 90247dc..946a7e3 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", "adaptive noise immunity" },
{ ATH5K_DEBUG_ANY, "all", "show all debug levels" },
};

@@ -573,6 +575,160 @@ 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->ani_state;
+
+ char buf[700];
+ unsigned int len = 0;
+
+ len += snprintf(buf+len, sizeof(buf)-len,
+ "HW has PHY error counters:\t%s\n",
+ sc->ah->ah_capabilities.cap_has_phyerr_counters ?
+ "yes" : "no");
+ len += snprintf(buf+len, sizeof(buf)-len,
+ "HW max spur immunity level:\t%d\n",
+ as->max_spur_level);
+ len += snprintf(buf+len, sizeof(buf)-len,
+ "\nANI state\n--------------------------------------------\n");
+ len += snprintf(buf+len, sizeof(buf)-len, "operating mode:\t\t\t");
+ switch (as->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 level:\t\t%d\n",
+ as->noise_imm_level);
+ 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,
+ "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,
+ "\nMIB INTERRUPTS:\t\t%u\n",
+ st->mib_intr);
+ len += snprintf(buf+len, sizeof(buf)-len,
+ "beacon RSSI average:\t%d\n",
+ sc->ah->ah_beacon_rssi_avg.avg);
+ len += snprintf(buf+len, sizeof(buf)-len, "profcnt tx\t\t%u\t(%d%%)\n",
+ as->pfc_tx,
+ as->pfc_cycles > 0 ?
+ as->pfc_tx*100/as->pfc_cycles : 0);
+ len += snprintf(buf+len, sizeof(buf)-len, "profcnt rx\t\t%u\t(%d%%)\n",
+ as->pfc_rx,
+ as->pfc_cycles > 0 ?
+ as->pfc_rx*100/as->pfc_cycles : 0);
+ len += snprintf(buf+len, sizeof(buf)-len, "profcnt busy\t\t%u\t(%d%%)\n",
+ as->pfc_busy,
+ as->pfc_cycles > 0 ?
+ as->pfc_busy*100/as->pfc_cycles : 0);
+ len += snprintf(buf+len, sizeof(buf)-len, "profcnt cycles\t\t%u\n",
+ as->pfc_cycles);
+ len += snprintf(buf+len, sizeof(buf)-len,
+ "listen time\t\t%d\tlast: %d\n",
+ as->listen_time, as->last_listen);
+ len += snprintf(buf+len, sizeof(buf)-len,
+ "OFDM errors\t\t%u\tlast: %u\tsum: %u\n",
+ as->ofdm_errors, as->last_ofdm_errors,
+ as->sum_ofdm_errors);
+ len += snprintf(buf+len, sizeof(buf)-len,
+ "CCK errors\t\t%u\tlast: %u\tsum: %u\n",
+ as->cck_errors, as->last_cck_errors,
+ as->sum_cck_errors);
+ len += snprintf(buf+len, sizeof(buf)-len,
+ "AR5K_PHYERR_CNT1\t%x\t(=%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%x\t(=%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, "sens-low", 8) == 0) {
+ ath5k_ani_init(sc->ah, ATH5K_ANI_MODE_MANUAL_HIGH);
+ } else if (strncmp(buf, "sens-high", 9) == 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-on", 6) == 0) {
+ ath5k_ani_init(sc->ah, ATH5K_ANI_MODE_AUTO);
+ } else if (strncmp(buf, "noise-low", 8) == 0) {
+ ath5k_ani_set_noise_immunity_level(sc->ah, 0);
+ } else if (strncmp(buf, "noise-high", 9) == 0) {
+ ath5k_ani_set_noise_immunity_level(sc->ah,
+ ATH5K_ANI_MAX_NOISE_IMM_LVL);
+ } 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,
+ sc->ani_state.max_spur_level);
+ } 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, ATH5K_ANI_MAX_FIRSTEP_LVL);
+ } else if (strncmp(buf, "ofdm-off", 8) == 0) {
+ ath5k_ani_set_ofdm_weak_signal_detection(sc->ah, false);
+ } else if (strncmp(buf, "ofdm-on", 7) == 0) {
+ ath5k_ani_set_ofdm_weak_signal_detection(sc->ah, true);
+ } else if (strncmp(buf, "cck-off", 7) == 0) {
+ ath5k_ani_set_cck_weak_signal_detection(sc->ah, false);
+ } else if (strncmp(buf, "cck-on", 6) == 0) {
+ ath5k_ani_set_cck_weak_signal_detection(sc->ah, true);
+ }
+ 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
@@ -611,6 +767,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
@@ -628,6 +789,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/desc.c b/drivers/net/wireless/ath/ath5k/desc.c
index 9d920fb..7d7b646 100644
--- a/drivers/net/wireless/ath/ath5k/desc.c
+++ b/drivers/net/wireless/ath/ath5k/desc.c
@@ -645,6 +645,7 @@ static int ath5k_hw_proc_5212_rx_status(struct ath5k_hw *ah,
rs->rs_status |= AR5K_RXERR_PHY;
rs->rs_phyerr |= AR5K_REG_MS(rx_err->rx_error_1,
AR5K_RX_DESC_ERROR1_PHY_ERROR_CODE);
+ ath5k_ani_phy_error_report(ah, rs->rs_phyerr);
}

if (rx_status->rx_status_1 &
diff --git a/drivers/net/wireless/ath/ath5k/pcu.c b/drivers/net/wireless/ath/ath5k/pcu.c
index c7c1fe0..710870e 100644
--- a/drivers/net/wireless/ath/ath5k/pcu.c
+++ b/drivers/net/wireless/ath/ath5k/pcu.c
@@ -379,7 +379,6 @@ 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)
{
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 (?)
*/


2010-03-26 00:18:36

by Bruno Randolf

[permalink] [raw]
Subject: Re: [PATCH 10/10] ath5k: Adaptive Noise Immunity (ANI) Implementation

On Thursday 25 March 2010 19:59:11 Joerg Pommnitz wrote:
> Bruno Randolf <br1@...> writes:
> > +/* debugfs: ani */
> > +
> > +static ssize_t read_file_ani(struct file *file, char __user *user_buf,
> > + size_t count, loff_t *ppos)
> > +{
> > + return simple_read_from_buffer(user_buf, count, ppos, buf, len);
>
> ...
>
> > +}
>
> I would prefer a machine readable solution, e.g. individual files
> in an ANI directory with one value each.
>
> > +static ssize_t write_file_ani(struct file *file,
> > + const char __user *userbuf,
> > + size_t count, loff_t *ppos)
> > +{
>
> ...
>
> > + return count;
> > +}
>
> Same as above: instead of a catch all with magic configuration words, use
> individual files with at least guessable values (e.g. 0/1 for
> enable/disable of individual features).

yes i agree with you and i mentioned this in my introdution message [PATCH
0/0]. these are just debug files...

my question is which interface to use?

iw? - probably not generic enough, allthough something like "sensitivity"
could be useful to other drivers too?

sysfs? configfs? - what is the interface of choice nowadays? ;)

bruno

2010-03-25 05:49:10

by Bruno Randolf

[permalink] [raw]
Subject: [PATCH 01/10] ath5k: remove static calibration interval variable

Remove static variable ath5k_calinterval which was used as a constant. Use a
#define instead. Also we don't need ah_cal_intval.

Signed-off-by: Bruno Randolf <[email protected]>
---
drivers/net/wireless/ath/ath5k/ath5k.h | 4 +---
drivers/net/wireless/ath/ath5k/base.c | 4 ----
drivers/net/wireless/ath/ath5k/phy.c | 2 +-
3 files changed, 2 insertions(+), 8 deletions(-)

diff --git a/drivers/net/wireless/ath/ath5k/ath5k.h b/drivers/net/wireless/ath/ath5k/ath5k.h
index 1d7491c..a3019ee 100644
--- a/drivers/net/wireless/ath/ath5k/ath5k.h
+++ b/drivers/net/wireless/ath/ath5k/ath5k.h
@@ -202,6 +202,7 @@
#define AR5K_TUNE_MAX_TXPOWER 63
#define AR5K_TUNE_DEFAULT_TXPOWER 25
#define AR5K_TUNE_TPC_TXPOWER false
+#define ATH5K_TUNE_CALIBRATION_INTERVAL_FULL 10000 /* 10 sec */

#define AR5K_INIT_CARR_SENSE_EN 1

@@ -1101,9 +1102,6 @@ struct ath5k_hw {
/* Calibration timestamp */
unsigned long ah_cal_tstamp;

- /* Calibration interval (secs) */
- u8 ah_cal_intval;
-
/* Software interrupt mask */
u8 ah_swi_mask;

diff --git a/drivers/net/wireless/ath/ath5k/base.c b/drivers/net/wireless/ath/ath5k/base.c
index b142a78..39d5800 100644
--- a/drivers/net/wireless/ath/ath5k/base.c
+++ b/drivers/net/wireless/ath/ath5k/base.c
@@ -59,7 +59,6 @@
#include "reg.h"
#include "debug.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.");
@@ -2497,9 +2496,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
diff --git a/drivers/net/wireless/ath/ath5k/phy.c b/drivers/net/wireless/ath/ath5k/phy.c
index 47f3679..07f59b1 100644
--- a/drivers/net/wireless/ath/ath5k/phy.c
+++ b/drivers/net/wireless/ath/ath5k/phy.c
@@ -1108,7 +1108,7 @@ 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_FULL);

/* Initialize timestamp if needed */
if (!ah->ah_cal_tstamp)


2010-03-26 01:46:19

by Bruno Randolf

[permalink] [raw]
Subject: Re: [ath5k-devel] [PATCH 00/10] ANI for ath5k

On Friday 26 March 2010 10:21:50 Derek Smithies wrote:
> On Fri, 26 Mar 2010, Bruno Randolf wrote:
> >>>> - Improve beacon averaging algorithm for IBSS:
> >>> is to turn ani off. which means the issue you raised else where of
> >>> providing reasonable userland controls (not via debugfs) becomes
> >>> important.
> >>
> >> If ANI helps IBSS so much then why not just default to turning it
> >> off within the driver?
> >
> > i would say in most standard cases, IBSS nodes are close together or at a
> > similar distance, so it does make sense to use ANI by default.
>
> Nope - I disagree. You have made a dangerous assumption, to asume
> that
> IBSS node are close together or at a similar distance
>
> if you have a network of ibss nodes, and then look at the rate tables of
> the different nodes (minstrel is great here - it gives a fair and accurte
> represetation of the best rate possible to other nodes)

yeah, we all know how great minstrel is... really.

> you see there is
> some variation in the optimal rates between nodes.Which suggests that
> using some automatic ani algorithm in an ibss network will foreever lower
> performance for those nodes at the edge of the network.
>
> > as well, we all agree that there should be userland controls. the
> > questions is just about the interface to use.
>
> Definately - need userland controls. The default for IBSS should have
> ANI off. If the user wants to "futz" with it, they can. With ANI off for
> IBSS, you ensure that on startup, the network has the greatest chance of
> working.

i said in most *standard* cases - the standard case for IBSS is still 3 or 5
guys sitting in a room, or close by, wanting to exchange some data. we should
have good performance for that, therefore ANI on by default.

in larger IBSS mesh networks or outdoor deployments you will want to tune ANI
manually anyhow, then you can turn it off.

again - i'm not willing to discuss this based on guesses and assumptions.
looking at the rate tables will not tell you if using ANI will shut a node out
or not. if you have test results showing that a specific ANI setting actually
prevented a node from joining an IBSS, i'm happy to resume this discussion.

bruno

2010-03-29 02:05:50

by Bob Copeland

[permalink] [raw]
Subject: Re: [ath5k-devel] [PATCH 10/10] ath5k: Adaptive Noise Immunity (ANI) Implementation

On Thu, Mar 25, 2010 at 02:49:52PM +0900, Bruno Randolf wrote:
> + } else if (strncmp(buf, "noise-low", 8) == 0) {
> + ath5k_ani_set_noise_immunity_level(sc->ah, 0);
> + } else if (strncmp(buf, "noise-high", 9) == 0) {

The number of characters here is off-by-one.

I also tried setting noise-high while in auto mode, but then the ani
debug file still showed the values at 0, is that to be expected?

On my hardware in my location, I don't really notice much of a difference
with/without ANI, but I definitely don't get any MIB storms, so it
gets my thumbs up. I can post my ani file if you're interested.

--
Bob Copeland %% http://www.bobcopeland.com


2010-03-25 05:49:53

by Bruno Randolf

[permalink] [raw]
Subject: [PATCH 09/10] ath5k: add capability flag for phyerror counters

Chipsets since revision AR5213A (0x59) have hardware counters for PHY errors
which we can read directly from the registers. Older hardware has to use the RX
descriptor status to get a count of PHY errors. This will be used in several
places in the ANI implementation, so a flag is useful.

Signed-off-by: Bruno Randolf <[email protected]>
---
drivers/net/wireless/ath/ath5k/ath5k.h | 2 ++
drivers/net/wireless/ath/ath5k/caps.c | 6 ++++++
2 files changed, 8 insertions(+), 0 deletions(-)

diff --git a/drivers/net/wireless/ath/ath5k/ath5k.h b/drivers/net/wireless/ath/ath5k/ath5k.h
index ec62690..f578c1e 100644
--- a/drivers/net/wireless/ath/ath5k/ath5k.h
+++ b/drivers/net/wireless/ath/ath5k/ath5k.h
@@ -982,6 +982,8 @@ struct ath5k_capabilities {
struct {
u8 q_tx_num;
} cap_queues;
+
+ bool cap_has_phyerr_counters;
};

/* size of noise floor history (keep it a power of two) */
diff --git a/drivers/net/wireless/ath/ath5k/caps.c b/drivers/net/wireless/ath/ath5k/caps.c
index e618e71..74f0071 100644
--- a/drivers/net/wireless/ath/ath5k/caps.c
+++ b/drivers/net/wireless/ath/ath5k/caps.c
@@ -109,6 +109,12 @@ int ath5k_hw_set_capabilities(struct ath5k_hw *ah)
else
ah->ah_capabilities.cap_queues.q_tx_num = AR5K_NUM_TX_QUEUES;

+ /* newer hardware has PHY error counters */
+ if (ah->ah_mac_srev >= AR5K_SREV_AR5213A)
+ ah->ah_capabilities.cap_has_phyerr_counters = true;
+ else
+ ah->ah_capabilities.cap_has_phyerr_counters = false;
+
return 0;
}



2010-03-26 01:06:35

by Luis R. Rodriguez

[permalink] [raw]
Subject: Re: [ath5k-devel] [PATCH 00/10] ANI for ath5k

Adding a few more people for wider review on this.

On Thu, Mar 25, 2010 at 05:53:48PM -0700, Bruno Randolf wrote:
> On Friday 26 March 2010 09:44:31 Luis R. Rodriguez wrote:
> > > as well, we all agree that there should be userland controls. the
> > > questions is just about the interface to use.
> >
> > ANI is specific to Atheros cards so debugfs would do it. If we want to
> > have a more rigit API we could use a configfs entry for ath9k.
>
> debugfs definetly is the wrong place! users might compile the module with
> debugging disabled (for performance reasons, e.g.).
>
> are you sure about configfs? as far as i read about it it's for the dynamic
> creation of kernel objects, which is not what we are doing here. or did the
> rules change in the mean time?

I intended on writing a file system API for 802.11 management a while
ago through configfs, the only reason why I put it aside is our focus
was to get a netlink based API done. I think its reasonable to use
it for configuration of specific devices parameters though given that
we don't have a private API through nl80211.

Luis

2010-03-25 05:49:48

by Bruno Randolf

[permalink] [raw]
Subject: [PATCH 08/10] ath5k: update phy errors codes

Update PHY error codes from the HAL, and keep them in statistics for debugging
via the 'frameerrors' file. This will also be used by ANI.

Signed-off-by: Bruno Randolf <[email protected]>
---
drivers/net/wireless/ath/ath5k/base.c | 2 ++
drivers/net/wireless/ath/ath5k/base.h | 1 +
drivers/net/wireless/ath/ath5k/debug.c | 8 +++++++
drivers/net/wireless/ath/ath5k/desc.h | 35 ++++++++++++++++++++++++--------
4 files changed, 37 insertions(+), 9 deletions(-)

diff --git a/drivers/net/wireless/ath/ath5k/base.c b/drivers/net/wireless/ath/ath5k/base.c
index ea33b99..a1c0dcb 100644
--- a/drivers/net/wireless/ath/ath5k/base.c
+++ b/drivers/net/wireless/ath/ath5k/base.c
@@ -1940,6 +1940,8 @@ ath5k_tasklet_rx(unsigned long data)
sc->stats.rxerr_fifo++;
if (rs.rs_status & AR5K_RXERR_PHY) {
sc->stats.rxerr_phy++;
+ if (rs.rs_phyerr > 0 && rs.rs_phyerr < 32)
+ sc->stats.rxerr_phy_code[rs.rs_phyerr]++;
goto next;
}
if (rs.rs_status & AR5K_RXERR_DECRYPT) {
diff --git a/drivers/net/wireless/ath/ath5k/base.h b/drivers/net/wireless/ath/ath5k/base.h
index fe5dae5..a572a48 100644
--- a/drivers/net/wireless/ath/ath5k/base.h
+++ b/drivers/net/wireless/ath/ath5k/base.h
@@ -116,6 +116,7 @@ struct ath5k_statistics {
unsigned int tx_all_count; /* all TX frames, including errors */
unsigned int rxerr_crc;
unsigned int rxerr_phy;
+ unsigned int rxerr_phy_code[32];
unsigned int rxerr_fifo;
unsigned int rxerr_decrypt;
unsigned int rxerr_mic;
diff --git a/drivers/net/wireless/ath/ath5k/debug.c b/drivers/net/wireless/ath/ath5k/debug.c
index bccd4a7..90247dc 100644
--- a/drivers/net/wireless/ath/ath5k/debug.c
+++ b/drivers/net/wireless/ath/ath5k/debug.c
@@ -474,6 +474,7 @@ static ssize_t read_file_frameerrors(struct file *file, char __user *user_buf,
struct ath5k_statistics *st = &sc->stats;
char buf[700];
unsigned int len = 0;
+ int i;

len += snprintf(buf+len, sizeof(buf)-len,
"RX\n---------------------\n");
@@ -485,6 +486,13 @@ static ssize_t read_file_frameerrors(struct file *file, char __user *user_buf,
st->rxerr_phy,
st->rx_all_count > 0 ?
st->rxerr_phy*100/st->rx_all_count : 0);
+ for (i = 0; i < 32; i++) {
+ if (st->rxerr_phy_code[i])
+ len += snprintf(buf+len, sizeof(buf)-len,
+ " phy_err[%d]\t%d\n",
+ i, st->rxerr_phy_code[i]);
+ }
+
len += snprintf(buf+len, sizeof(buf)-len, "FIFO\t%d\t(%d%%)\n",
st->rxerr_fifo,
st->rx_all_count > 0 ?
diff --git a/drivers/net/wireless/ath/ath5k/desc.h b/drivers/net/wireless/ath/ath5k/desc.h
index 56158c8..64538fb 100644
--- a/drivers/net/wireless/ath/ath5k/desc.h
+++ b/drivers/net/wireless/ath/ath5k/desc.h
@@ -112,15 +112,32 @@ struct ath5k_hw_rx_error {
#define AR5K_RX_DESC_ERROR1_PHY_ERROR_CODE 0x0000ff00
#define AR5K_RX_DESC_ERROR1_PHY_ERROR_CODE_S 8

-/* PHY Error codes */
-#define AR5K_DESC_RX_PHY_ERROR_NONE 0x00
-#define AR5K_DESC_RX_PHY_ERROR_TIMING 0x20
-#define AR5K_DESC_RX_PHY_ERROR_PARITY 0x40
-#define AR5K_DESC_RX_PHY_ERROR_RATE 0x60
-#define AR5K_DESC_RX_PHY_ERROR_LENGTH 0x80
-#define AR5K_DESC_RX_PHY_ERROR_64QAM 0xa0
-#define AR5K_DESC_RX_PHY_ERROR_SERVICE 0xc0
-#define AR5K_DESC_RX_PHY_ERROR_TRANSMITOVR 0xe0
+/**
+ * enum ath5k_phy_error_code - PHY Error codes
+ */
+enum ath5k_phy_error_code {
+ AR5K_RX_PHY_ERROR_UNDERRUN = 0, /* Transmit underrun */
+ AR5K_RX_PHY_ERROR_TIMING = 1, /* Timing error */
+ AR5K_RX_PHY_ERROR_PARITY = 2, /* Illegal parity */
+ AR5K_RX_PHY_ERROR_RATE = 3, /* Illegal rate */
+ AR5K_RX_PHY_ERROR_LENGTH = 4, /* Illegal length */
+ AR5K_RX_PHY_ERROR_RADAR = 5, /* Radar detect */
+ AR5K_RX_PHY_ERROR_SERVICE = 6, /* Illegal service */
+ AR5K_RX_PHY_ERROR_TOR = 7, /* Transmit override receive */
+ /* these are specific to the 5212 */
+ AR5K_RX_PHY_ERROR_OFDM_TIMING = 17,
+ AR5K_RX_PHY_ERROR_OFDM_SIGNAL_PARITY = 18,
+ AR5K_RX_PHY_ERROR_OFDM_RATE_ILLEGAL = 19,
+ AR5K_RX_PHY_ERROR_OFDM_LENGTH_ILLEGAL = 20,
+ AR5K_RX_PHY_ERROR_OFDM_POWER_DROP = 21,
+ AR5K_RX_PHY_ERROR_OFDM_SERVICE = 22,
+ AR5K_RX_PHY_ERROR_OFDM_RESTART = 23,
+ AR5K_RX_PHY_ERROR_CCK_TIMING = 25,
+ AR5K_RX_PHY_ERROR_CCK_HEADER_CRC = 26,
+ AR5K_RX_PHY_ERROR_CCK_RATE_ILLEGAL = 27,
+ AR5K_RX_PHY_ERROR_CCK_SERVICE = 30,
+ AR5K_RX_PHY_ERROR_CCK_RESTART = 31,
+};

/*
* 5210/5211 hardware 2-word TX control descriptor


2010-03-25 05:49:43

by Bruno Randolf

[permalink] [raw]
Subject: [PATCH 07/10] ath5k: simplify MIB counters

Let's keep MIB counter statistics in our own statistics structure and only
convert it to ieee80211_low_level_stats when needed by mac80211. Also we don't
need to read profile count registers in the MIB interrupt (they don't trigger
MIB interrupts).

Signed-off-by: Bruno Randolf <[email protected]>
---
drivers/net/wireless/ath/ath5k/ath5k.h | 3 +-
drivers/net/wireless/ath/ath5k/base.c | 16 ++++++-------
drivers/net/wireless/ath/ath5k/base.h | 13 +++++++++--
drivers/net/wireless/ath/ath5k/pcu.c | 39 +++++++++++---------------------
4 files changed, 32 insertions(+), 39 deletions(-)

diff --git a/drivers/net/wireless/ath/ath5k/ath5k.h b/drivers/net/wireless/ath/ath5k/ath5k.h
index cefd28d..ec62690 100644
--- a/drivers/net/wireless/ath/ath5k/ath5k.h
+++ b/drivers/net/wireless/ath/ath5k/ath5k.h
@@ -1173,8 +1173,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/base.c b/drivers/net/wireless/ath/ath5k/base.c
index ba2fad2..ea33b99 100644
--- a/drivers/net/wireless/ath/ath5k/base.c
+++ b/drivers/net/wireless/ath/ath5k/base.c
@@ -2114,7 +2114,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++;
@@ -2708,11 +2708,7 @@ ath5k_intr(int irq, void *dev_id)
/* TODO */
}
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);
+ ath5k_hw_update_mib_counters(ah);
}
if (status & AR5K_INT_GPIO)
tasklet_schedule(&sc->rf_kill.toggleq);
@@ -3234,12 +3230,14 @@ ath5k_get_stats(struct ieee80211_hw *hw,
struct ieee80211_low_level_stats *stats)
{
struct ath5k_softc *sc = hw->priv;
- struct ath5k_hw *ah = sc->ah;

/* Force update */
- ath5k_hw_update_mib_counters(ah, &sc->ll_stats);
+ ath5k_hw_update_mib_counters(sc->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..fe5dae5 100644
--- a/drivers/net/wireless/ath/ath5k/base.h
+++ b/drivers/net/wireless/ath/ath5k/base.h
@@ -105,10 +105,13 @@ struct ath5k_rfkill {
struct tasklet_struct toggleq;
};

-/* statistics (only used for debugging now) */
+/* statistics */
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 */
+
+ /* frame errors */
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,13 @@ 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;
};

#if CHAN_DEBUG
@@ -135,7 +145,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/pcu.c b/drivers/net/wireless/ath/ath5k/pcu.c
index 1b9fcb8..c7c1fe0 100644
--- a/drivers/net/wireless/ath/ath5k/pcu.c
+++ b/drivers/net/wireless/ath/ath5k/pcu.c
@@ -113,39 +113,26 @@ 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.
+ * Reads MIB counters from PCU and updates sw statistics. Is called after a
+ * MIB interrupt, because one of these counters might have reached their maximum
+ * and triggered the MIB interrupt, to let us read and clear the counter.
+ *
+ * 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);
}

/**


2010-03-25 11:05:05

by Joerg

[permalink] [raw]
Subject: Re: [PATCH 10/10] ath5k: Adaptive Noise Immunity (ANI) Implementation

Bruno Randolf <br1@...> writes:
>
> +/* debugfs: ani */
> +
> +static ssize_t read_file_ani(struct file *file, char __user *user_buf,
> + size_t count, loff_t *ppos)
> +{
> + return simple_read_from_buffer(user_buf, count, ppos, buf, len);
...
> +}

I would prefer a machine readable solution, e.g. individual files
in an ANI directory with one value each.

> +static ssize_t write_file_ani(struct file *file,
> + const char __user *userbuf,
> + size_t count, loff_t *ppos)
> +{
...
> + return count;
> +}

Same as above: instead of a catch all with magic configuration words, use
individual files with at least guessable values (e.g. 0/1 for enable/disable
of individual features).

Otherwise I'm excited to see ath5k become feature complete compared with
Madwifi.

Regards
Joerg