@@ -14,10 +14,28 @@
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
+#include <linux/moduleparam.h>
#include <linux/etherdevice.h>
+#include <net/netlink.h>
#include "wil6210.h"
#include "wmi.h"
+static unsigned short scan_dwell_time = WMI_SCAN_DWELL_TIME_MS;
+module_param(scan_dwell_time, ushort, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(scan_dwell_time, " Scan dwell time");
+
+static unsigned short acs_ch_weight[4] = {120, 100, 100, 100};
+module_param_array(acs_ch_weight, ushort, NULL, 0);
+MODULE_PARM_DESC(acs_ch_weight, " Channel weight in %. This is channel priority for ACS");
+
+/* 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 = IEEE80211_BAND_60GHZ, \
.center_freq = 56160 + (2160 * (_channel)), \
@@ -34,6 +52,72 @@ static struct ieee80211_channel wil_60ghz_channels[] = {
/* channel 4 not supported yet */
};
+/* Vendor id to be used in vendor specific command and events
+ * to user space.
+ * NOTE: The authoritative place for definition of QCA_NL80211_VENDOR_ID,
+ * vendor subcmd definitions prefixed with QCA_NL80211_VENDOR_SUBCMD, and
+ * qca_wlan_vendor_attr is open source file src/common/qca-vendor.h in
+ * git://w1.fi/srv/git/hostap.git; the values here are just a copy of that
+ */
+
+#define QCA_NL80211_VENDOR_ID 0x001374
+
+enum qca_wlan_vendor_attr_acs_offload {
+ QCA_WLAN_VENDOR_ATTR_ACS_CHANNEL_INVALID = 0,
+ QCA_WLAN_VENDOR_ATTR_ACS_PRIMARY_CHANNEL,
+ QCA_WLAN_VENDOR_ATTR_ACS_SECONDARY_CHANNEL,
+ QCA_WLAN_VENDOR_ATTR_ACS_HW_MODE,
+ QCA_WLAN_VENDOR_ATTR_ACS_HT_ENABLED,
+ QCA_WLAN_VENDOR_ATTR_ACS_HT40_ENABLED,
+ /* keep last */
+ QCA_WLAN_VENDOR_ATTR_ACS_AFTER_LAST,
+ QCA_WLAN_VENDOR_ATTR_ACS_MAX =
+ QCA_WLAN_VENDOR_ATTR_ACS_AFTER_LAST - 1
+};
+
+enum qca_wlan_vendor_acs_hw_mode {
+ QCA_ACS_MODE_IEEE80211B,
+ QCA_ACS_MODE_IEEE80211G,
+ QCA_ACS_MODE_IEEE80211A,
+ QCA_ACS_MODE_IEEE80211AD,
+};
+
+static const struct
+nla_policy qca_wlan_acs_vendor_attr[QCA_WLAN_VENDOR_ATTR_ACS_MAX + 1] = {
+ [QCA_WLAN_VENDOR_ATTR_ACS_HW_MODE] = { .type = NLA_U8 },
+};
+
+enum qca_nl80211_vendor_subcmds {
+ QCA_NL80211_VENDOR_SUBCMD_DO_ACS = 54,
+};
+
+enum qca_nl80211_vendor_subcmds_index {
+ QCA_NL80211_VENDOR_SUBCMD_DO_ACS_INDEX,
+};
+
+static int wil_do_acs(struct wiphy *wiphy, struct wireless_dev *wdev,
+ const void *data, int data_len);
+
+/* vendor specific commands */
+static const struct wiphy_vendor_command wil_nl80211_vendor_commands[] = {
+ [QCA_NL80211_VENDOR_SUBCMD_DO_ACS_INDEX] = {
+ .info.vendor_id = QCA_NL80211_VENDOR_ID,
+ .info.subcmd = QCA_NL80211_VENDOR_SUBCMD_DO_ACS,
+ .flags = WIPHY_VENDOR_CMD_NEED_WDEV |
+ WIPHY_VENDOR_CMD_NEED_NETDEV |
+ WIPHY_VENDOR_CMD_NEED_RUNNING,
+ .doit = wil_do_acs
+ },
+};
+
+/* vendor specific events */
+static const struct nl80211_vendor_cmd_info wil_nl80211_vendor_events[] = {
+ [QCA_NL80211_VENDOR_SUBCMD_DO_ACS_INDEX] = {
+ .vendor_id = QCA_NL80211_VENDOR_ID,
+ .subcmd = QCA_NL80211_VENDOR_SUBCMD_DO_ACS
+ },
+};
+
static struct ieee80211_supported_band wil_band_60ghz = {
.channels = wil_60ghz_channels,
.n_channels = ARRAY_SIZE(wil_60ghz_channels),
@@ -1140,6 +1224,11 @@ static void wil_wiphy_init(struct wiphy *wiphy)
wiphy->n_cipher_suites = ARRAY_SIZE(wil_cipher_suites);
wiphy->mgmt_stypes = wil_mgmt_stypes;
wiphy->features |= NL80211_FEATURE_SK_TX_STATUS;
+
+ wiphy->n_vendor_commands = ARRAY_SIZE(wil_nl80211_vendor_commands);
+ wiphy->vendor_commands = wil_nl80211_vendor_commands;
+ wiphy->vendor_events = wil_nl80211_vendor_events;
+ wiphy->n_vendor_events = ARRAY_SIZE(wil_nl80211_vendor_events);
}
struct wireless_dev *wil_cfg80211_init(struct device *dev)
@@ -1190,3 +1279,303 @@ void wil_wdev_free(struct wil6210_priv *wil)
wiphy_free(wdev->wiphy);
kfree(wdev);
}
+
+static int wil_start_acs_survey(struct wil6210_priv *wil, uint dwell_time,
+ struct ieee80211_channel *channels,
+ u8 num_channels)
+{
+ int rc, i;
+ unsigned long to = msecs_to_jiffies(WMI_SURVEY_TIMEOUT_MS);
+ struct {
+ struct wmi_start_scan_cmd cmd;
+ struct {
+ u8 channel;
+ u8 reserved;
+ } channel_list[4];
+ } __packed scan_cmd = {
+ .cmd = {
+ .scan_type = WMI_PASSIVE_SCAN,
+ .dwell_time = cpu_to_le32(dwell_time),
+ .num_channels = min_t(u8, num_channels,
+ ARRAY_SIZE(wil_60ghz_channels)),
+ },
+ };
+
+ wil->survey_ready = false;
+ memset(&wil->survey_reply, 0, sizeof(wil->survey_reply));
+
+ for (i = 0; i < scan_cmd.cmd.num_channels; i++) {
+ u8 ch = channels[i].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].center_freq);
+ scan_cmd.channel_list[i].channel = ch - 1;
+ }
+
+ /* send scan command with the requested channel and wait
+ * for results
+ */
+ rc = wmi_send(wil, WMI_START_SCAN_CMDID, &scan_cmd, sizeof(scan_cmd));
+ if (rc) {
+ wil_err(wil, "ACS passive Scan failed (0x%08x)\n", rc);
+ return rc;
+ }
+
+ if (wait_event_interruptible_timeout(wil->wq, wil->survey_ready, to)
+ < 0) {
+ wil_err(wil, "%s: ACS survey interrupted\n", __func__);
+ return -ERESTARTSYS;
+ }
+
+ if (!wil->survey_ready) {
+ wil_err(wil, "ACS survey time out\n");
+ return -ETIME;
+ }
+
+ /* The results in survey_reply */
+ wil_dbg_misc(wil, "ACS scan success, filled mask: 0x%08X\n",
+ le16_to_cpu(wil->survey_reply.evt.filled));
+
+ return 0;
+}
+
+static u8 wil_acs_calc_channel(struct wil6210_priv *wil)
+{
+ int i, best_channel = ACS_DEFAULT_BEST_CHANNEL - 1;
+ struct scan_channel_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, "%s: filled info: 0x%04X, for %u channels\n",
+ __func__, filled, num_channels);
+
+ if (!num_channels) {
+ wil_err(wil, "%s: received results with no channel info\n",
+ __func__);
+ 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, "%s: p_min is %u\n", __func__, 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];
+
+ if (ch->channel > 3) {
+ wil_err(wil,
+ "%s: invalid channel number %d\n",
+ __func__, ch->channel + 1);
+ continue;
+ }
+
+ 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,
+ "%s: Ch[%d]: busy %llu, tx %llu, noise %u, dwell %llu\n",
+ __func__, ch->channel + 1, busy_time, tx_time,
+ ch_noise, dwell_time);
+
+ if (dwell_time == tx_time) {
+ wil_err(wil,
+ "%s: Ch[%d] dwell_time == tx_time: %llu\n",
+ __func__, 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) *
+ acs_ch_weight[ch->channel];
+ do_div(cur_i_ch, 100);
+
+ wil_dbg_misc(wil, "%s: Ch[%d] w %u, I_ch %llu\n", __func__,
+ ch->channel + 1, 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, "%s: best channel %d with I_ch of %llu\n", __func__,
+ best_channel + 1, min_i_ch);
+
+ return best_channel;
+}
+
+static void wil_acs_report_channel(struct wil6210_priv *wil)
+{
+ struct sk_buff *vendor_event;
+ int ret_val;
+ struct nlattr *nla;
+ u8 channel = wil_acs_calc_channel(wil);
+
+ vendor_event = cfg80211_vendor_event_alloc(wil_to_wiphy(wil), NULL,
+ 2 + 4 + NLMSG_HDRLEN,
+ QCA_NL80211_VENDOR_SUBCMD_DO_ACS_INDEX,
+ GFP_KERNEL);
+ if (!vendor_event) {
+ wil_err(wil, "cfg80211_vendor_event_alloc failed\n");
+ return;
+ }
+
+ /* Send the IF INDEX to differentiate the ACS event for each interface
+ * TODO: To be update once cfg80211 APIs are updated to accept if_index
+ */
+ nla_nest_cancel(vendor_event, ((void **)vendor_event->cb)[2]);
+
+ ret_val = nla_put_u32(vendor_event, NL80211_ATTR_IFINDEX,
+ wil_to_ndev(wil)->ifindex);
+ if (ret_val) {
+ wil_err(wil, "NL80211_ATTR_IFINDEX put fail\n");
+ kfree_skb(vendor_event);
+ return;
+ }
+
+ nla = nla_nest_start(vendor_event, NL80211_ATTR_VENDOR_DATA);
+ ((void **)vendor_event->cb)[2] = nla;
+
+ /* channel indices used by fw are zero based and those used upper
+ * layers are 1 based: must add 1
+ */
+ ret_val = nla_put_u8(vendor_event,
+ QCA_WLAN_VENDOR_ATTR_ACS_PRIMARY_CHANNEL,
+ channel + 1);
+ if (ret_val) {
+ wil_err(wil,
+ "QCA_WLAN_VENDOR_ATTR_ACS_PRIMARY_CHANNEL put fail\n");
+ kfree_skb(vendor_event);
+ return;
+ }
+
+ /* must report secondary channel always, 0 is harmless */
+ ret_val = nla_put_u8(vendor_event,
+ QCA_WLAN_VENDOR_ATTR_ACS_SECONDARY_CHANNEL, 0);
+ if (ret_val) {
+ wil_err(wil,
+ "QCA_WLAN_VENDOR_ATTR_ACS_SECONDARY_CHANNEL put fail\n");
+ kfree_skb(vendor_event);
+ return;
+ }
+
+ cfg80211_vendor_event(vendor_event, GFP_KERNEL);
+}
+
+static int wil_do_acs(struct wiphy *wiphy, struct wireless_dev *wdev,
+ const void *data, int data_len)
+{
+ struct wil6210_priv *wil = wdev_to_wil(wdev);
+ struct sk_buff *temp_skbuff;
+ int rc;
+ struct nlattr *tb[QCA_WLAN_VENDOR_ATTR_ACS_MAX + 1];
+ u8 hw_mode;
+ struct ieee80211_channel reg_channels[ARRAY_SIZE(wil_60ghz_channels)];
+ int num_channels;
+ const struct ieee80211_reg_rule *reg_rule;
+ int i;
+
+ rc = nla_parse(tb, QCA_WLAN_VENDOR_ATTR_ACS_MAX, data, data_len,
+ qca_wlan_acs_vendor_attr);
+ if (rc) {
+ wil_err(wil, "%s: Invalid ATTR\n", __func__);
+ goto out;
+ }
+
+ if (!tb[QCA_WLAN_VENDOR_ATTR_ACS_HW_MODE]) {
+ wil_err(wil, "%s: Attr hw_mode failed\n", __func__);
+ goto out;
+ }
+
+ hw_mode = nla_get_u8(tb[QCA_WLAN_VENDOR_ATTR_ACS_HW_MODE]);
+
+ if (hw_mode != QCA_ACS_MODE_IEEE80211AD) {
+ wil_err(wil, "%s: Illegal HW mode (%d), must be %d (11AD)\n",
+ __func__, hw_mode, QCA_ACS_MODE_IEEE80211AD);
+ goto out;
+ }
+
+ /* get list of channels allowed by regulatory */
+ num_channels = 0;
+ for (i = 0; i < ARRAY_SIZE(wil_60ghz_channels); i++) {
+ u32 ch_center_freq =
+ MHZ_TO_KHZ(wil_60ghz_channels[i].center_freq);
+ reg_rule = freq_reg_info(wiphy, ch_center_freq);
+ if (IS_ERR(reg_rule)) {
+ wil_dbg_misc(wil,
+ "%s: channel %d (%d) reg db err %ld\n",
+ __func__, wil_60ghz_channels[i].hw_value,
+ wil_60ghz_channels[i].center_freq,
+ PTR_ERR(reg_rule));
+ continue;
+ }
+
+ /* we assume if active scan allowed, we can use the
+ * channel to start AP on it
+ */
+ if (!(reg_rule->flags & NL80211_RRF_PASSIVE_SCAN)) {
+ reg_channels[i] = wil_60ghz_channels[i];
+ num_channels++;
+ wil_dbg_misc(wil, "%s: Adding ch %d to ACS scan\n",
+ __func__, wil_60ghz_channels[i].hw_value);
+ } else {
+ wil_dbg_misc(wil,
+ "%s: channel %d (%d) can't be used: 0x%08X\n",
+ __func__, wil_60ghz_channels[i].hw_value,
+ wil_60ghz_channels[i].center_freq,
+ reg_rule->flags);
+ }
+ }
+
+ if (!num_channels) {
+ wil_err(wil,
+ "ACS aborted. Couldn't find channels allowed by regulatory\n");
+ rc = -EPERM;
+ goto out;
+ }
+
+ /* start acs survey*/
+ rc = wil_start_acs_survey(wil, scan_dwell_time, reg_channels,
+ num_channels);
+
+ if (!rc)
+ wil_acs_report_channel(wil);
+out:
+ if (0 == rc) {
+ temp_skbuff = cfg80211_vendor_cmd_alloc_reply_skb(wiphy,
+ NLMSG_HDRLEN);
+ if (temp_skbuff)
+ return cfg80211_vendor_cmd_reply(temp_skbuff);
+ }
+ return rc;
+}
+
@@ -22,6 +22,7 @@
#include <net/cfg80211.h>
#include <linux/timex.h>
#include <linux/types.h>
+#include "wmi.h"
#include "wil_platform.h"
extern bool no_fw_recovery;
@@ -619,6 +620,11 @@ struct wil6210_priv {
struct wil_platform_ops platform_ops;
struct pmc_ctx pmc;
+ bool survey_ready;
+ struct {
+ struct wmi_acs_passive_scan_complete_event evt;
+ struct scan_channel_info ch_info[4];
+ } __packed survey_reply;
};
#define wil_to_wiphy(i) (i->wdev->wiphy)
@@ -382,6 +382,27 @@ static void wmi_evt_scan_complete(struct wil6210_priv *wil, int id,
}
}
+static void wmi_evt_survey_complete(struct wil6210_priv *wil, int id,
+ void *d, int len)
+{
+ struct wmi_acs_passive_scan_complete_event *evt =
+ (struct wmi_acs_passive_scan_complete_event *)d;
+
+ int expected_size = sizeof(struct wmi_acs_passive_scan_complete_event) +
+ evt->num_scanned_channels *
+ sizeof(struct scan_channel_info);
+
+ if (len < expected_size) {
+ wil_err(wil, "Survey event size is too small (%d): expected %d channels\n",
+ len, evt->num_scanned_channels);
+ return;
+ }
+
+ memcpy(&wil->survey_reply, d, sizeof(wil->survey_reply));
+ wil->survey_ready = true;
+ wake_up_interruptible(&wil->wq);
+}
+
static void wmi_evt_connect(struct wil6210_priv *wil, int id, void *d, int len)
{
struct net_device *ndev = wil_to_ndev(wil);
@@ -656,17 +677,18 @@ static const struct {
void (*handler)(struct wil6210_priv *wil, int eventid,
void *data, int data_len);
} wmi_evt_handlers[] = {
- {WMI_READY_EVENTID, wmi_evt_ready},
- {WMI_FW_READY_EVENTID, wmi_evt_fw_ready},
- {WMI_RX_MGMT_PACKET_EVENTID, wmi_evt_rx_mgmt},
- {WMI_SCAN_COMPLETE_EVENTID, wmi_evt_scan_complete},
- {WMI_CONNECT_EVENTID, wmi_evt_connect},
- {WMI_DISCONNECT_EVENTID, wmi_evt_disconnect},
- {WMI_EAPOL_RX_EVENTID, wmi_evt_eapol_rx},
- {WMI_BA_STATUS_EVENTID, wmi_evt_ba_status},
- {WMI_RCP_ADDBA_REQ_EVENTID, wmi_evt_addba_rx_req},
- {WMI_DELBA_EVENTID, wmi_evt_delba},
- {WMI_VRING_EN_EVENTID, wmi_evt_vring_en},
+ {WMI_READY_EVENTID, wmi_evt_ready},
+ {WMI_FW_READY_EVENTID, wmi_evt_fw_ready},
+ {WMI_RX_MGMT_PACKET_EVENTID, wmi_evt_rx_mgmt},
+ {WMI_SCAN_COMPLETE_EVENTID, wmi_evt_scan_complete},
+ {WMI_ACS_PASSIVE_SCAN_COMPLETE_EVENTID, wmi_evt_survey_complete},
+ {WMI_CONNECT_EVENTID, wmi_evt_connect},
+ {WMI_DISCONNECT_EVENTID, wmi_evt_disconnect},
+ {WMI_EAPOL_RX_EVENTID, wmi_evt_eapol_rx},
+ {WMI_BA_STATUS_EVENTID, wmi_evt_ba_status},
+ {WMI_RCP_ADDBA_REQ_EVENTID, wmi_evt_addba_rx_req},
+ {WMI_DELBA_EVENTID, wmi_evt_delba},
+ {WMI_VRING_EN_EVENTID, wmi_evt_vring_en},
};
/*
@@ -33,6 +33,8 @@
#define WMI_MAC_LEN (6)
#define WMI_PROX_RANGE_NUM (3)
#define WMI_MAX_LOSS_DMG_BEACONS (32)
+#define WMI_SCAN_DWELL_TIME_MS (100)
+#define WMI_SURVEY_TIMEOUT_MS (10000)
/* List of Commands */
enum wmi_command_id {
@@ -288,7 +290,7 @@ struct wmi_delete_cipher_key_cmd {
enum wmi_scan_type {
WMI_LONG_SCAN = 0,
WMI_SHORT_SCAN = 1,
- WMI_PBC_SCAN = 2,
+ WMI_PASSIVE_SCAN = 2,
WMI_DIRECT_SCAN = 3,
WMI_ACTIVE_SCAN = 4,
};
@@ -296,7 +298,7 @@ enum wmi_scan_type {
struct wmi_start_scan_cmd {
u8 direct_scan_mac_addr[6];
u8 reserved[2];
- __le32 home_dwell_time; /* Max duration in the home channel(ms) */
+ __le32 dwell_time; /* duration in the home channel(ms) */
__le32 force_scan_interval; /* Time interval between scans (ms)*/
u8 scan_type; /* wmi_scan_type */
u8 num_channels; /* how many channels follow */
@@ -943,6 +945,7 @@ enum wmi_event_id {
WMI_EAPOL_RX_EVENTID = 0x9002,
WMI_MAC_ADDR_RESP_EVENTID = 0x9003,
WMI_FW_VER_EVENTID = 0x9004,
+ WMI_ACS_PASSIVE_SCAN_COMPLETE_EVENTID = 0x9005,
};
/*
@@ -1127,6 +1130,37 @@ struct wmi_scan_complete_event {
} __packed;
/*
+ * WMI_ACS_PASSIVE_SCAN_COMPLETE_EVENT
+ */
+enum wmi_acs_info_bitmask_e {
+ WMI_ACS_INFO_BITMASK_BEACON_FOUND = 0x01,
+ WMI_ACS_INFO_BITMASK_BUSY_TIME = 0x02,
+ WMI_ACS_INFO_BITMASK_TX_TIME = 0x04,
+ WMI_ACS_INFO_BITMASK_RX_TIME = 0x08,
+ WMI_ACS_INFO_BITMASK_NOISE = 0x10,
+};
+
+struct scan_channel_info {
+ u8 channel;
+ u8 beacon_found;
+ __le16 busy_time;
+ __le16 tx_time;
+ __le16 rx_time;
+ u8 noise;
+ u8 reserved[3];
+} __packed;
+
+struct wmi_acs_passive_scan_complete_event {
+ __le32 dwell_time;
+ __le16 filled; /* valid fields within channel info according to
+ * their appearance in struct order
+ */
+ u8 num_scanned_channels;
+ u8 reserved;
+ struct scan_channel_info scan_time_list[0];
+} __packed;
+
+/*
* WMI_BA_STATUS_EVENTID
*/
enum wmi_vring_ba_status {