Add ACS to cfg80211 operations that the device handles and performs.
wil6210 reports NL80211_EXT_FEATURE_ACS_OFFLOAD indicating a support
for acs operation through cfg80211_ops.

ACS is performed by the driver and the FW. Once ACS operation is
called the driver builds a WMI command with the requested channels
and sends the command to the FW. The FW performs ACS scan on the
channels and reports for each channel the following:
1- Number of beacon received on channel
2- channel busy time
3- transmit time
4- receive time
5- noise level
The driver uses the above information to select the best channel
to start the AP, and reports it to the host.

User may use debugfs acs_ch_weight to set a different weights
for the channels.

Change-Id: I8e13296fad0c9fa8b15788fd37c23d825d41d8e5
Signed-off-by: Ahmad Masri <[email protected]>
---
 drivers/net/wireless/ath/wil6210/cfg80211.c | 140 ++++++++++++++++++++++++-
 drivers/net/wireless/ath/wil6210/debugfs.c  | 154 ++++++++++++++++++++++++++++
 drivers/net/wireless/ath/wil6210/main.c     |   5 +
 drivers/net/wireless/ath/wil6210/wil6210.h  |  15 +++
 drivers/net/wireless/ath/wil6210/wmi.c      |  76 ++++++++++++++
 drivers/net/wireless/ath/wil6210/wmi.h      |   3 +-
 6 files changed, 390 insertions(+), 3 deletions(-)

diff --git a/drivers/net/wireless/ath/wil6210/cfg80211.c 
b/drivers/net/wireless/ath/wil6210/cfg80211.c
index 9b2f9f5..62abfb5 100644
--- a/drivers/net/wireless/ath/wil6210/cfg80211.c
+++ b/drivers/net/wireless/ath/wil6210/cfg80211.c
@@ -35,6 +35,14 @@
 };
 #endif
 
+/* in case of channels' noise values all zero, applying weights will not work.
+ * to avoid such a case, we will add some small positive value to
+ * all channels' noise calculation
+ */
+#define ACS_CH_NOISE_INIT_VAL (100)
+
+#define ACS_DEFAULT_BEST_CHANNEL 2
+
 #define CHAN60G(_channel, _flags) {                            \
        .band                   = NL80211_BAND_60GHZ,           \
        .center_freq            = 56160 + (2160 * (_channel)),  \
@@ -44,7 +52,7 @@
        .max_power              = 40,                           \
 }
 
-static struct ieee80211_channel wil_60ghz_channels[] = {
+static struct ieee80211_channel wil_60ghz_channels[WIL_MAX_CHANNELS] = {
        CHAN60G(1, 0),
        CHAN60G(2, 0),
        CHAN60G(3, 0),
@@ -64,7 +72,7 @@
        }
 }
 
-static int wil_num_supported_channels(struct wil6210_priv *wil)
+int wil_num_supported_channels(struct wil6210_priv *wil)
 {
        int num_channels = ARRAY_SIZE(wil_60ghz_channels);
 
@@ -2358,6 +2366,132 @@ static int wil_cfg80211_resume(struct wiphy *wiphy)
        return rc;
 }
 
+static u8 wil_acs_calc_channel(struct wil6210_priv *wil)
+{
+       int i, best_channel = ACS_DEFAULT_BEST_CHANNEL - 1;
+       struct scan_acs_info *ch;
+       u64 dwell_time = le32_to_cpu(wil->survey_reply.evt.dwell_time);
+       u16 filled = le16_to_cpu(wil->survey_reply.evt.filled);
+       u8 num_channels = wil->survey_reply.evt.num_scanned_channels;
+       u64 busy_time, tx_time;
+       u64 min_i_ch = (u64)-1, cur_i_ch;
+       u8 p_min = 0, ch_noise;
+
+       wil_dbg_misc(wil,
+                    "acs_calc_channel: filled info: 0x%04X, for %u channels\n",
+                    filled, num_channels);
+
+       if (!num_channels) {
+               wil_err(wil, "received results with no channel info\n");
+               return 0;
+       }
+
+       /* find P_min */
+       if (filled & WMI_ACS_INFO_BITMASK_NOISE) {
+               p_min = wil->survey_reply.ch_info[0].noise;
+
+               for (i = 1; i < num_channels; i++)
+                       p_min = min(p_min, wil->survey_reply.ch_info[i].noise);
+       }
+
+       wil_dbg_misc(wil, "acs_calc_channel: p_min is %u\n", p_min);
+
+       /* Choosing channel according to the following formula:
+        * 16 bit fixed point math
+        * I_ch = { [ (T_busy - T_tx) << 16 ] /
+        *        (T_dwell - T_tx) } * 2^(P_rx - P_min)
+        */
+       for (i = 0; i < num_channels; i++) {
+               ch = &wil->survey_reply.ch_info[i];
+
+               busy_time = filled & WMI_ACS_INFO_BITMASK_BUSY_TIME ?
+                               le16_to_cpu(ch->busy_time) : 0;
+
+               tx_time = filled & WMI_ACS_INFO_BITMASK_TX_TIME ?
+                               le16_to_cpu(ch->tx_time) : 0;
+
+               ch_noise = filled & WMI_ACS_INFO_BITMASK_NOISE ? ch->noise : 0;
+
+               wil_dbg_misc(wil,
+                            "acs_calc_channel: Ch[%d]: busy %llu, tx %llu, 
noise %u, dwell %llu\n",
+                            ch->channel + 1, busy_time, tx_time, ch_noise,
+                            dwell_time);
+
+               if (dwell_time == tx_time) {
+                       wil_err(wil,
+                               "Ch[%d] dwell_time == tx_time: %llu\n",
+                               ch->channel + 1, dwell_time);
+                       continue;
+               }
+
+               cur_i_ch = (busy_time - tx_time) << 16;
+               do_div(cur_i_ch,
+                      ((dwell_time - tx_time) << (ch_noise - p_min)));
+
+               /* Apply channel priority */
+               cur_i_ch = (cur_i_ch + ACS_CH_NOISE_INIT_VAL) *
+                          wil->acs_ch_weight[ch->channel];
+               do_div(cur_i_ch, 100);
+
+               wil_dbg_misc(wil, "acs_calc_channel: Ch[%d] w %u, I_ch %llu\n",
+                            ch->channel + 1, wil->acs_ch_weight[ch->channel],
+                            cur_i_ch);
+
+               if (i == 0 || cur_i_ch < min_i_ch) {
+                       min_i_ch = cur_i_ch;
+                       best_channel = ch->channel;
+               }
+       }
+
+       wil_dbg_misc(wil,
+                    "acs_calc_channel: best channel %d with I_ch of %llu\n",
+                    best_channel + 1, min_i_ch);
+
+       return best_channel;
+}
+
+static void wil_acs_notify(struct wiphy *wiphy, struct net_device *dev,
+                          u32 status)
+{
+       struct wil6210_priv *wil = wiphy_to_wil(wiphy);
+       u8 ch = wil_acs_calc_channel(wil);
+       u32 freq = ieee80211_channel_to_frequency(ch + 1, NL80211_BAND_60GHZ);
+       struct ieee80211_channel *channel = ieee80211_get_channel(wiphy, freq);
+       struct cfg80211_chan_def chandef = {0};
+
+       if (channel) {
+               cfg80211_chandef_create(&chandef, channel, NL80211_CHAN_NO_HT);
+       } else {
+               wil_err(wil, "Invalid freq %d\n", freq);
+               status = NL80211_ACS_FAILED;
+       }
+
+       cfg80211_acs_result(dev, &chandef, status, GFP_KERNEL);
+}
+
+static int
+wil_cfg80211_acs(struct wiphy *wiphy, struct net_device *dev,
+                struct cfg80211_acs_params *params)
+{
+       struct wil6210_priv *wil = wiphy_to_wil(wiphy);
+       enum nl80211_acs_status status = NL80211_ACS_SUCCESS;
+       int rc;
+
+       if (params->n_channels == 0) {
+               wil_err(wil, "acs: No valid channels for ACS\n");
+               return -EINVAL;
+       }
+
+       rc = wil_start_acs_survey(wil, WMI_SCAN_DWELL_TIME_MS,
+                                 params->channels, params->n_channels);
+       if (rc)
+               status = NL80211_ACS_FAILED;
+
+       wil_acs_notify(wiphy, dev, status);
+
+       return 0;
+}
+
 static const struct cfg80211_ops wil_cfg80211_ops = {
        .add_virtual_intf = wil_cfg80211_add_iface,
        .del_virtual_intf = wil_cfg80211_del_iface,
@@ -2394,6 +2528,7 @@ static int wil_cfg80211_resume(struct wiphy *wiphy)
        .sched_scan_start = wil_cfg80211_sched_scan_start,
        .sched_scan_stop = wil_cfg80211_sched_scan_stop,
        .update_ft_ies = wil_cfg80211_update_ft_ies,
+       .acs = wil_cfg80211_acs,
 };
 
 static void wil_wiphy_init(struct wiphy *wiphy)
@@ -2436,6 +2571,7 @@ static void wil_wiphy_init(struct wiphy *wiphy)
 #ifdef CONFIG_PM
        wiphy->wowlan = &wil_wowlan_support;
 #endif
+       wiphy_ext_feature_set(wiphy, NL80211_EXT_FEATURE_ACS_OFFLOAD);
 }
 
 int wil_cfg80211_iface_combinations_from_fw(
diff --git a/drivers/net/wireless/ath/wil6210/debugfs.c 
b/drivers/net/wireless/ath/wil6210/debugfs.c
index 20dd4d0..a376229 100644
--- a/drivers/net/wireless/ath/wil6210/debugfs.c
+++ b/drivers/net/wireless/ath/wil6210/debugfs.c
@@ -26,6 +26,8 @@
 #include "txrx.h"
 #include "pmc.h"
 
+#define WIL_DEBUGFS_BUF_SIZE 400
+
 /* Nasty hack. Better have per device instances */
 static u32 mem_addr;
 static u32 dbg_txdesc_index;
@@ -2250,6 +2252,98 @@ static ssize_t wil_read_led_blink_time(struct file 
*file, char __user *user_buf,
        .open  = simple_open,
 };
 
+/*---------ACS channel weight------------*/
+static ssize_t wil_write_acs_ch_weight(struct file *file,
+                                      const char __user *buf,
+                                      size_t len, loff_t *ppos)
+{
+       struct wil6210_priv *wil = file->private_data;
+       int i, rc;
+       char *token, *dupbuf, *kbuf = kmalloc(len + 1, GFP_KERNEL);
+       unsigned short channel_weights[WIL_MAX_CHANNELS];
+
+       if (!kbuf)
+               return -ENOMEM;
+
+       rc = simple_write_to_buffer(kbuf, len, ppos, buf, len);
+       if (rc != len) {
+               kfree(kbuf);
+               return rc >= 0 ? -EIO : rc;
+       }
+
+       kbuf[len] = '\0';
+       dupbuf = kbuf;
+
+       /* Format for writing is num of channels unsigned short separated
+        * by spaces:
+        * <ch 1 weight> ... <channel max weight>
+        */
+
+       /* set the channels weights */
+       for (i = 0; i < WIL_MAX_CHANNELS; ++i) {
+               token = strsep(&dupbuf, " ");
+               if (!token)
+                       goto out;
+               if (kstrtou16(token, 0, &channel_weights[i]))
+                       goto out;
+       }
+       memcpy(wil->acs_ch_weight, channel_weights, sizeof(wil->acs_ch_weight));
+
+out:
+       kfree(kbuf);
+       if (i != WIL_MAX_CHANNELS)
+               return -EINVAL;
+
+       return len;
+}
+
+static ssize_t wil_read_acs_ch_weight(struct file *file, char __user *user_buf,
+                                     size_t count, loff_t *ppos)
+{
+       struct wil6210_priv *wil = file->private_data;
+       char *buf;
+       size_t buf_size = WIL_DEBUGFS_BUF_SIZE;
+       int i, bytes_used, offset, rc = -EINVAL;
+
+       buf = kmalloc(buf_size, GFP_KERNEL);
+       if (!buf)
+               return -ENOMEM;
+
+       bytes_used = snprintf(buf, buf_size,
+                             "To set acs channel weights write:\n"
+                             "<ch 1 weight> <ch 2 weight> ... <ch max 
weight>\n"
+                             "The current values are:\n");
+
+       if (bytes_used < 0 || bytes_used >= buf_size)
+               goto out;
+
+       buf_size -= bytes_used;
+       offset = bytes_used;
+
+       for (i = 0; i < WIL_MAX_CHANNELS; ++i) {
+               bytes_used = snprintf(buf + offset, buf_size, "%hu ",
+                                     wil->acs_ch_weight[i]);
+               if (bytes_used < 0 || bytes_used >= buf_size)
+                       goto out;
+
+               buf_size -= bytes_used;
+               offset += bytes_used;
+       }
+       strncat(buf, "\n", WIL_DEBUGFS_BUF_SIZE);
+       rc = simple_read_from_buffer(user_buf, count, ppos, buf, offset);
+
+out:
+       kfree(buf);
+
+       return rc;
+}
+
+static const struct file_operations fops_acs_ch_weight = {
+       .read = wil_read_acs_ch_weight,
+       .write = wil_write_acs_ch_weight,
+       .open  = simple_open,
+};
+
 /*---------FW capabilities------------*/
 static int wil_fw_capabilities_debugfs_show(struct seq_file *s, void *data)
 {
@@ -2414,6 +2508,65 @@ static ssize_t wil_compressed_rx_status_write(struct 
file *file,
        .llseek = seq_lseek,
 };
 
+/*---------Survey results------------*/
+static int wil_survey_debugfs_show(struct seq_file *s, void *data)
+{
+       struct wil6210_priv *wil = s->private;
+       int i, n_ch;
+       u16 filled;
+
+       if (!wil->survey_ready) {
+               seq_puts(s, "Survey not ready\n");
+               return 0;
+       }
+       seq_printf(s, "dwell_time : %d\n",
+                  le32_to_cpu(wil->survey_reply.evt.dwell_time));
+       filled = le16_to_cpu(wil->survey_reply.evt.filled);
+       n_ch = min_t(int, wil->survey_reply.evt.num_scanned_channels,
+                    ARRAY_SIZE(wil->survey_reply.ch_info));
+
+#define ACS_FILLED(x) (filled & WMI_ACS_INFO_BITMASK_ ## x) ? \
+       " " __stringify(x) : ""
+       seq_printf(s, "Filled : 0x%04x%s%s%s%s%s\n", filled,
+                  ACS_FILLED(BEACON_FOUND),
+                  ACS_FILLED(BUSY_TIME),
+                  ACS_FILLED(TX_TIME),
+                  ACS_FILLED(RX_TIME),
+                  ACS_FILLED(NOISE)
+                 );
+#undef ACS_FILLED
+       seq_printf(s, "Channels [%d] {\n", n_ch);
+       for (i = 0; i < n_ch; i++) {
+               struct scan_acs_info *ch = &wil->survey_reply.ch_info[i];
+
+               seq_printf(s, "  [%d]", ch->channel);
+#define ACS_PRINT(x, str, field) do { if (filled & WMI_ACS_INFO_BITMASK_ ## x) 
\
+               seq_printf(s, " %s : %d", str, field); \
+       } while (0)
+               ACS_PRINT(BEACON_FOUND, "bcon", ch->beacon_found);
+               ACS_PRINT(BUSY_TIME, "busy", le16_to_cpu(ch->busy_time));
+               ACS_PRINT(TX_TIME, "tx", le16_to_cpu(ch->tx_time));
+               ACS_PRINT(RX_TIME, "rx", le16_to_cpu(ch->rx_time));
+               ACS_PRINT(NOISE, "noise", ch->noise);
+#undef ACS_PRINT
+               seq_puts(s, "\n");
+       }
+       seq_puts(s, "}\n");
+       return 0;
+}
+
+static int wil_survey_seq_open(struct inode *inode, struct file *file)
+{
+       return single_open(file, wil_survey_debugfs_show, inode->i_private);
+}
+
+static const struct file_operations fops_survey = {
+       .open           = wil_survey_seq_open,
+       .release        = single_release,
+       .read           = seq_read,
+       .llseek         = seq_lseek,
+};
+
 /*----------------*/
 static void wil6210_debugfs_init_blobs(struct wil6210_priv *wil,
                                       struct dentry *dbg)
@@ -2473,6 +2626,7 @@ static void wil6210_debugfs_init_blobs(struct 
wil6210_priv *wil,
        {"tx_latency",  0644,           &fops_tx_latency},
        {"link_stats",  0644,           &fops_link_stats},
        {"link_stats_global",   0644,   &fops_link_stats_global},
+       {"acs_ch_weight",       0644,   &fops_acs_ch_weight},
 };
 
 static void wil6210_debugfs_init_files(struct wil6210_priv *wil,
diff --git a/drivers/net/wireless/ath/wil6210/main.c 
b/drivers/net/wireless/ath/wil6210/main.c
index ba6a2ee..f20ced0 100644
--- a/drivers/net/wireless/ath/wil6210/main.c
+++ b/drivers/net/wireless/ath/wil6210/main.c
@@ -750,6 +750,11 @@ int wil_priv_init(struct wil6210_priv *wil)
 
        wil->amsdu_en = 1;
 
+       /* ACS related */
+       wil->acs_ch_weight[0] = 120;
+       for (i = 1; i < WIL_MAX_CHANNELS; i++)
+               wil->acs_ch_weight[i] = 100;
+
        return 0;
 
 out_wmi_wq:
diff --git a/drivers/net/wireless/ath/wil6210/wil6210.h 
b/drivers/net/wireless/ath/wil6210/wil6210.h
index 0f3be3ff..c286750 100644
--- a/drivers/net/wireless/ath/wil6210/wil6210.h
+++ b/drivers/net/wireless/ath/wil6210/wil6210.h
@@ -67,6 +67,8 @@
  */
 #define WIL_MAX_VIFS 4
 
+#define WIL_MAX_CHANNELS 4 /* max supported channels */
+
 /**
  * extract bits [@b0:@b1] (inclusive) from the value @x
  * it should be @b0 <= @b1, or result is incorrect
@@ -1045,6 +1047,15 @@ struct wil6210_priv {
 
        u32 max_agg_wsize;
        u32 max_ampdu_size;
+
+       /* ACS related */
+       unsigned short acs_ch_weight[WIL_MAX_CHANNELS];
+       bool survey_ready;
+       struct {
+               struct wmi_cmd_hdr wmi;
+               struct wmi_acs_passive_scan_complete_event evt;
+               struct scan_acs_info ch_info[WIL_MAX_CHANNELS];
+       } __packed survey_reply;
 };
 
 #define wil_to_wiphy(i) (i->wiphy)
@@ -1252,6 +1263,9 @@ int wil_addba_rx_request(struct wil6210_priv *wil, u8 mid,
                         u8 cidxtid, u8 dialog_token, __le16 ba_param_set,
                         __le16 ba_timeout, __le16 ba_seq_ctrl);
 int wil_addba_tx_request(struct wil6210_priv *wil, u8 ringid, u16 wsize);
+int wil_start_acs_survey(struct wil6210_priv *wil, uint dwell_time,
+                        struct cfg80211_chan_def channels[],
+                        u8 num_channels);
 
 void wil6210_clear_irq(struct wil6210_priv *wil);
 int wil6210_init_irq(struct wil6210_priv *wil, int irq);
@@ -1402,4 +1416,5 @@ int wmi_addba_rx_resp_edma(struct wil6210_priv *wil, u8 
mid, u8 cid,
 
 void update_supported_bands(struct wil6210_priv *wil);
 
+int wil_num_supported_channels(struct wil6210_priv *wil);
 #endif /* __WIL6210_H__ */
diff --git a/drivers/net/wireless/ath/wil6210/wmi.c 
b/drivers/net/wireless/ath/wil6210/wmi.c
index 345f059..5ca0d88 100644
--- a/drivers/net/wireless/ath/wil6210/wmi.c
+++ b/drivers/net/wireless/ath/wil6210/wmi.c
@@ -3811,3 +3811,79 @@ int wmi_link_stats_cfg(struct wil6210_vif *vif, u32 
type, u8 cid, u32 interval)
 
        return 0;
 }
+
+int wil_start_acs_survey(struct wil6210_priv *wil, uint dwell_time,
+                        struct cfg80211_chan_def channels[],
+                        u8 num_channels)
+{
+       struct wil6210_vif *vif = ndev_to_vif(wil->main_ndev);
+       int rc, i;
+       u8 ch;
+       struct wmi_acs_passive_scan_complete_event *reply;
+       u8 num_supported_channels = wil_num_supported_channels(wil);
+       struct {
+               struct wmi_start_scan_cmd cmd;
+               struct {
+                       u8 channel;
+                       u8 reserved;
+               } channel_list[WIL_MAX_CHANNELS];
+       } __packed scan_cmd = {
+               .cmd = {
+                       .scan_type = WMI_PASSIVE_SCAN,
+                       .dwell_time = cpu_to_le32(dwell_time),
+                       .num_channels = min_t(u8, num_channels,
+                                             num_supported_channels),
+               },
+       };
+
+       wil->survey_ready = false;
+       memset(&wil->survey_reply, 0, sizeof(wil->survey_reply));
+       reply = &wil->survey_reply.evt;
+       reply->status = WMI_SCAN_FAILED;
+
+       for (i = 0; i < scan_cmd.cmd.num_channels; i++) {
+               ch = channels[i].chan->hw_value;
+
+               if (ch == 0) {
+                       wil_err(wil, "ACS requested for wrong channel\n");
+                       return -EINVAL;
+               }
+               wil_dbg_misc(wil, "ACS channel %d : %d MHz\n",
+                            ch, channels[i].chan->center_freq);
+               scan_cmd.channel_list[i].channel = ch - 1;
+       }
+
+       /* send scan command with the requested channel and wait
+        * for results
+        */
+       rc = wmi_call(wil, WMI_START_SCAN_CMDID, vif->mid, &scan_cmd,
+                     sizeof(scan_cmd), WMI_ACS_PASSIVE_SCAN_COMPLETE_EVENTID,
+                     &wil->survey_reply, sizeof(wil->survey_reply),
+                     WMI_SURVEY_TIMEOUT_MS);
+       if (rc) {
+               wil_err(wil, "ACS passive Scan failed (0x%08x)\n", rc);
+               return rc;
+       }
+
+       if (reply->num_scanned_channels > num_supported_channels) {
+               wil_err(wil,
+                       "Survey num of scanned channels %d exceeds num of 
supported channels %d\n",
+                       reply->num_scanned_channels,
+                       num_supported_channels);
+               reply->status = WMI_SCAN_FAILED;
+               return -EINVAL;
+       }
+
+       if (reply->status != WMI_SCAN_SUCCESS) {
+               wil_err(wil, "ACS survey failed, status (%d)\n",
+                       wil->survey_reply.evt.status);
+               return -EINVAL;
+       }
+       wil->survey_ready = true;
+
+       /* The results in survey_reply */
+       wil_dbg_misc(wil, "ACS scan success, filled mask: 0x%08X\n",
+                    le16_to_cpu(reply->filled));
+
+       return 0;
+}
diff --git a/drivers/net/wireless/ath/wil6210/wmi.h 
b/drivers/net/wireless/ath/wil6210/wmi.h
index b668758..5a71e98 100644
--- a/drivers/net/wireless/ath/wil6210/wmi.h
+++ b/drivers/net/wireless/ath/wil6210/wmi.h
@@ -2444,7 +2444,8 @@ struct wmi_acs_passive_scan_complete_event {
         */
        __le16 filled;
        u8 num_scanned_channels;
-       u8 reserved;
+       /* enum scan_status */
+       u8 status;
        struct scan_acs_info scan_info_list[0];
 } __packed;
 
-- 
1.9.1

Reply via email to