Return-path: Received: from wolverine01.qualcomm.com ([199.106.114.254]:4099 "EHLO wolverine01.qualcomm.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1753018AbbGFHkZ (ORCPT ); Mon, 6 Jul 2015 03:40:25 -0400 From: Vladimir Kondratiev Cc: Vladimir Kondratiev , linux-wireless@vger.kernel.org, wil6210@qca.qualcomm.com, Vladimir Shulman To: Kalle Valo Subject: [PATCH v2 02/13] wil6210: ACS implementation using QCA vendor command Date: Mon, 6 Jul 2015 10:39:38 +0300 Message-Id: <1436168389-3676-3-git-send-email-qca_vkondrat@qca.qualcomm.com> (sfid-20150706_094032_354683_A5DF6FB0) In-Reply-To: <1436168389-3676-1-git-send-email-qca_vkondrat@qca.qualcomm.com> References: <1436081080-27305-1-git-send-email-qca_vkondrat@qca.qualcomm.com> <1436168389-3676-1-git-send-email-qca_vkondrat@qca.qualcomm.com> Sender: linux-wireless-owner@vger.kernel.org List-ID: ACS is implemented by registering to QCA vendor ACS sub command. On receive of the command from hostapd, driver issues passive scan command and block until scan results are available. When received, the results are sent up using QCA vendor ACS results event Signed-off-by: Vladimir Shulman Signed-off-by: Vladimir Kondratiev --- drivers/net/wireless/ath/wil6210/cfg80211.c | 389 ++++++++++++++++++++++++++++ drivers/net/wireless/ath/wil6210/wil6210.h | 6 + drivers/net/wireless/ath/wil6210/wmi.c | 44 +++- drivers/net/wireless/ath/wil6210/wmi.h | 38 ++- 4 files changed, 464 insertions(+), 13 deletions(-) diff --git a/drivers/net/wireless/ath/wil6210/cfg80211.c b/drivers/net/wireless/ath/wil6210/cfg80211.c index e4be2d9..5f05388 100644 --- a/drivers/net/wireless/ath/wil6210/cfg80211.c +++ b/drivers/net/wireless/ath/wil6210/cfg80211.c @@ -14,10 +14,28 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +#include #include +#include #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; +} + diff --git a/drivers/net/wireless/ath/wil6210/wil6210.h b/drivers/net/wireless/ath/wil6210/wil6210.h index b79ba49..4b7135e 100644 --- a/drivers/net/wireless/ath/wil6210/wil6210.h +++ b/drivers/net/wireless/ath/wil6210/wil6210.h @@ -22,6 +22,7 @@ #include #include #include +#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) diff --git a/drivers/net/wireless/ath/wil6210/wmi.c b/drivers/net/wireless/ath/wil6210/wmi.c index c759759..12b5770 100644 --- a/drivers/net/wireless/ath/wil6210/wmi.c +++ b/drivers/net/wireless/ath/wil6210/wmi.c @@ -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}, }; /* diff --git a/drivers/net/wireless/ath/wil6210/wmi.h b/drivers/net/wireless/ath/wil6210/wmi.h index 6e90e78..b44f7ae 100644 --- a/drivers/net/wireless/ath/wil6210/wmi.h +++ b/drivers/net/wireless/ath/wil6210/wmi.h @@ -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 { -- 2.1.4