@@ -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(
@@ -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,
@@ -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:
@@ -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__ */
@@ -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;
+}
@@ -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;
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 <amasri@codeaurora.org> --- 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(-)