diff mbox

[2/2] ath10k: add spectral scan feature

Message ID 1405689992-8538-3-git-send-email-sw@simonwunderlich.de (mailing list archive)
State Not Applicable, archived
Headers show

Commit Message

Simon Wunderlich July 18, 2014, 1:26 p.m. UTC
Adds the spectral scan feature for ath10k. The spectral scan is triggered by
configuring a mode through a debugfs control file. Samples can be gathered via
another relay debugfs file.

Essentially, to try it out:

ip link dev wlan0 set up
echo background > /sys/kernel/debug/ieee80211/phy0/ath10k/spectral_scan_ctl
iw dev wlan0 scan
echo disable > /sys/kernel/debug/ieee80211/phy0/ath10k/spectral_scan_ctl
cat /sys/kernel/debug/ieee80211/phy0/ath10k/spectral_scan0 > samples

This feature is still experimental. Based on the original RFC patch of
Sven Eckelmann.

Signed-off-by: Simon Wunderlich <sw@simonwunderlich.de>
Signed-off-by: Mathias Kretschmer <mathias.kretschmer@fokus.fraunhofer.de>
--
PATCH:
 * change the tlv format to a variable-length format, depending on the
   number of bins
 * perform dc interpolation and max_exp calculation in the driver (as
   for ath9k)
 * make fft_size configurable
 * background scan will only start after beeing trigger, like in ath9k
 * document at least some spectral configuration members (thanks Kathy)
 * fix sparse problems
 * sample bandwidth appears to be 110% of what is reported by the
   hardware according to the plotter (e.g. 20->22, 40->44 MHz etc),
   so fix that in the driver.
 * disable spectral for a vif when it's beeing deleted and still active
   (Michals comment)
 * modify ath_ prefix of function/struct/enum to ath10k_ (Michal)
 * changed variable ret to res (Michal)
 * added ath10k_warn messages (Michal)
 * renamed wmi functions (Michal)
 * use ar->conf_mutex for locking (Michal)
 * renamed mhz to chan_width_mhz (Michal)
 * only enable spectral scan files when ATH10K_DEBUGFS is enabled
---
 drivers/net/wireless/ath/ath10k/Kconfig    |   1 +
 drivers/net/wireless/ath/ath10k/Makefile   |   3 +-
 drivers/net/wireless/ath/ath10k/core.c     |   4 +
 drivers/net/wireless/ath/ath10k/core.h     |   7 +
 drivers/net/wireless/ath/ath10k/debug.c    |   3 +
 drivers/net/wireless/ath/ath10k/mac.c      |   8 +
 drivers/net/wireless/ath/ath10k/spectral.c | 558 +++++++++++++++++++++++++++++
 drivers/net/wireless/ath/ath10k/spectral.h |  64 ++++
 drivers/net/wireless/ath/ath10k/wmi.c      | 104 +++++-
 drivers/net/wireless/ath/ath10k/wmi.h      |  94 +++++
 drivers/net/wireless/ath/spectral_common.h |  24 ++
 11 files changed, 868 insertions(+), 2 deletions(-)
 create mode 100644 drivers/net/wireless/ath/ath10k/spectral.c
 create mode 100644 drivers/net/wireless/ath/ath10k/spectral.h

Comments

Chun-Yeow Yeoh July 21, 2014, 4:15 a.m. UTC | #1
On Fri, Jul 18, 2014 at 9:26 PM, Simon Wunderlich <sw@simonwunderlich.de> wrote:
> Adds the spectral scan feature for ath10k. The spectral scan is triggered by
> configuring a mode through a debugfs control file. Samples can be gathered via
> another relay debugfs file.
>
> Essentially, to try it out:
>
> ip link dev wlan0 set up

It should be "ip link set dev wlan0 up", right?

I have tried to capture the samples on ath10k and open it using fft_eval.

But found the following errors in my samples "invalid bin length 63".

I will send the samples in separate email.

---
Chun-Yeow
--
To unsubscribe from this list: send the line "unsubscribe linux-wireless" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Michal Kazior July 21, 2014, 6:56 a.m. UTC | #2
On 18 July 2014 15:26, Simon Wunderlich <sw@simonwunderlich.de> wrote:
> Adds the spectral scan feature for ath10k. The spectral scan is triggered by
> configuring a mode through a debugfs control file. Samples can be gathered via
> another relay debugfs file.
[...]
> diff --git a/drivers/net/wireless/ath/ath10k/core.h b/drivers/net/wireless/ath/ath10k/core.h
> index 80aa5a4..5fc7ff5 100644
> --- a/drivers/net/wireless/ath/ath10k/core.h
> +++ b/drivers/net/wireless/ath/ath10k/core.h
> @@ -31,6 +31,7 @@
>  #include "../ath.h"
>  #include "../regd.h"
>  #include "../dfs_pattern_detector.h"
> +#include "spectral.h"
>
>  #define MS(_v, _f) (((_v) & _f##_MASK) >> _f##_LSB)
>  #define SM(_v, _f) (((_v) << _f##_LSB) & _f##_MASK)
> @@ -230,6 +231,7 @@ struct ath10k_vif {
>
>         bool is_started;
>         bool is_up;
> +       bool spectral_enabled;
>         u32 aid;
>         u8 bssid[ETH_ALEN];
>
> @@ -469,6 +471,11 @@ struct ath10k {
>  #ifdef CONFIG_ATH10K_DEBUGFS
>         struct ath10k_debug debug;
>  #endif
> +
> +       /* relay(fs) channel for spectral scan */
> +       struct rchan *rfs_chan_spec_scan;
> +       enum ath10k_spectral_mode spectral_mode;
> +       struct ath10k_spec_scan spec_config;

It's probably a good idea to add a comment stating that spectral_mode
and spec_config are conf_mutex protected.


>  };
>
>  struct ath10k *ath10k_core_create(void *hif_priv, struct device *dev,
> diff --git a/drivers/net/wireless/ath/ath10k/debug.c b/drivers/net/wireless/ath/ath10k/debug.c
> index a84691e..e39aa96 100644
> --- a/drivers/net/wireless/ath/ath10k/debug.c
> +++ b/drivers/net/wireless/ath/ath10k/debug.c
> @@ -827,12 +827,15 @@ int ath10k_debug_create(struct ath10k *ar)
>                                     &fops_dfs_stats);
>         }
>
> +       ath10k_spectral_init_debug(ar);
> +
>         return 0;
>  }
>
>  void ath10k_debug_destroy(struct ath10k *ar)
>  {
>         cancel_delayed_work_sync(&ar->debug.htt_stats_dwork);
> +       ath10k_spectral_deinit_debug(ar);
>  }
>
>  #endif /* CONFIG_ATH10K_DEBUGFS */
> diff --git a/drivers/net/wireless/ath/ath10k/mac.c b/drivers/net/wireless/ath/ath10k/mac.c
> index 348a639..c369ac1 100644
> --- a/drivers/net/wireless/ath/ath10k/mac.c
> +++ b/drivers/net/wireless/ath/ath10k/mac.c
> @@ -2671,8 +2671,16 @@ static void ath10k_remove_interface(struct ieee80211_hw *hw,
>                 dev_kfree_skb_any(arvif->beacon);
>                 arvif->beacon = NULL;
>         }
> +
>         spin_unlock_bh(&ar->data_lock);
>
> +       if (arvif->spectral_enabled) {
> +               ret = ath10k_interface_disable_spectral(arvif);
> +               if (ret)
> +                       ath10k_warn("spectral disable for vdev %i failed\n",
> +                                   arvif->vdev_id);

We tend to put verbs first, e.g. "failed to disable spectral scan for
vdev %i: %d\n". Also, your warning is missing "ret" here.


[...]
> +#include <linux/relay.h>
> +#include "core.h"
> +#include "debug.h"
> +
> +static void ath10k_debug_send_fft_sample(struct ath10k *ar,
> +                                        struct fft_sample_tlv *fft_sample_tlv)

I guess fft_sample_tlv could be const. Other functions could also have
const arguments. But I'm just being picky now :-)


> +int ath10k_process_fft(struct ath10k *ar,
> +                      struct wmi_single_phyerr_rx_event *event,
> +                      struct phyerr_fft_report *fftr, size_t bin_len, u64 tsf)
> +{
[...]
> +       nf_list1 = __le32_to_cpu(event->hdr.nf_list_1);
> +       nf_list2 = __le32_to_cpu(event->hdr.nf_list_2);
> +       chain_idx = MS(reg0, SEARCH_FFT_REPORT_REG0_FFT_CHN_IDX);
> +       switch (chain_idx) {
> +       case 0:
> +               fft_sample->noise = __cpu_to_be16(nf_list1 & 0xffffu);

Are you sure you want to & nf_list1 itself *before* the byte swap? You
probably won't see a difference with an intel host system which is
little-endian just like the target device.


> +               break;
> +       case 1:
> +               fft_sample->noise = __cpu_to_be16((nf_list1 >> 16) & 0xffffu);
> +               break;
> +       case 2:
> +               fft_sample->noise = __cpu_to_be16(nf_list2 & 0xffffu);
> +               break;
> +       case 3:
> +               fft_sample->noise = __cpu_to_be16((nf_list2 >> 16) & 0xffffu);
> +               break;
> +       }

Ditto for the above.


> +static int ath10k_spectral_scan_trigger(struct ath10k *ar)
> +{
> +       struct ath10k_vif *arvif;
> +       int res;
> +       int vdev_id;
> +

You access ar->spectral_mode here which requires conf_mutex to be
held. I suggest lockdep_assert_held() to be put here.


> +       arvif = ath10k_get_spectral_vdev(ar);
> +       if (!arvif)
> +               return -ENODEV;
> +       vdev_id = arvif->vdev_id;
> +
> +       if (ar->spectral_mode == SPECTRAL_DISABLED)
> +               return 0;
> +
> +       res = ath10k_wmi_vdev_spectral_enable(ar, vdev_id,
> +                                             WMI_SPECTRAL_TRIGGER_CMD_CLEAR,
> +                                             WMI_SPECTRAL_ENABLE_CMD_ENABLE);
> +       if (res < 0)
> +               return res;
> +
> +       res = ath10k_wmi_vdev_spectral_enable(ar, vdev_id,
> +                                             WMI_SPECTRAL_TRIGGER_CMD_TRIGGER,
> +                                             WMI_SPECTRAL_ENABLE_CMD_ENABLE);
> +       if (res < 0)
> +               return res;
> +
> +       return 0;
> +}
> +static int ath10k_spectral_scan_config(struct ath10k *ar,
> +                                      enum ath10k_spectral_mode mode)
> +{
> +       struct wmi_vdev_spectral_conf_arg arg;
> +       struct ath10k_vif *arvif;
> +       int vdev_id, count, res = 0;
> +

Ditto. lockdep_assert_held().


> +       arvif = ath10k_get_spectral_vdev(ar);
> +       if (!arvif)
> +               return -ENODEV;
> +
> +       vdev_id = arvif->vdev_id;
> +
> +       arvif->spectral_enabled = (mode != SPECTRAL_DISABLED);
> +       ar->spectral_mode = mode;
> +
> +       res = ath10k_wmi_vdev_spectral_enable(ar, vdev_id,
> +                                             WMI_SPECTRAL_TRIGGER_CMD_CLEAR,
> +                                             WMI_SPECTRAL_ENABLE_CMD_DISABLE);
> +       if (res < 0) {
> +               ath10k_warn("failed to enable spectral scan: %d\n", res);
> +               return res;
> +       }
> +
> +       if (mode == SPECTRAL_DISABLED)
> +               return 0;
> +
> +       if (mode == SPECTRAL_BACKGROUND)
> +               count = WMI_SPECTRAL_COUNT_DEFAULT;
> +       else
> +               count = max_t(u8, 1, ar->spec_config.count);
> +
> +       arg.vdev_id              = vdev_id;
> +       arg.scan_count           = count;
> +       arg.scan_period          = WMI_SPECTRAL_PERIOD_DEFAULT;
> +       arg.scan_priority        = WMI_SPECTRAL_PRIORITY_DEFAULT;
> +       arg.scan_fft_size        = ar->spec_config.fft_size;
> +       arg.scan_gc_ena          = WMI_SPECTRAL_GC_ENA_DEFAULT;
> +       arg.scan_restart_ena     = WMI_SPECTRAL_RESTART_ENA_DEFAULT;
> +       arg.scan_noise_floor_ref = WMI_SPECTRAL_NOISE_FLOOR_REF_DEFAULT;
> +       arg.scan_init_delay      = WMI_SPECTRAL_INIT_DELAY_DEFAULT;
> +       arg.scan_nb_tone_thr     = WMI_SPECTRAL_NB_TONE_THR_DEFAULT;
> +       arg.scan_str_bin_thr     = WMI_SPECTRAL_STR_BIN_THR_DEFAULT;
> +       arg.scan_wb_rpt_mode     = WMI_SPECTRAL_WB_RPT_MODE_DEFAULT;
> +       arg.scan_rssi_rpt_mode   = WMI_SPECTRAL_RSSI_RPT_MODE_DEFAULT;
> +       arg.scan_rssi_thr        = WMI_SPECTRAL_RSSI_THR_DEFAULT;
> +       arg.scan_pwr_format      = WMI_SPECTRAL_PWR_FORMAT_DEFAULT;
> +       arg.scan_rpt_mode        = WMI_SPECTRAL_RPT_MODE_DEFAULT;
> +       arg.scan_bin_scale       = WMI_SPECTRAL_BIN_SCALE_DEFAULT;
> +       arg.scan_dbm_adj         = WMI_SPECTRAL_DBM_ADJ_DEFAULT;
> +       arg.scan_chn_mask        = WMI_SPECTRAL_CHN_MASK_DEFAULT;
> +
> +       res = ath10k_wmi_vdev_spectral_conf(ar, &arg);
> +       if (res < 0) {
> +               ath10k_warn("failed to configure spectral scan: %d\n", res);
> +               return res;
> +       }
> +
> +       return 0;
> +}


Did you test this against a firmware crash while spectral scan is
enabled? ar->spectral_mode will be left as-is when restart is
performed but no spectral scan will be actually configured. You either
need to restart spectral scan (better from user perspective) or clear
out the old mode (easier to code, but bad from user perspective
because suddenly spectral scan would be stopped implicitly by device
crash).


Micha?
--
To unsubscribe from this list: send the line "unsubscribe linux-wireless" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Simon Wunderlich July 21, 2014, 9:43 a.m. UTC | #3
> On Fri, Jul 18, 2014 at 9:26 PM, Simon Wunderlich <sw@simonwunderlich.de> 
wrote:
> > Adds the spectral scan feature for ath10k. The spectral scan is triggered
> > by configuring a mode through a debugfs control file. Samples can be
> > gathered via another relay debugfs file.
> > 
> > Essentially, to try it out:
> > 
> > ip link dev wlan0 set up
> 
> It should be "ip link set dev wlan0 up", right?

Whoops, yeah, that's right, I'll change that in the next iteration.
> 
> I have tried to capture the samples on ath10k and open it using fft_eval.
> 
> But found the following errors in my samples "invalid bin length 63".
> 
> I will send the samples in separate email.

Thanks for the output! Could it be that you are using the old RFC patch with 
the new fft-eval? I've added another byte (max_exp), so the header became 
longer. Please try again with the latest patch (from this thread), if that is 
the case. Otherwise we have to find out whats wrong ... The sample_len as used 
in fft_eval (tlv-header + data) should be 93 with that version.

Thanks!
    Simon
--
To unsubscribe from this list: send the line "unsubscribe linux-wireless" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Simon Wunderlich July 21, 2014, 9:59 a.m. UTC | #4
Hey Michal,

thanks for the review! I agree to most points and will fixed them in the next 
revision, for the rest I'm putting comments below:

[...]
> > +       nf_list1 = __le32_to_cpu(event->hdr.nf_list_1);
> > +       nf_list2 = __le32_to_cpu(event->hdr.nf_list_2);
> > +       chain_idx = MS(reg0, SEARCH_FFT_REPORT_REG0_FFT_CHN_IDX);
> > +       switch (chain_idx) {
> > +       case 0:
> > +               fft_sample->noise = __cpu_to_be16(nf_list1 & 0xffffu);
> 
> Are you sure you want to & nf_list1 itself *before* the byte swap? You
> probably won't see a difference with an intel host system which is
> little-endian just like the target device.

That is intended as written: with le32_to_cpu() above we get the data into 
host order to process it, then we get the 16-bit noise values according to the 
chain index, and finally convert it to big endian as this is our exchange 
format to userspace - as you can see, we do the same for the other 16bit 
members of fft_sample as well.

[...]
> > +       arvif = ath10k_get_spectral_vdev(ar);
> > +       if (!arvif)
> > +               return -ENODEV;
> > +       vdev_id = arvif->vdev_id;
> > +
> > +       if (ar->spectral_mode == SPECTRAL_DISABLED)
> > +               return 0;
> > +
> > +       res = ath10k_wmi_vdev_spectral_enable(ar, vdev_id,
> > +                                            
> > WMI_SPECTRAL_TRIGGER_CMD_CLEAR, +                                       
> >      WMI_SPECTRAL_ENABLE_CMD_ENABLE); +       if (res < 0)
> > +               return res;
> > +
> > +       res = ath10k_wmi_vdev_spectral_enable(ar, vdev_id,
> > +                                            
> > WMI_SPECTRAL_TRIGGER_CMD_TRIGGER, +                                     
> >        WMI_SPECTRAL_ENABLE_CMD_ENABLE); +       if (res < 0)
> > +               return res;
> > +
> > +       return 0;
> > +}
> > +static int ath10k_spectral_scan_config(struct ath10k *ar,
> > +                                      enum ath10k_spectral_mode mode)
> > +{
> > +       struct wmi_vdev_spectral_conf_arg arg;
> > +       struct ath10k_vif *arvif;
> > +       int vdev_id, count, res = 0;
> > +
> 
> Ditto. lockdep_assert_held().

Actually both functions are calling ath10k_get_spectral_vdev() which already 
has that assert, but it doesn't hurt to put it here as well. Will do. :)
[...]

> 
> Did you test this against a firmware crash while spectral scan is
> enabled? ar->spectral_mode will be left as-is when restart is
> performed but no spectral scan will be actually configured. You either
> need to restart spectral scan (better from user perspective) or clear
> out the old mode (easier to code, but bad from user perspective
> because suddenly spectral scan would be stopped implicitly by device
> crash).

No, I didn't test that against a firmware crash yet ... Restarting spectral 
would probably a good idea in endless mode, but not if "count" has been set. 
I'll look into that and propose something in the next iteration.

Thanks. :)
    Simon
--
To unsubscribe from this list: send the line "unsubscribe linux-wireless" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Michal Kazior July 21, 2014, 10:15 a.m. UTC | #5
On 21 July 2014 11:59, Simon Wunderlich <sw@simonwunderlich.de> wrote:
> Hey Michal,
>
> thanks for the review! I agree to most points and will fixed them in the next
> revision, for the rest I'm putting comments below:
>
> [...]
>> > +       nf_list1 = __le32_to_cpu(event->hdr.nf_list_1);
>> > +       nf_list2 = __le32_to_cpu(event->hdr.nf_list_2);
>> > +       chain_idx = MS(reg0, SEARCH_FFT_REPORT_REG0_FFT_CHN_IDX);
>> > +       switch (chain_idx) {
>> > +       case 0:
>> > +               fft_sample->noise = __cpu_to_be16(nf_list1 & 0xffffu);
>>
>> Are you sure you want to & nf_list1 itself *before* the byte swap? You
>> probably won't see a difference with an intel host system which is
>> little-endian just like the target device.
>
> That is intended as written: with le32_to_cpu() above we get the data into
> host order to process it, then we get the 16-bit noise values according to the
> chain index, and finally convert it to big endian as this is our exchange
> format to userspace - as you can see, we do the same for the other 16bit
> members of fft_sample as well.

Ah, I guess I should put on my glasses now. I parsed that line as
_to_cpu(). Sorry :-)


> [...]
>> > +       arvif = ath10k_get_spectral_vdev(ar);
>> > +       if (!arvif)
>> > +               return -ENODEV;
>> > +       vdev_id = arvif->vdev_id;
>> > +
>> > +       if (ar->spectral_mode == SPECTRAL_DISABLED)
>> > +               return 0;
>> > +
>> > +       res = ath10k_wmi_vdev_spectral_enable(ar, vdev_id,
>> > +
>> > WMI_SPECTRAL_TRIGGER_CMD_CLEAR, +
>> >      WMI_SPECTRAL_ENABLE_CMD_ENABLE); +       if (res < 0)
>> > +               return res;
>> > +
>> > +       res = ath10k_wmi_vdev_spectral_enable(ar, vdev_id,
>> > +
>> > WMI_SPECTRAL_TRIGGER_CMD_TRIGGER, +
>> >        WMI_SPECTRAL_ENABLE_CMD_ENABLE); +       if (res < 0)
>> > +               return res;
>> > +
>> > +       return 0;
>> > +}
>> > +static int ath10k_spectral_scan_config(struct ath10k *ar,
>> > +                                      enum ath10k_spectral_mode mode)
>> > +{
>> > +       struct wmi_vdev_spectral_conf_arg arg;
>> > +       struct ath10k_vif *arvif;
>> > +       int vdev_id, count, res = 0;
>> > +
>>
>> Ditto. lockdep_assert_held().
>
> Actually both functions are calling ath10k_get_spectral_vdev() which already
> has that assert, but it doesn't hurt to put it here as well. Will do. :)

True but those functions (scan_config, scan_trigger) also access
conf_mutex protected data themselves directly. If
ath10k_get_spectral_vdev() was to be changed to not require conf_mutex
you'd have to remember to move lockdep_assert_held() up the call
stack.


>> Did you test this against a firmware crash while spectral scan is
>> enabled? ar->spectral_mode will be left as-is when restart is
>> performed but no spectral scan will be actually configured. You either
>> need to restart spectral scan (better from user perspective) or clear
>> out the old mode (easier to code, but bad from user perspective
>> because suddenly spectral scan would be stopped implicitly by device
>> crash).
>
> No, I didn't test that against a firmware crash yet ...

There's 'simulate_fw_crash' in debugfs. I suggest 'hard' method.
'soft' works only with fw 636 now. Watch out for possible host lockups
though.


> Restarting spectral
> would probably a good idea in endless mode, but not if "count" has been set.

Hmm good point, but then what if you have count=20 and fw crashes in
the middle of performing the spectral scan run (e.g. at iteration 10)?

But then this is just a debug interface (or is it?). I don't know if
it's worth it going to great lengths to provide a super smooth user
experience.


Micha?
--
To unsubscribe from this list: send the line "unsubscribe linux-wireless" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Simon Wunderlich July 21, 2014, 12:13 p.m. UTC | #6
> >> > +       arvif = ath10k_get_spectral_vdev(ar);
> >> > +       if (!arvif)
> >> > +               return -ENODEV;
> >> > +       vdev_id = arvif->vdev_id;
> >> > +
> >> > +       if (ar->spectral_mode == SPECTRAL_DISABLED)
> >> > +               return 0;
> >> > +
> >> > +       res = ath10k_wmi_vdev_spectral_enable(ar, vdev_id,
> >> > +
> >> > WMI_SPECTRAL_TRIGGER_CMD_CLEAR, +
> >> > 
> >> >      WMI_SPECTRAL_ENABLE_CMD_ENABLE); +       if (res < 0)
> >> > 
> >> > +               return res;
> >> > +
> >> > +       res = ath10k_wmi_vdev_spectral_enable(ar, vdev_id,
> >> > +
> >> > WMI_SPECTRAL_TRIGGER_CMD_TRIGGER, +
> >> > 
> >> >        WMI_SPECTRAL_ENABLE_CMD_ENABLE); +       if (res < 0)
> >> > 
> >> > +               return res;
> >> > +
> >> > +       return 0;
> >> > +}
> >> > +static int ath10k_spectral_scan_config(struct ath10k *ar,
> >> > +                                      enum ath10k_spectral_mode mode)
> >> > +{
> >> > +       struct wmi_vdev_spectral_conf_arg arg;
> >> > +       struct ath10k_vif *arvif;
> >> > +       int vdev_id, count, res = 0;
> >> > +
> >> 
> >> Ditto. lockdep_assert_held().
> > 
> > Actually both functions are calling ath10k_get_spectral_vdev() which
> > already has that assert, but it doesn't hurt to put it here as well.
> > Will do. :)
> 
> True but those functions (scan_config, scan_trigger) also access
> conf_mutex protected data themselves directly. If
> ath10k_get_spectral_vdev() was to be changed to not require conf_mutex
> you'd have to remember to move lockdep_assert_held() up the call
> stack.

Ah, that's right. I'll add that anyway. :)

> 
> >> Did you test this against a firmware crash while spectral scan is
> >> enabled? ar->spectral_mode will be left as-is when restart is
> >> performed but no spectral scan will be actually configured. You either
> >> need to restart spectral scan (better from user perspective) or clear
> >> out the old mode (easier to code, but bad from user perspective
> >> because suddenly spectral scan would be stopped implicitly by device
> >> crash).
> > 
> > No, I didn't test that against a firmware crash yet ...
> 
> There's 'simulate_fw_crash' in debugfs. I suggest 'hard' method.
> 'soft' works only with fw 636 now. Watch out for possible host lockups
> though.
> 

Thanks, the hard firmware crash worked for me.

> > Restarting spectral
> > would probably a good idea in endless mode, but not if "count" has been
> > set.
> 
> Hmm good point, but then what if you have count=20 and fw crashes in
> the middle of performing the spectral scan run (e.g. at iteration 10)?
> 
> But then this is just a debug interface (or is it?). I don't know if
> it's worth it going to great lengths to provide a super smooth user
> experience.

Well yeah, it's in debugfs so its a debug interface. ;) Since we don't really 
keep track whether a scan was triggered or not, and the firmware takes some 
time to recover, I don't think it's worth the hassle - without creating more 
confusion. I'll reset the spectral mode in any case so users at least see that 
its disabled.

I'll send the next round of patches in some minutes. :)

Thanks!
    Simon
--
To unsubscribe from this list: send the line "unsubscribe linux-wireless" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Chun-Yeow Yeoh July 22, 2014, 4:59 a.m. UTC | #7
Hi, Simon

>
> Thanks for the output! Could it be that you are using the old RFC patch with
> the new fft-eval? I've added another byte (max_exp), so the header became
> longer. Please try again with the latest patch (from this thread), if that is
> the case. Otherwise we have to find out whats wrong ... The sample_len as used
> in fft_eval (tlv-header + data) should be 93 with that version.

Yes, you are right. I am picking out the ath-next-test branch which is
using the RFC old patch before that.

By the way, the new submitted patch v2 [1] has taken out the following:

if (mode == SPECTRAL_BACKGROUND)ath10k_spectral_scan_trigger(ar);


-----
Chun-Yeow

[1] http://lists.infradead.org/pipermail/ath10k/2014-July/002711.html
--
To unsubscribe from this list: send the line "unsubscribe linux-wireless" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Chun-Yeow Yeoh July 22, 2014, 5:02 a.m. UTC | #8
Hi, Simon

Sorry, resend-email...

>>
>> Thanks for the output! Could it be that you are using the old RFC patch with
>> the new fft-eval? I've added another byte (max_exp), so the header became
>> longer. Please try again with the latest patch (from this thread), if that is
>> the case. Otherwise we have to find out whats wrong ... The sample_len as used
>> in fft_eval (tlv-header + data) should be 93 with that version.
>
Yes, you are right. I am picking out the ath-next-test branch which is
using the RFC old patch before that.

By the way, the new submitted patch v2 [1] has taken out the following:

if (mode == SPECTRAL_BACKGROUND)
ath10k_spectral_scan_trigger(ar);

So should we add the trigger command in the comment?

ip link set dev wlan0 up
echo background > /sys/kernel/debug/ieee80211/phy0/ath10k/spectral_scan_ctl
echo trigger > /sys/kernel/debug/ieee80211/phy0/ath10k/spectral_scan_ctl
iw dev wlan0 scan
echo disable > /sys/kernel/debug/ieee80211/phy0/ath10k/spectral_scan_ctl
cat /sys/kernel/debug/ieee80211/phy0/ath10k/spectral_scan0 > samples

Otherwise, I am not able to get any useful data.

-----
Chun-Yeow

[1] http://lists.infradead.org/pipermail/ath10k/2014-July/002711.html
--
To unsubscribe from this list: send the line "unsubscribe linux-wireless" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
diff mbox

Patch

diff --git a/drivers/net/wireless/ath/ath10k/Kconfig b/drivers/net/wireless/ath/ath10k/Kconfig
index 489bf8b..095dcaf 100644
--- a/drivers/net/wireless/ath/ath10k/Kconfig
+++ b/drivers/net/wireless/ath/ath10k/Kconfig
@@ -27,6 +27,7 @@  config ATH10K_DEBUG
 config ATH10K_DEBUGFS
 	bool "Atheros ath10k debugfs support"
 	depends on ATH10K
+	select RELAY
 	---help---
 	  Enabled debugfs support
 
diff --git a/drivers/net/wireless/ath/ath10k/Makefile b/drivers/net/wireless/ath/ath10k/Makefile
index 2110a5c..e1dcefd 100644
--- a/drivers/net/wireless/ath/ath10k/Makefile
+++ b/drivers/net/wireless/ath/ath10k/Makefile
@@ -8,7 +8,8 @@  ath10k_core-y += mac.o \
 		 htt_tx.o \
 		 txrx.o \
 		 wmi.o \
-		 bmi.o
+		 bmi.o \
+		 spectral.o
 
 ath10k_core-$(CONFIG_ATH10K_TRACING) += trace.o
 
diff --git a/drivers/net/wireless/ath/ath10k/core.c b/drivers/net/wireless/ath/ath10k/core.c
index ebc5fc2..3a113fb 100644
--- a/drivers/net/wireless/ath/ath10k/core.c
+++ b/drivers/net/wireless/ath/ath10k/core.c
@@ -699,6 +699,10 @@  struct ath10k *ath10k_core_create(void *hif_priv, struct device *dev,
 	ar->hif.priv = hif_priv;
 	ar->hif.ops = hif_ops;
 
+	ar->spectral_mode = SPECTRAL_DISABLED;
+	ar->spec_config.count = WMI_SPECTRAL_COUNT_DEFAULT;
+	ar->spec_config.fft_size = WMI_SPECTRAL_FFT_SIZE_DEFAULT;
+
 	init_completion(&ar->scan.started);
 	init_completion(&ar->scan.completed);
 	init_completion(&ar->scan.on_channel);
diff --git a/drivers/net/wireless/ath/ath10k/core.h b/drivers/net/wireless/ath/ath10k/core.h
index 80aa5a4..5fc7ff5 100644
--- a/drivers/net/wireless/ath/ath10k/core.h
+++ b/drivers/net/wireless/ath/ath10k/core.h
@@ -31,6 +31,7 @@ 
 #include "../ath.h"
 #include "../regd.h"
 #include "../dfs_pattern_detector.h"
+#include "spectral.h"
 
 #define MS(_v, _f) (((_v) & _f##_MASK) >> _f##_LSB)
 #define SM(_v, _f) (((_v) << _f##_LSB) & _f##_MASK)
@@ -230,6 +231,7 @@  struct ath10k_vif {
 
 	bool is_started;
 	bool is_up;
+	bool spectral_enabled;
 	u32 aid;
 	u8 bssid[ETH_ALEN];
 
@@ -469,6 +471,11 @@  struct ath10k {
 #ifdef CONFIG_ATH10K_DEBUGFS
 	struct ath10k_debug debug;
 #endif
+
+	/* relay(fs) channel for spectral scan */
+	struct rchan *rfs_chan_spec_scan;
+	enum ath10k_spectral_mode spectral_mode;
+	struct ath10k_spec_scan spec_config;
 };
 
 struct ath10k *ath10k_core_create(void *hif_priv, struct device *dev,
diff --git a/drivers/net/wireless/ath/ath10k/debug.c b/drivers/net/wireless/ath/ath10k/debug.c
index a84691e..e39aa96 100644
--- a/drivers/net/wireless/ath/ath10k/debug.c
+++ b/drivers/net/wireless/ath/ath10k/debug.c
@@ -827,12 +827,15 @@  int ath10k_debug_create(struct ath10k *ar)
 				    &fops_dfs_stats);
 	}
 
+	ath10k_spectral_init_debug(ar);
+
 	return 0;
 }
 
 void ath10k_debug_destroy(struct ath10k *ar)
 {
 	cancel_delayed_work_sync(&ar->debug.htt_stats_dwork);
+	ath10k_spectral_deinit_debug(ar);
 }
 
 #endif /* CONFIG_ATH10K_DEBUGFS */
diff --git a/drivers/net/wireless/ath/ath10k/mac.c b/drivers/net/wireless/ath/ath10k/mac.c
index 348a639..c369ac1 100644
--- a/drivers/net/wireless/ath/ath10k/mac.c
+++ b/drivers/net/wireless/ath/ath10k/mac.c
@@ -2671,8 +2671,16 @@  static void ath10k_remove_interface(struct ieee80211_hw *hw,
 		dev_kfree_skb_any(arvif->beacon);
 		arvif->beacon = NULL;
 	}
+
 	spin_unlock_bh(&ar->data_lock);
 
+	if (arvif->spectral_enabled) {
+		ret = ath10k_interface_disable_spectral(arvif);
+		if (ret)
+			ath10k_warn("spectral disable for vdev %i failed\n",
+				    arvif->vdev_id);
+	}
+
 	ar->free_vdev_map |= 1 << (arvif->vdev_id);
 	list_del(&arvif->list);
 
diff --git a/drivers/net/wireless/ath/ath10k/spectral.c b/drivers/net/wireless/ath/ath10k/spectral.c
new file mode 100644
index 0000000..a7a74f7
--- /dev/null
+++ b/drivers/net/wireless/ath/ath10k/spectral.c
@@ -0,0 +1,558 @@ 
+/*
+ * Copyright (c) 2013 Qualcomm Atheros, Inc.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <linux/relay.h>
+#include "core.h"
+#include "debug.h"
+
+static void ath10k_debug_send_fft_sample(struct ath10k *ar,
+					 struct fft_sample_tlv *fft_sample_tlv)
+{
+	int length;
+
+	if (!ar->rfs_chan_spec_scan)
+		return;
+
+	length = __be16_to_cpu(fft_sample_tlv->length) +
+		 sizeof(*fft_sample_tlv);
+	relay_write(ar->rfs_chan_spec_scan, fft_sample_tlv, length);
+}
+
+static uint8_t get_max_exp(s8 max_index, u16 max_magnitude, size_t bin_len,
+			   u8 *data)
+{
+	int dc_pos;
+	u8 max_exp;
+
+	dc_pos = bin_len / 2;
+	/* peak index outside of bins */
+	if (dc_pos < max_index || -dc_pos >= max_index)
+		return 0;
+
+	for (max_exp = 0; max_exp < 8; max_exp++) {
+		if (data[dc_pos + max_index] == (max_magnitude >> max_exp))
+			break;
+	}
+
+	/* max_exp not found */
+	if (data[dc_pos + max_index] != (max_magnitude >> max_exp))
+		return 0;
+
+	return max_exp;
+}
+
+int ath10k_process_fft(struct ath10k *ar,
+		       struct wmi_single_phyerr_rx_event *event,
+		       struct phyerr_fft_report *fftr, size_t bin_len, u64 tsf)
+{
+	struct fft_sample_ath10k *fft_sample;
+	u8 buf[sizeof(*fft_sample) + SPECTRAL_ATH10K_MAX_NUM_BINS];
+	int dc_pos;
+	u32 reg0, reg1;
+	u16 peak_mag;
+	u8 *bins;
+	u16 freq1;
+	u16 freq2;
+	u16 total_gain_db;
+	u16 base_pwr_db;
+	u8 chain_idx;
+	u32 nf_list1;
+	u32 nf_list2;
+	u16 length;
+
+	fft_sample = (struct fft_sample_ath10k *)&buf;
+
+	if (bin_len < 64 || bin_len > SPECTRAL_ATH10K_MAX_NUM_BINS)
+		return -EINVAL;
+
+	reg0 = __le32_to_cpu(fftr->reg0);
+	reg1 = __le32_to_cpu(fftr->reg1);
+
+	length = sizeof(*fft_sample) - sizeof(struct fft_sample_tlv) + bin_len;
+	fft_sample->tlv.type = ATH_FFT_SAMPLE_ATH10K;
+	fft_sample->tlv.length = __cpu_to_be16(length);
+
+	/* TODO: there might be a reason why the hardware reports 20/40/80 MHz,
+	 * but the results/plots suggest that its actually 22/44/88 MHz.
+	 */
+	switch (event->hdr.chan_width_mhz) {
+	case 20:
+		fft_sample->chan_width_mhz = 22;
+		break;
+	case 40:
+		fft_sample->chan_width_mhz = 44;
+		break;
+	case 80:
+		/* TODO: As experiments with an analogue sender and various
+		 * configuaritions (fft-sizes of 64/128/256 and 20/40/80 Mhz)
+		 * show, the particular configuration of 80 MHz/64 bins does
+		 * not match with the other smaples at all. Until the reason
+		 * for that is found, don't report these samples.
+		 */
+		if (bin_len == 64)
+			return -EINVAL;
+		fft_sample->chan_width_mhz = 88;
+		break;
+	default:
+		fft_sample->chan_width_mhz = event->hdr.chan_width_mhz;
+	}
+	fft_sample->relpwr_db = MS(reg1, SEARCH_FFT_REPORT_REG1_RELPWR_DB);
+	fft_sample->avgpwr_db = MS(reg1, SEARCH_FFT_REPORT_REG1_AVGPWR_DB);
+
+	peak_mag = MS(reg1, SEARCH_FFT_REPORT_REG1_PEAK_MAG);
+	fft_sample->max_magnitude = __cpu_to_be16(peak_mag);
+	fft_sample->max_index = MS(reg0, SEARCH_FFT_REPORT_REG0_PEAK_SIDX);
+	fft_sample->rssi = event->hdr.rssi_combined;
+
+	total_gain_db = MS(reg0, SEARCH_FFT_REPORT_REG0_TOTAL_GAIN_DB);
+	base_pwr_db = MS(reg0, SEARCH_FFT_REPORT_REG0_BASE_PWR_DB);
+	fft_sample->total_gain_db = __cpu_to_be16(total_gain_db);
+	fft_sample->base_pwr_db = __cpu_to_be16(base_pwr_db);
+
+	freq1 = __le16_to_cpu(event->hdr.freq1);
+	freq2 = __le16_to_cpu(event->hdr.freq2);
+	fft_sample->freq1 = __cpu_to_be16(freq1);
+	fft_sample->freq2 = __cpu_to_be16(freq2);
+
+	nf_list1 = __le32_to_cpu(event->hdr.nf_list_1);
+	nf_list2 = __le32_to_cpu(event->hdr.nf_list_2);
+	chain_idx = MS(reg0, SEARCH_FFT_REPORT_REG0_FFT_CHN_IDX);
+	switch (chain_idx) {
+	case 0:
+		fft_sample->noise = __cpu_to_be16(nf_list1 & 0xffffu);
+		break;
+	case 1:
+		fft_sample->noise = __cpu_to_be16((nf_list1 >> 16) & 0xffffu);
+		break;
+	case 2:
+		fft_sample->noise = __cpu_to_be16(nf_list2 & 0xffffu);
+		break;
+	case 3:
+		fft_sample->noise = __cpu_to_be16((nf_list2 >> 16) & 0xffffu);
+		break;
+	}
+
+	bins = (u8 *)fftr;
+	bins += sizeof(*fftr);
+
+	fft_sample->tsf = __cpu_to_be64(tsf);
+	/* max_exp has been directly reported by previous hardware (ath9k),
+	 * maybe its possible to get it by other means?
+	 */
+	fft_sample->max_exp = get_max_exp(fft_sample->max_index, peak_mag,
+					  bin_len, bins);
+
+	memcpy(fft_sample->data, bins, bin_len);
+
+	/* DC value (value in the middle) is the blind spot of the spectral
+	 * sample and invalid, interpolate it.
+	 */
+	dc_pos = bin_len / 2;
+	fft_sample->data[dc_pos] = (fft_sample->data[dc_pos + 1] +
+				    fft_sample->data[dc_pos - 1]) / 2;
+
+	ath10k_debug_send_fft_sample(ar, &fft_sample->tlv);
+
+	return 0;
+}
+
+static struct ath10k_vif *ath10k_get_spectral_vdev(struct ath10k *ar)
+{
+	struct ath10k_vif *arvif;
+
+	lockdep_assert_held(&ar->conf_mutex);
+
+	if (list_empty(&ar->arvifs))
+		return NULL;
+
+	/* if there already is a vif doing spectral, return that. */
+	list_for_each_entry(arvif, &ar->arvifs, list)
+		if (arvif->spectral_enabled)
+			return arvif;
+
+	/* otherwise, return the first vif. */
+	return list_first_entry(&ar->arvifs, typeof(*arvif), list);
+}
+
+static int ath10k_spectral_scan_trigger(struct ath10k *ar)
+{
+	struct ath10k_vif *arvif;
+	int res;
+	int vdev_id;
+
+	arvif = ath10k_get_spectral_vdev(ar);
+	if (!arvif)
+		return -ENODEV;
+	vdev_id = arvif->vdev_id;
+
+	if (ar->spectral_mode == SPECTRAL_DISABLED)
+		return 0;
+
+	res = ath10k_wmi_vdev_spectral_enable(ar, vdev_id,
+					      WMI_SPECTRAL_TRIGGER_CMD_CLEAR,
+					      WMI_SPECTRAL_ENABLE_CMD_ENABLE);
+	if (res < 0)
+		return res;
+
+	res = ath10k_wmi_vdev_spectral_enable(ar, vdev_id,
+					      WMI_SPECTRAL_TRIGGER_CMD_TRIGGER,
+					      WMI_SPECTRAL_ENABLE_CMD_ENABLE);
+	if (res < 0)
+		return res;
+
+	return 0;
+}
+
+static int ath10k_spectral_scan_config(struct ath10k *ar,
+				       enum ath10k_spectral_mode mode)
+{
+	struct wmi_vdev_spectral_conf_arg arg;
+	struct ath10k_vif *arvif;
+	int vdev_id, count, res = 0;
+
+	arvif = ath10k_get_spectral_vdev(ar);
+	if (!arvif)
+		return -ENODEV;
+
+	vdev_id = arvif->vdev_id;
+
+	arvif->spectral_enabled = (mode != SPECTRAL_DISABLED);
+	ar->spectral_mode = mode;
+
+	res = ath10k_wmi_vdev_spectral_enable(ar, vdev_id,
+					      WMI_SPECTRAL_TRIGGER_CMD_CLEAR,
+					      WMI_SPECTRAL_ENABLE_CMD_DISABLE);
+	if (res < 0) {
+		ath10k_warn("failed to enable spectral scan: %d\n", res);
+		return res;
+	}
+
+	if (mode == SPECTRAL_DISABLED)
+		return 0;
+
+	if (mode == SPECTRAL_BACKGROUND)
+		count = WMI_SPECTRAL_COUNT_DEFAULT;
+	else
+		count = max_t(u8, 1, ar->spec_config.count);
+
+	arg.vdev_id              = vdev_id;
+	arg.scan_count           = count;
+	arg.scan_period          = WMI_SPECTRAL_PERIOD_DEFAULT;
+	arg.scan_priority        = WMI_SPECTRAL_PRIORITY_DEFAULT;
+	arg.scan_fft_size        = ar->spec_config.fft_size;
+	arg.scan_gc_ena          = WMI_SPECTRAL_GC_ENA_DEFAULT;
+	arg.scan_restart_ena     = WMI_SPECTRAL_RESTART_ENA_DEFAULT;
+	arg.scan_noise_floor_ref = WMI_SPECTRAL_NOISE_FLOOR_REF_DEFAULT;
+	arg.scan_init_delay      = WMI_SPECTRAL_INIT_DELAY_DEFAULT;
+	arg.scan_nb_tone_thr     = WMI_SPECTRAL_NB_TONE_THR_DEFAULT;
+	arg.scan_str_bin_thr     = WMI_SPECTRAL_STR_BIN_THR_DEFAULT;
+	arg.scan_wb_rpt_mode     = WMI_SPECTRAL_WB_RPT_MODE_DEFAULT;
+	arg.scan_rssi_rpt_mode   = WMI_SPECTRAL_RSSI_RPT_MODE_DEFAULT;
+	arg.scan_rssi_thr        = WMI_SPECTRAL_RSSI_THR_DEFAULT;
+	arg.scan_pwr_format      = WMI_SPECTRAL_PWR_FORMAT_DEFAULT;
+	arg.scan_rpt_mode        = WMI_SPECTRAL_RPT_MODE_DEFAULT;
+	arg.scan_bin_scale       = WMI_SPECTRAL_BIN_SCALE_DEFAULT;
+	arg.scan_dbm_adj         = WMI_SPECTRAL_DBM_ADJ_DEFAULT;
+	arg.scan_chn_mask        = WMI_SPECTRAL_CHN_MASK_DEFAULT;
+
+	res = ath10k_wmi_vdev_spectral_conf(ar, &arg);
+	if (res < 0) {
+		ath10k_warn("failed to configure spectral scan: %d\n", res);
+		return res;
+	}
+
+	return 0;
+}
+
+int ath10k_interface_disable_spectral(struct ath10k_vif *arvif)
+{
+	return ath10k_spectral_scan_config(arvif->ar, SPECTRAL_DISABLED);
+}
+
+/*********************/
+/* spectral_scan_ctl */
+/*********************/
+
+static ssize_t read_file_spec_scan_ctl(struct file *file, char __user *user_buf,
+				       size_t count, loff_t *ppos)
+{
+	struct ath10k *ar = file->private_data;
+	char *mode = "";
+	unsigned int len;
+	enum ath10k_spectral_mode spectral_mode;
+
+	mutex_lock(&ar->conf_mutex);
+	spectral_mode = ar->spectral_mode;
+	mutex_unlock(&ar->conf_mutex);
+
+	switch (spectral_mode) {
+	case SPECTRAL_DISABLED:
+		mode = "disable";
+		break;
+	case SPECTRAL_BACKGROUND:
+		mode = "background";
+		break;
+	case SPECTRAL_MANUAL:
+		mode = "manual";
+		break;
+	}
+
+	len = strlen(mode);
+	return simple_read_from_buffer(user_buf, count, ppos, mode, len);
+}
+
+static ssize_t write_file_spec_scan_ctl(struct file *file,
+					const char __user *user_buf,
+					size_t count, loff_t *ppos)
+{
+	struct ath10k *ar = file->private_data;
+	char buf[32];
+	ssize_t len;
+	int res;
+
+	len = min(count, sizeof(buf) - 1);
+	if (copy_from_user(buf, user_buf, len))
+		return -EFAULT;
+
+	buf[len] = '\0';
+
+	mutex_lock(&ar->conf_mutex);
+
+	if (strncmp("trigger", buf, 7) == 0) {
+		if (ar->spectral_mode == SPECTRAL_MANUAL ||
+		    ar->spectral_mode == SPECTRAL_BACKGROUND) {
+			/* reset the configuration to adopt possibly changed
+			 * debugfs parameters
+			 */
+			res = ath10k_spectral_scan_config(ar,
+							  ar->spectral_mode);
+			if (res < 0) {
+				ath10k_warn("failed to reconfigure spectral scan: %d\n",
+					    res);
+			}
+			res = ath10k_spectral_scan_trigger(ar);
+			if (res < 0) {
+				ath10k_warn("failed to trigger spectral scan: %d\n",
+					    res);
+			}
+		} else {
+			res = -EINVAL;
+		}
+	} else if (strncmp("background", buf, 9) == 0) {
+		res = ath10k_spectral_scan_config(ar, SPECTRAL_BACKGROUND);
+	} else if (strncmp("manual", buf, 6) == 0) {
+		res = ath10k_spectral_scan_config(ar, SPECTRAL_MANUAL);
+	} else if (strncmp("disable", buf, 7) == 0) {
+		res = ath10k_spectral_scan_config(ar, SPECTRAL_DISABLED);
+	} else {
+		res = -EINVAL;
+	}
+
+	mutex_unlock(&ar->conf_mutex);
+
+	if (res < 0)
+		return res;
+
+	return count;
+}
+
+static const struct file_operations fops_spec_scan_ctl = {
+	.read = read_file_spec_scan_ctl,
+	.write = write_file_spec_scan_ctl,
+	.open = simple_open,
+	.owner = THIS_MODULE,
+	.llseek = default_llseek,
+};
+
+/******************/
+/* spectral_count */
+/******************/
+
+static ssize_t read_file_spectral_count(struct file *file,
+					char __user *user_buf,
+					size_t count, loff_t *ppos)
+{
+	struct ath10k *ar = file->private_data;
+	char buf[32];
+	unsigned int len;
+	u8 spectral_count;
+
+	mutex_lock(&ar->conf_mutex);
+	spectral_count = ar->spec_config.count;
+	mutex_unlock(&ar->conf_mutex);
+
+	len = sprintf(buf, "%d\n", spectral_count);
+	return simple_read_from_buffer(user_buf, count, ppos, buf, len);
+}
+
+static ssize_t write_file_spectral_count(struct file *file,
+					 const char __user *user_buf,
+					 size_t count, loff_t *ppos)
+{
+	struct ath10k *ar = file->private_data;
+	unsigned long val;
+	char buf[32];
+	ssize_t len;
+
+	len = min(count, sizeof(buf) - 1);
+	if (copy_from_user(buf, user_buf, len))
+		return -EFAULT;
+
+	buf[len] = '\0';
+	if (kstrtoul(buf, 0, &val))
+		return -EINVAL;
+
+	if (val < 0 || val > 255)
+		return -EINVAL;
+
+	mutex_lock(&ar->conf_mutex);
+	ar->spec_config.count = val;
+	mutex_unlock(&ar->conf_mutex);
+
+	return count;
+}
+
+static const struct file_operations fops_spectral_count = {
+	.read = read_file_spectral_count,
+	.write = write_file_spectral_count,
+	.open = simple_open,
+	.owner = THIS_MODULE,
+	.llseek = default_llseek,
+};
+
+/*****************/
+/* spectral_bins */
+/*****************/
+
+static ssize_t read_file_spectral_bins(struct file *file,
+				       char __user *user_buf,
+				       size_t count, loff_t *ppos)
+{
+	struct ath10k *ar = file->private_data;
+	char buf[32];
+	unsigned int len;
+	unsigned int bins;
+
+	mutex_lock(&ar->conf_mutex);
+	bins = 1 << (ar->spec_config.fft_size - WMI_SPECTRAL_BIN_SCALE_DEFAULT);
+	mutex_unlock(&ar->conf_mutex);
+
+	len = sprintf(buf, "%d\n", bins);
+	return simple_read_from_buffer(user_buf, count, ppos, buf, len);
+}
+
+static ssize_t write_file_spectral_bins(struct file *file,
+					const char __user *user_buf,
+					size_t count, loff_t *ppos)
+{
+	struct ath10k *ar = file->private_data;
+	unsigned long val;
+	char buf[32];
+	ssize_t len;
+
+	len = min(count, sizeof(buf) - 1);
+	if (copy_from_user(buf, user_buf, len))
+		return -EFAULT;
+
+	buf[len] = '\0';
+	if (kstrtoul(buf, 0, &val))
+		return -EINVAL;
+
+	if (val < 64 || val > SPECTRAL_ATH10K_MAX_NUM_BINS)
+		return -EINVAL;
+
+	if (!is_power_of_2(val))
+		return -EINVAL;
+
+	mutex_lock(&ar->conf_mutex);
+	ar->spec_config.fft_size = ilog2(val);
+	ar->spec_config.fft_size += WMI_SPECTRAL_BIN_SCALE_DEFAULT;
+	mutex_unlock(&ar->conf_mutex);
+
+	return count;
+}
+
+static const struct file_operations fops_spectral_bins = {
+	.read = read_file_spectral_bins,
+	.write = write_file_spectral_bins,
+	.open = simple_open,
+	.owner = THIS_MODULE,
+	.llseek = default_llseek,
+};
+
+/*******************/
+/* Relay interface */
+/*******************/
+
+static struct dentry *create_buf_file_handler(const char *filename,
+					      struct dentry *parent,
+					      umode_t mode,
+					      struct rchan_buf *buf,
+					      int *is_global)
+{
+	struct dentry *buf_file;
+
+	buf_file = debugfs_create_file(filename, mode, parent, buf,
+				       &relay_file_operations);
+	*is_global = 1;
+	return buf_file;
+}
+
+static int remove_buf_file_handler(struct dentry *dentry)
+{
+	debugfs_remove(dentry);
+
+	return 0;
+}
+
+static struct rchan_callbacks rfs_spec_scan_cb = {
+	.create_buf_file = create_buf_file_handler,
+	.remove_buf_file = remove_buf_file_handler,
+};
+
+/*********************/
+/* Debug Init/Deinit */
+/*********************/
+
+void ath10k_spectral_deinit_debug(struct ath10k *ar)
+{
+	if (config_enabled(CONFIG_ATH10K_DEBUGFS) && ar->rfs_chan_spec_scan) {
+		relay_close(ar->rfs_chan_spec_scan);
+		ar->rfs_chan_spec_scan = NULL;
+	}
+}
+
+void ath10k_spectral_init_debug(struct ath10k *ar)
+{
+#ifdef CONFIG_ATH10K_DEBUGFS
+	ar->rfs_chan_spec_scan = relay_open("spectral_scan",
+					    ar->debug.debugfs_phy,
+					    1024, 256, &rfs_spec_scan_cb,
+					    NULL);
+	debugfs_create_file("spectral_scan_ctl",
+			    S_IRUSR | S_IWUSR,
+			    ar->debug.debugfs_phy, ar,
+			    &fops_spec_scan_ctl);
+	debugfs_create_file("spectral_count",
+			    S_IRUSR | S_IWUSR,
+			    ar->debug.debugfs_phy, ar,
+			    &fops_spectral_count);
+	debugfs_create_file("spectral_bins",
+			    S_IRUSR | S_IWUSR,
+			    ar->debug.debugfs_phy, ar,
+			    &fops_spectral_bins);
+#endif
+}
diff --git a/drivers/net/wireless/ath/ath10k/spectral.h b/drivers/net/wireless/ath/ath10k/spectral.h
new file mode 100644
index 0000000..40c0718
--- /dev/null
+++ b/drivers/net/wireless/ath/ath10k/spectral.h
@@ -0,0 +1,64 @@ 
+/*
+ * Copyright (c) 2013 Qualcomm Atheros, Inc.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef SPECTRAL_H
+#define SPECTRAL_H
+
+#include "../spectral_common.h"
+
+/**
+ * struct ath10k_spec_scan - parameters for Atheros spectral scan
+ *
+ * @count: number of scan results requested for manual mode
+ */
+struct ath10k_spec_scan {
+	u8 count;
+	u8 fft_size;
+};
+
+/* enum ath10k_spectral_mode:
+ *
+ * @SPECTRAL_DISABLED: spectral mode is disabled
+ * @SPECTRAL_BACKGROUND: hardware sends samples when it is not busy with
+ *	something else.
+ * @SPECTRAL_MANUAL: spectral scan is enabled, triggering for samples
+ *	is performed manually.
+ */
+enum ath10k_spectral_mode {
+	SPECTRAL_DISABLED = 0,
+	SPECTRAL_BACKGROUND,
+	SPECTRAL_MANUAL,
+};
+
+void ath10k_spectral_init_debug(struct ath10k *ar);
+void ath10k_spectral_deinit_debug(struct ath10k *ar);
+int ath10k_interface_disable_spectral(struct ath10k_vif *arvif);
+
+#ifdef CONFIG_ATH10K_DEBUGFS
+int ath10k_process_fft(struct ath10k *ar,
+		       struct wmi_single_phyerr_rx_event *event,
+		       struct phyerr_fft_report *fftr, size_t bin_len, u64 tsf);
+#else
+static inline int
+ath10k_process_fft(struct ath10k *ar,
+		   struct wmi_single_phyerr_rx_event *event,
+		   struct phyerr_fft_report *fftr, size_t bin_len, u64 tsf)
+{
+	return 0;
+}
+#endif /* CONFIG_ATH10K_DEBUGFS */
+
+#endif /* SPECTRAL_H */
diff --git a/drivers/net/wireless/ath/ath10k/wmi.c b/drivers/net/wireless/ath/ath10k/wmi.c
index beac30d..1422296 100644
--- a/drivers/net/wireless/ath/ath10k/wmi.c
+++ b/drivers/net/wireless/ath/ath10k/wmi.c
@@ -1649,7 +1649,53 @@  static void ath10k_wmi_event_spectral_scan(struct ath10k *ar,
 				struct wmi_single_phyerr_rx_event *event,
 				u64 tsf)
 {
-	ath10k_dbg(ATH10K_DBG_WMI, "wmi event spectral scan\n");
+	int buf_len, tlv_len, res, i = 0;
+	struct phyerr_tlv *tlv;
+	u8 *tlv_buf;
+	struct phyerr_fft_report *fftr;
+	size_t fftr_len;
+
+	buf_len = __le32_to_cpu(event->hdr.buf_len);
+
+	while (i < buf_len) {
+		if (i + sizeof(*tlv) > buf_len) {
+			ath10k_warn("failed to parse phyerr tlv header at byte %d\n",
+				    i);
+			return;
+		}
+
+		tlv = (struct phyerr_tlv *)&event->bufp[i];
+		tlv_len = __le16_to_cpu(tlv->len);
+		tlv_buf = &event->bufp[i + sizeof(*tlv)];
+
+		if (i + sizeof(*tlv) + tlv_len > buf_len) {
+			ath10k_warn("failed to parse phyerr tlv payload at byte %d\n",
+				    i);
+			return;
+		}
+
+		switch (tlv->tag) {
+		case PHYERR_TLV_TAG_SEARCH_FFT_REPORT:
+			if (sizeof(*fftr) > tlv_len) {
+				ath10k_warn("failed to parse fft report at byte %d\n",
+					    i);
+				return;
+			}
+
+			fftr_len = tlv_len - sizeof(*fftr);
+			fftr = (struct phyerr_fft_report *)tlv_buf;
+			res = ath10k_process_fft(ar, event, fftr, fftr_len,
+						 tsf);
+			if (res < 0) {
+				ath10k_warn("failed to process fft report: %d\n",
+					    res);
+				return;
+			}
+			break;
+		}
+
+		i += sizeof(*tlv) + tlv_len;
+	}
 }
 
 static void ath10k_wmi_event_phyerr(struct ath10k *ar, struct sk_buff *skb)
@@ -3191,6 +3237,62 @@  int ath10k_wmi_vdev_install_key(struct ath10k *ar,
 				   ar->wmi.cmd->vdev_install_key_cmdid);
 }
 
+int ath10k_wmi_vdev_spectral_conf(struct ath10k *ar,
+				  const struct wmi_vdev_spectral_conf_arg *arg)
+{
+	struct wmi_vdev_spectral_conf_cmd *cmd;
+	struct sk_buff *skb;
+	u32 cmdid;
+
+	skb = ath10k_wmi_alloc_skb(sizeof(*cmd));
+	if (!skb)
+		return -ENOMEM;
+
+	cmd = (struct wmi_vdev_spectral_conf_cmd *)skb->data;
+	cmd->vdev_id              = __cpu_to_le32(arg->vdev_id);
+	cmd->scan_count           = __cpu_to_le32(arg->scan_count);
+	cmd->scan_period          = __cpu_to_le32(arg->scan_period);
+	cmd->scan_priority        = __cpu_to_le32(arg->scan_priority);
+	cmd->scan_fft_size        = __cpu_to_le32(arg->scan_fft_size);
+	cmd->scan_gc_ena          = __cpu_to_le32(arg->scan_gc_ena);
+	cmd->scan_restart_ena     = __cpu_to_le32(arg->scan_restart_ena);
+	cmd->scan_noise_floor_ref = __cpu_to_le32(arg->scan_noise_floor_ref);
+	cmd->scan_init_delay      = __cpu_to_le32(arg->scan_init_delay);
+	cmd->scan_nb_tone_thr     = __cpu_to_le32(arg->scan_nb_tone_thr);
+	cmd->scan_str_bin_thr     = __cpu_to_le32(arg->scan_str_bin_thr);
+	cmd->scan_wb_rpt_mode     = __cpu_to_le32(arg->scan_wb_rpt_mode);
+	cmd->scan_rssi_rpt_mode   = __cpu_to_le32(arg->scan_rssi_rpt_mode);
+	cmd->scan_rssi_thr        = __cpu_to_le32(arg->scan_rssi_thr);
+	cmd->scan_pwr_format      = __cpu_to_le32(arg->scan_pwr_format);
+	cmd->scan_rpt_mode        = __cpu_to_le32(arg->scan_rpt_mode);
+	cmd->scan_bin_scale       = __cpu_to_le32(arg->scan_bin_scale);
+	cmd->scan_dbm_adj         = __cpu_to_le32(arg->scan_dbm_adj);
+	cmd->scan_chn_mask        = __cpu_to_le32(arg->scan_chn_mask);
+
+	cmdid = ar->wmi.cmd->vdev_spectral_scan_configure_cmdid;
+	return ath10k_wmi_cmd_send(ar, skb, cmdid);
+}
+
+int ath10k_wmi_vdev_spectral_enable(struct ath10k *ar, u32 vdev_id, u32 trigger,
+				    u32 enable)
+{
+	struct wmi_vdev_spectral_enable_cmd *cmd;
+	struct sk_buff *skb;
+	u32 cmdid;
+
+	skb = ath10k_wmi_alloc_skb(sizeof(*cmd));
+	if (!skb)
+		return -ENOMEM;
+
+	cmd = (struct wmi_vdev_spectral_enable_cmd *)skb->data;
+	cmd->vdev_id     = __cpu_to_le32(vdev_id);
+	cmd->trigger_cmd = __cpu_to_le32(trigger);
+	cmd->enable_cmd  = __cpu_to_le32(enable);
+
+	cmdid = ar->wmi.cmd->vdev_spectral_scan_enable_cmdid;
+	return ath10k_wmi_cmd_send(ar, skb, cmdid);
+}
+
 int ath10k_wmi_peer_create(struct ath10k *ar, u32 vdev_id,
 			   const u8 peer_addr[ETH_ALEN])
 {
diff --git a/drivers/net/wireless/ath/ath10k/wmi.h b/drivers/net/wireless/ath/ath10k/wmi.h
index 4fcc96a..da5cbda 100644
--- a/drivers/net/wireless/ath/ath10k/wmi.h
+++ b/drivers/net/wireless/ath/ath10k/wmi.h
@@ -2077,6 +2077,7 @@  struct wmi_comb_phyerr_rx_event {
 #define PHYERR_TLV_SIG				0xBB
 #define PHYERR_TLV_TAG_SEARCH_FFT_REPORT	0xFB
 #define PHYERR_TLV_TAG_RADAR_PULSE_SUMMARY	0xF8
+#define PHYERR_TLV_TAG_SPECTRAL_SUMMARY_REPORT	0xF9
 
 struct phyerr_radar_report {
 	__le32 reg0; /* RADAR_REPORT_REG0_* */
@@ -3381,6 +3382,95 @@  struct wmi_vdev_simple_event {
 /* unsupported VDEV combination */
 #define WMI_INIFIED_VDEV_START_RESPONSE_NOT_SUPPORTED	0x2
 
+/* TODO: please add more comments if you have in-depth information */
+struct wmi_vdev_spectral_conf_cmd {
+	__le32 vdev_id;
+	/* number of fft samples to send (0 for infinite) */
+	__le32 scan_count;
+	__le32 scan_period;
+	__le32 scan_priority;
+	/* number of bins in the FFT: 2^(fft_size - bin_scale) */
+	__le32 scan_fft_size;
+	__le32 scan_gc_ena;
+	__le32 scan_restart_ena;
+	__le32 scan_noise_floor_ref;
+	__le32 scan_init_delay;
+	__le32 scan_nb_tone_thr;
+	__le32 scan_str_bin_thr;
+	__le32 scan_wb_rpt_mode;
+	__le32 scan_rssi_rpt_mode;
+	__le32 scan_rssi_thr;
+	__le32 scan_pwr_format;
+	/* rpt_mode: Format of FFT report to software for spectral scan
+	 * triggered FFTs:
+	 *	0: No FFT report (only spectral scan summary report)
+	 *	1: 2-dword summary of metrics for each completed FFT + spectral
+	 *	   scan	summary report
+	 *	2: 2-dword summary of metrics for each completed FFT +
+	 *	   1x- oversampled bins(in-band) per FFT + spectral scan summary
+	 *	   report
+	 *	3: 2-dword summary of metrics for each completed FFT +
+	 *	   2x- oversampled bins	(all) per FFT + spectral scan summary
+	 */
+	__le32 scan_rpt_mode;
+	__le32 scan_bin_scale;
+	__le32 scan_dbm_adj;
+	__le32 scan_chn_mask;
+} __packed;
+
+struct wmi_vdev_spectral_conf_arg {
+	u32 vdev_id;
+	u32 scan_count;
+	u32 scan_period;
+	u32 scan_priority;
+	u32 scan_fft_size;
+	u32 scan_gc_ena;
+	u32 scan_restart_ena;
+	u32 scan_noise_floor_ref;
+	u32 scan_init_delay;
+	u32 scan_nb_tone_thr;
+	u32 scan_str_bin_thr;
+	u32 scan_wb_rpt_mode;
+	u32 scan_rssi_rpt_mode;
+	u32 scan_rssi_thr;
+	u32 scan_pwr_format;
+	u32 scan_rpt_mode;
+	u32 scan_bin_scale;
+	u32 scan_dbm_adj;
+	u32 scan_chn_mask;
+};
+
+#define WMI_SPECTRAL_ENABLE_DEFAULT              0
+#define WMI_SPECTRAL_COUNT_DEFAULT               0
+#define WMI_SPECTRAL_PERIOD_DEFAULT             35
+#define WMI_SPECTRAL_PRIORITY_DEFAULT            1
+#define WMI_SPECTRAL_FFT_SIZE_DEFAULT            7
+#define WMI_SPECTRAL_GC_ENA_DEFAULT              1
+#define WMI_SPECTRAL_RESTART_ENA_DEFAULT         0
+#define WMI_SPECTRAL_NOISE_FLOOR_REF_DEFAULT   -96
+#define WMI_SPECTRAL_INIT_DELAY_DEFAULT         80
+#define WMI_SPECTRAL_NB_TONE_THR_DEFAULT        12
+#define WMI_SPECTRAL_STR_BIN_THR_DEFAULT         8
+#define WMI_SPECTRAL_WB_RPT_MODE_DEFAULT         0
+#define WMI_SPECTRAL_RSSI_RPT_MODE_DEFAULT       0
+#define WMI_SPECTRAL_RSSI_THR_DEFAULT         0xf0
+#define WMI_SPECTRAL_PWR_FORMAT_DEFAULT          0
+#define WMI_SPECTRAL_RPT_MODE_DEFAULT            2
+#define WMI_SPECTRAL_BIN_SCALE_DEFAULT           1
+#define WMI_SPECTRAL_DBM_ADJ_DEFAULT             1
+#define WMI_SPECTRAL_CHN_MASK_DEFAULT            1
+
+struct wmi_vdev_spectral_enable_cmd {
+	__le32 vdev_id;
+	__le32 trigger_cmd;
+	__le32 enable_cmd;
+} __packed;
+
+#define WMI_SPECTRAL_TRIGGER_CMD_TRIGGER  1
+#define WMI_SPECTRAL_TRIGGER_CMD_CLEAR    2
+#define WMI_SPECTRAL_ENABLE_CMD_ENABLE    1
+#define WMI_SPECTRAL_ENABLE_CMD_DISABLE   2
+
 /* Beacon processing related command and event structures */
 struct wmi_bcn_tx_hdr {
 	__le32 vdev_id;
@@ -4226,6 +4316,10 @@  int ath10k_wmi_vdev_set_param(struct ath10k *ar, u32 vdev_id,
 			      u32 param_id, u32 param_value);
 int ath10k_wmi_vdev_install_key(struct ath10k *ar,
 				const struct wmi_vdev_install_key_arg *arg);
+int ath10k_wmi_vdev_spectral_conf(struct ath10k *ar,
+				  const struct wmi_vdev_spectral_conf_arg *arg);
+int ath10k_wmi_vdev_spectral_enable(struct ath10k *ar, u32 vdev_id, u32 trigger,
+				    u32 enable);
 int ath10k_wmi_peer_create(struct ath10k *ar, u32 vdev_id,
 		    const u8 peer_addr[ETH_ALEN]);
 int ath10k_wmi_peer_delete(struct ath10k *ar, u32 vdev_id,
diff --git a/drivers/net/wireless/ath/spectral_common.h b/drivers/net/wireless/ath/spectral_common.h
index b9ab722..0827cb7 100644
--- a/drivers/net/wireless/ath/spectral_common.h
+++ b/drivers/net/wireless/ath/spectral_common.h
@@ -19,6 +19,10 @@ 
 
 #define SPECTRAL_HT20_NUM_BINS		56
 #define SPECTRAL_HT20_40_NUM_BINS		128
+/* TODO: could possibly be 512, but no samples this large
+ * could be acquired so far.
+ */
+#define SPECTRAL_ATH10K_MAX_NUM_BINS		256
 
 /* FFT sample format given to userspace via debugfs.
  *
@@ -31,6 +35,7 @@ 
 enum ath_fft_sample_type {
 	ATH_FFT_SAMPLE_HT20 = 1,
 	ATH_FFT_SAMPLE_HT20_40,
+	ATH_FFT_SAMPLE_ATH10K,
 };
 
 struct fft_sample_tlv {
@@ -85,4 +90,23 @@  struct fft_sample_ht20_40 {
 	u8 data[SPECTRAL_HT20_40_NUM_BINS];
 } __packed;
 
+struct fft_sample_ath10k {
+	struct fft_sample_tlv tlv;
+	u8 chan_width_mhz;
+	__be16 freq1;
+	__be16 freq2;
+	__be16 noise;
+	__be16 max_magnitude;
+	__be16 total_gain_db;
+	__be16 base_pwr_db;
+	__be64 tsf;
+	s8 max_index;
+	u8 rssi;
+	u8 relpwr_db;
+	u8 avgpwr_db;
+	u8 max_exp;
+
+	u8 data[0];
+} __packed;
+
 #endif /* SPECTRAL_COMMON_H */