/* * Copyright (c) 2010-2011 Atheros Communications 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 "testmode.h" #include #include "core.h" /* * netlink.h remove these macros from kernel 3.5. * TODO : Error handle for nla_put_XXX calls. */ #ifndef NLA_PUT #define NLA_PUT_U32 nla_put_u32 #define NLA_PUT nla_put #else #define _NLA_PUT_ERR_RTN #endif enum ath6kl_tm_attr { __ATH6KL_TM_ATTR_INVALID = 0, ATH6KL_TM_ATTR_CMD = 1, ATH6KL_TM_ATTR_DATA = 2, ATH6KL_TM_ATTR_TYPE = 3, /* keep last */ __ATH6KL_TM_ATTR_AFTER_LAST, ATH6KL_TM_ATTR_MAX = __ATH6KL_TM_ATTR_AFTER_LAST - 1, }; enum ath6kl_tm_cmd { ATH6KL_TM_CMD_TCMD = 0, ATH6KL_TM_CMD_WLAN_HB = 1, ATH6KL_TM_CMD_WIFI_DISC = 2, ATH6KL_TM_CMD_WIFI_KTK = 3, ATH6KL_TM_CMD_DFS_SKIP = 4, }; #define ATH6KL_TM_DATA_MAX_LEN 5000 static const struct nla_policy ath6kl_tm_policy[ATH6KL_TM_ATTR_MAX + 1] = { [ATH6KL_TM_ATTR_CMD] = { .type = NLA_U32 }, [ATH6KL_TM_ATTR_DATA] = { .type = NLA_BINARY, .len = ATH6KL_TM_DATA_MAX_LEN }, }; #ifdef CONFIG_NL80211_TESTMODE void ath6kl_tm_rx_report_event(struct ath6kl *ar, void *buf, size_t buf_len) { if (down_interruptible(&ar->sem)) return; kfree(ar->tm.rx_report); ar->tm.rx_report = kmemdup(buf, buf_len, GFP_KERNEL); ar->tm.rx_report_len = buf_len; up(&ar->sem); wake_up(&ar->event_wq); } void ath6kl_tm_rx_event(struct ath6kl *ar, void *buf, size_t buf_len) { struct sk_buff *skb; if (!buf || buf_len == 0) { printk(KERN_ERR "buf buflen is empty\n"); return; } skb = cfg80211_testmode_alloc_event_skb(ar->wiphy, buf_len, GFP_ATOMIC); if (!skb) { printk(KERN_ERR "failed to allocate testmode rx skb!\n"); return; } NLA_PUT_U32(skb, ATH6KL_TM_ATTR_CMD, ATH6KL_TM_CMD_TCMD); NLA_PUT(skb, ATH6KL_TM_ATTR_DATA, buf_len, buf); cfg80211_testmode_event(skb, GFP_ATOMIC); return; #ifdef _NLA_PUT_ERR_RTN nla_put_failure: kfree_skb(skb); printk(KERN_ERR "nla_put failed on testmode rx skb!\n"); #endif } #ifdef ATH6KL_SUPPORT_WLAN_HB void ath6kl_wlan_hb_event(struct ath6kl *ar, u8 value, void *buf, size_t buf_len) { struct sk_buff *skb; if (!buf || buf_len == 0) { printk(KERN_ERR "buf buflen is empty\n"); return; } skb = cfg80211_testmode_alloc_event_skb(ar->wiphy, buf_len, GFP_ATOMIC); if (!skb) { printk(KERN_ERR "failed to allocate testmode event skb!\n"); return; } NLA_PUT_U32(skb, ATH6KL_TM_ATTR_CMD, ATH6KL_TM_CMD_WLAN_HB); NLA_PUT_U32(skb, ATH6KL_TM_ATTR_TYPE, value); NLA_PUT(skb, ATH6KL_TM_ATTR_DATA, buf_len, buf); cfg80211_testmode_event(skb, GFP_ATOMIC); return; #ifdef _NLA_PUT_ERR_RTN nla_put_failure: kfree_skb(skb); printk(KERN_ERR "nla_put failed on testmode event skb!\n"); #endif } #endif #ifdef ATH6KL_SUPPORT_WIFI_DISC void ath6kl_tm_disc_event(struct ath6kl *ar, void *buf, size_t buf_len) { struct sk_buff *skb; if (!buf || buf_len == 0) { printk(KERN_ERR "buf buflen is empty\n"); return; } skb = cfg80211_testmode_alloc_event_skb(ar->wiphy, buf_len, GFP_ATOMIC); if (!skb) { printk(KERN_ERR "failed to allocate testmode event skb!\n"); return; } NLA_PUT_U32(skb, ATH6KL_TM_ATTR_CMD, ATH6KL_TM_CMD_WIFI_DISC); NLA_PUT(skb, ATH6KL_TM_ATTR_DATA, buf_len, buf); cfg80211_testmode_event(skb, GFP_ATOMIC); return; #ifdef _NLA_PUT_ERR_RTN nla_put_failure: kfree_skb(skb); printk(KERN_ERR "nla_put failed on testmode event skb!\n"); #endif } #endif static int ath6kl_tm_rx_report(struct ath6kl *ar, void *buf, size_t buf_len, struct sk_buff *skb) { int ret = 0; long left; if (!test_bit(WMI_READY, &ar->flag)) { ret = -EIO; goto out; } if (test_bit(DESTROY_IN_PROGRESS, &ar->flag)) { ret = -EBUSY; goto out; } if (down_interruptible(&ar->sem)) return -EIO; if (ath6kl_wmi_test_cmd(ar->wmi, buf, buf_len) < 0) { up(&ar->sem); return -EIO; } left = wait_event_interruptible_timeout(ar->event_wq, ar->tm.rx_report != NULL, WMI_TIMEOUT); if (left == 0) { ret = -ETIMEDOUT; goto out; } else if (left < 0) { ret = left; goto out; } if (ar->tm.rx_report == NULL || ar->tm.rx_report_len == 0) { ret = -EINVAL; goto out; } NLA_PUT(skb, ATH6KL_TM_ATTR_DATA, ar->tm.rx_report_len, ar->tm.rx_report); kfree(ar->tm.rx_report); ar->tm.rx_report = NULL; out: up(&ar->sem); return ret; #ifdef _NLA_PUT_ERR_RTN nla_put_failure: ret = -ENOBUFS; goto out; #endif } EXPORT_SYMBOL(ath6kl_tm_rx_report); #ifdef ATH6KL_SUPPORT_WLAN_HB enum nl80211_wlan_hb_cmd { NL80211_WLAN_HB_ENABLE = 0, NL80211_WLAN_TCP_PARAMS = 1, NL80211_WLAN_TCP_FILTER = 2, NL80211_WLAN_UDP_PARAMS = 3, NL80211_WLAN_UDP_FILTER = 4, }; #define WLAN_HB_UDP 0x1 #define WLAN_HB_TCP 0x2 struct wlan_hb_params { u16 cmd; u16 dummy; union { struct { u8 enable; u8 item; u8 session; } hb_params; struct { u32 srv_ip; u32 dev_ip; u32 seq; u16 src_port; u16 dst_port; u16 interval; u16 timeout; u8 session; u8 gateway_mac[ETH_ALEN]; } tcp_params; struct { u16 length; u8 offset; u8 session; u8 filter[64]; } tcp_filter; struct { u32 srv_ip; u32 dev_ip; u16 src_port; u16 dst_port; u16 interval; u16 timeout; u8 session; u8 gateway_mac[ETH_ALEN]; } udp_params; struct { u16 length; u8 offset; u8 session; u8 filter[64]; } udp_filter; } params; }; #endif #ifdef ATH6KL_SUPPORT_WIFI_DISC #define WLAN_WIFI_DISC_MAX_IE_SIZE 200 enum nl80211_wifi_disc_cmd { NL80211_WIFI_DISC_IE = 0, NL80211_WIFI_DISC_IE_FILTER = 1, NL80211_WIFI_DISC_START = 2, NL80211_WIFI_DISC_STOP = 3, }; struct wifi_disc_params { u16 cmd; union { struct { u16 length; u8 ie[WLAN_WIFI_DISC_MAX_IE_SIZE]; } ie_params; struct { u16 enable; u16 startPos; u16 length; u8 filter[WLAN_WIFI_DISC_MAX_IE_SIZE]; } ie_filter_params; struct { u16 channel; u16 dwellTime; u16 sleepTime; u16 random; u16 numPeers; u16 peerTimeout; u16 txPower; } start_params; } params; }; #endif #ifdef ATH6KL_SUPPORT_WIFI_KTK #define WLAN_WIFI_KTK_MAX_IE_SIZE 200 enum nl80211_wifi_ktk_cmd { NL80211_WIFI_KTK_IE = 0, NL80211_WIFI_KTK_IE_FILTER = 1, NL80211_WIFI_KTK_START = 2, NL80211_WIFI_KTK_STOP = 3, }; struct wifi_ktk_params { u16 cmd; union { struct { u16 length; u8 ie[WLAN_WIFI_KTK_MAX_IE_SIZE]; } ie_params; struct { u16 enable; u16 startPos; u16 length; u8 filter[WLAN_WIFI_KTK_MAX_IE_SIZE]; } ie_filter_params; struct { u16 ssid_len; u8 ssid[32]; u8 passphrase[16]; } start_params; } params; }; #endif struct wifi_dfs_skip_params { u16 enable; }; int ath6kl_tm_cmd(struct wiphy *wiphy, void *data, int len) { struct ath6kl *ar = wiphy_priv(wiphy); struct nlattr *tb[ATH6KL_TM_ATTR_MAX + 1]; int err, buf_len; void *buf; err = nla_parse(tb, ATH6KL_TM_ATTR_MAX, data, len, ath6kl_tm_policy); if (err) return err; if (!tb[ATH6KL_TM_ATTR_CMD]) return -EINVAL; switch (nla_get_u32(tb[ATH6KL_TM_ATTR_CMD])) { case ATH6KL_TM_CMD_TCMD: if (!tb[ATH6KL_TM_ATTR_DATA]) return -EINVAL; buf = nla_data(tb[ATH6KL_TM_ATTR_DATA]); buf_len = nla_len(tb[ATH6KL_TM_ATTR_DATA]); ath6kl_wmi_test_cmd(ar->wmi, buf, buf_len); return 0; break; #ifdef ATH6KL_SUPPORT_WLAN_HB case ATH6KL_TM_CMD_WLAN_HB: { struct wlan_hb_params *hb_params; struct ath6kl_vif *vif; vif = ath6kl_vif_first(ar); if (!vif) return -EINVAL; if (!tb[ATH6KL_TM_ATTR_DATA]) { printk(KERN_ERR "%s: NO DATA\n", __func__); return -EINVAL; } buf = nla_data(tb[ATH6KL_TM_ATTR_DATA]); buf_len = nla_len(tb[ATH6KL_TM_ATTR_DATA]); hb_params = (struct wlan_hb_params *)buf; if (hb_params->cmd == NL80211_WLAN_HB_ENABLE) { if (hb_params->params.hb_params.enable != 0) { if (ath6kl_enable_wow_hb(ar)) { printk(KERN_ERR "%s: enable hb wow fail\n", __func__); return -EINVAL; } if (hb_params->params.hb_params.item == WLAN_HB_TCP) { if (ath6kl_wmi_set_heart_beat_params( ar->wmi, vif->fw_vif_idx, 1, WLAN_HB_TCP, hb_params->params.hb_params.session)) { printk(KERN_ERR "%s: set heart beat enable fail\n", __func__); return -EINVAL; } } else if (hb_params->params.hb_params.item == WLAN_HB_UDP) { if (ath6kl_wmi_set_heart_beat_params( ar->wmi, vif->fw_vif_idx, 1, WLAN_HB_UDP, hb_params->params.hb_params.session)) { printk(KERN_ERR "%s: set heart beat enable fail\n", __func__); return -EINVAL; } } } else { #ifdef CONFIG_ANDROID if (ath6kl_android_enable_wow_default(ar)) { printk(KERN_ERR "%s: enable android defualt wow fail\n", __func__); } #endif if (hb_params->params.hb_params.item == WLAN_HB_TCP) { if (ath6kl_wmi_set_heart_beat_params( ar->wmi, vif->fw_vif_idx, 0, WLAN_HB_TCP, hb_params->params.hb_params.session)) { printk(KERN_ERR "%s: set heart beat disable fail\n", __func__); return -EINVAL; } } else if (hb_params->params.hb_params.item == WLAN_HB_UDP) { if (ath6kl_wmi_set_heart_beat_params( ar->wmi, vif->fw_vif_idx, 0, WLAN_HB_UDP, hb_params->params.hb_params.session)) { printk(KERN_ERR "%s: set heart beat disable fail\n", __func__); return -EINVAL; } } } } else if (hb_params->cmd == NL80211_WLAN_TCP_PARAMS) { if (ath6kl_wmi_heart_beat_set_tcp_params(ar->wmi, vif->fw_vif_idx, hb_params->params.tcp_params.src_port, hb_params->params.tcp_params.dst_port, hb_params->params.tcp_params.srv_ip, hb_params->params.tcp_params.dev_ip, hb_params->params.tcp_params.seq, hb_params->params.udp_params.interval, hb_params->params.tcp_params.timeout, hb_params->params.tcp_params.session, hb_params->params.tcp_params.gateway_mac)) { printk(KERN_ERR "%s: set heart beat tcp params fail\n", __func__); return -EINVAL; } } else if (hb_params->cmd == NL80211_WLAN_TCP_FILTER) { if (hb_params->params.tcp_filter.length > WMI_MAX_TCP_FILTER_SIZE) { printk(KERN_ERR "%s: size of tcp filter is too large: %d\n", __func__, hb_params->params.tcp_filter.length); return -E2BIG; } if (ath6kl_wmi_heart_beat_set_tcp_filter(ar->wmi, vif->fw_vif_idx, hb_params->params.tcp_filter.filter, hb_params->params.tcp_filter.length, hb_params->params.tcp_filter.offset, hb_params->params.tcp_filter.session)) { printk(KERN_ERR "%s: set heart beat tcp filter fail\n", __func__); return -EINVAL; } } else if (hb_params->cmd == NL80211_WLAN_UDP_PARAMS) { if (ath6kl_wmi_heart_beat_set_udp_params(ar->wmi, vif->fw_vif_idx, hb_params->params.udp_params.src_port, hb_params->params.udp_params.dst_port, hb_params->params.udp_params.srv_ip, hb_params->params.udp_params.dev_ip, hb_params->params.udp_params.interval, hb_params->params.udp_params.timeout, hb_params->params.udp_params.session, hb_params->params.udp_params.gateway_mac)) { printk(KERN_ERR "%s: set heart beat udp params fail\n", __func__); return -EINVAL; } } else if (hb_params->cmd == NL80211_WLAN_UDP_FILTER) { if (hb_params->params.udp_filter.length > WMI_MAX_UDP_FILTER_SIZE) { printk(KERN_ERR "%s: size of udp filter is too large: %d\n", __func__, hb_params->params.udp_filter.length); return -E2BIG; } if (ath6kl_wmi_heart_beat_set_udp_filter(ar->wmi, vif->fw_vif_idx, hb_params->params.udp_filter.filter, hb_params->params.udp_filter.length, hb_params->params.udp_filter.offset, hb_params->params.udp_filter.session)) { printk(KERN_ERR "%s: set heart beat udp filter fail\n", __func__); return -EINVAL; } } } return 0; break; #endif #ifdef ATH6KL_SUPPORT_WIFI_DISC case ATH6KL_TM_CMD_WIFI_DISC: { struct wifi_disc_params *disc_params; struct ath6kl_vif *vif; vif = ath6kl_vif_first(ar); if (!vif) return -EINVAL; if (!tb[ATH6KL_TM_ATTR_DATA]) { printk(KERN_ERR "%s: NO DATA\n", __func__); return -EINVAL; } buf = nla_data(tb[ATH6KL_TM_ATTR_DATA]); buf_len = nla_len(tb[ATH6KL_TM_ATTR_DATA]); disc_params = (struct wifi_disc_params *)buf; if (disc_params->cmd == NL80211_WIFI_DISC_IE) { u8 ie_hdr[6] = {0xDD, 0x00, 0x00, 0x03, 0x7f, 0x00}; u8 *ie = NULL; u16 ie_length = disc_params->params.ie_params.length; ie = kmalloc(ie_length+6, GFP_KERNEL); if (ie == NULL) return -ENOMEM; memcpy(ie, ie_hdr, 6); ie[1] = ie_length+4; memcpy(ie+6, disc_params->params.ie_params.ie, ie_length); if (ath6kl_wmi_set_appie_cmd(ar->wmi, vif->fw_vif_idx, WMI_FRAME_PROBE_REQ, ie, ie_length+6)) { kfree(ie); printk(KERN_ERR "%s: wifi discovery set probe request ie fail\n", __func__); return -EINVAL; } if (ath6kl_wmi_set_appie_cmd(ar->wmi, vif->fw_vif_idx, WMI_FRAME_PROBE_RESP, ie, ie_length+6)) { kfree(ie); printk(KERN_ERR "%s: wifi discovery set probe response ie fail\n", __func__); return -EINVAL; } kfree(ie); } else if (disc_params->cmd == NL80211_WIFI_DISC_IE_FILTER) { if (ath6kl_wmi_disc_ie_cmd(ar->wmi, vif->fw_vif_idx, disc_params->params.ie_filter_params.enable, disc_params->params.ie_filter_params.startPos, disc_params->params.ie_filter_params.filter, disc_params->params.ie_filter_params.length)) { printk(KERN_ERR "%s: wifi discovery set ie filter fail\n", __func__); return -EINVAL; } } else if (disc_params->cmd == NL80211_WIFI_DISC_START) { int band, freq, numPeers, random; if (disc_params->params.start_params.channel <= 14) band = IEEE80211_BAND_2GHZ; else band = IEEE80211_BAND_5GHZ; freq = ieee80211_channel_to_frequency( disc_params->params.start_params.channel, band); if (!freq) { printk(KERN_ERR "%s: wifi discovery start channel %d error\n", __func__, disc_params->params.start_params.channel); return -EINVAL; } if (disc_params->params.start_params.numPeers == 0) numPeers = 1; else if (disc_params->params.start_params.numPeers > 4) numPeers = 4; else numPeers = disc_params->params.start_params.numPeers; random = (disc_params->params.start_params.random == 0) ? 100 : disc_params->params.start_params.random; if (disc_params->params.start_params.txPower) ath6kl_wmi_set_tx_pwr_cmd(ar->wmi, vif->fw_vif_idx, disc_params->params.start_params.txPower); /* disable scanning */ ath6kl_wmi_scanparams_cmd(ar->wmi, vif->fw_vif_idx, 0xFFFF, 0, 0, 0, 0, 0, 0, 0, 0, 0); if (ath6kl_wmi_disc_mode_cmd(ar->wmi, vif->fw_vif_idx, 1, freq, disc_params->params.start_params.dwellTime, disc_params->params.start_params.sleepTime, random, numPeers, disc_params->params.start_params.peerTimeout )) { printk(KERN_ERR "%s: wifi discovery start fail\n", __func__); return -EINVAL; } /* change disc state to active */ ar->disc_active = true; } else if (disc_params->cmd == NL80211_WIFI_DISC_STOP) { /* change disc state to inactive */ ar->disc_active = false; if (ath6kl_wmi_disc_mode_cmd(ar->wmi, vif->fw_vif_idx, 0, 0, 0, 0, 0, 0, 0)) { printk(KERN_ERR "%s: wifi discovery stop fail\n", __func__); return -EINVAL; } } } return 0; break; #endif #ifdef ATH6KL_SUPPORT_WIFI_KTK case ATH6KL_TM_CMD_WIFI_KTK: { struct wifi_ktk_params *ktk_params; struct ath6kl_vif *vif; vif = ath6kl_vif_first(ar); if (!vif) return -EINVAL; if (!tb[ATH6KL_TM_ATTR_DATA]) { printk(KERN_ERR "%s: NO DATA\n", __func__); return -EINVAL; } buf = nla_data(tb[ATH6KL_TM_ATTR_DATA]); buf_len = nla_len(tb[ATH6KL_TM_ATTR_DATA]); ktk_params = (struct wifi_ktk_params *)buf; if (ktk_params->cmd == NL80211_WIFI_KTK_IE) { u8 ie_hdr[6] = {0xDD, 0x00, 0x00, 0x03, 0x7f, 0x00}; u8 *ie = NULL; u16 ie_length = ktk_params->params.ie_params.length; ie = kmalloc(ie_length+6, GFP_KERNEL); if (ie == NULL) return -ENOMEM; memcpy(ie, ie_hdr, 6); ie[1] = ie_length+4; memcpy(ie+6, ktk_params->params.ie_params.ie, ie_length); if (ath6kl_wmi_set_appie_cmd(ar->wmi, vif->fw_vif_idx, WMI_FRAME_PROBE_RESP, ie, ie_length+6)) { kfree(ie); printk(KERN_ERR "%s: wifi ktk set probe response ie fail\n", __func__); return -EINVAL; } if (ath6kl_wmi_set_appie_cmd(ar->wmi, vif->fw_vif_idx, WMI_FRAME_BEACON, ie, ie_length+6)) { kfree(ie); printk(KERN_ERR "%s: wifi ktk set beacon ie fail\n", __func__); return -EINVAL; } kfree(ie); } else if (ktk_params->cmd == NL80211_WIFI_KTK_IE_FILTER) { if (ath6kl_wmi_disc_ie_cmd(ar->wmi, vif->fw_vif_idx, ktk_params->params.ie_filter_params.enable, ktk_params->params.ie_filter_params.startPos, ktk_params->params.ie_filter_params.filter, ktk_params->params.ie_filter_params.length)) { printk(KERN_ERR "%s: wifi ktk set ie filter fail\n", __func__); return -EINVAL; } } else if (ktk_params->cmd == NL80211_WIFI_KTK_START) { ar->ktk_active = true; /* Clear the legacy ie pattern and filter */ if (ath6kl_wmi_disc_ie_cmd(ar->wmi, vif->fw_vif_idx, 0, 0, NULL, 0)) { printk(KERN_ERR "%s: wifi ktk clear ie filter fail\n", __func__); return -EINVAL; } memcpy(ar->ktk_passphrase, ktk_params->params.start_params.passphrase, 16); if (ath6kl_wmi_probedssid_cmd(ar->wmi, vif->fw_vif_idx, 1, SPECIFIC_SSID_FLAG, ktk_params->params.start_params.ssid_len, ktk_params->params.start_params.ssid)) { printk(KERN_ERR "%s: wifi ktk set probedssid fail\n", __func__); return -EINVAL; } if (ath6kl_wmi_ibss_pm_caps_cmd(ar->wmi, vif->fw_vif_idx, ADHOC_PS_KTK, 5, 10, 10)) { printk(KERN_ERR "%s: wifi ktk set power save mode on fail\n", __func__); return -EINVAL; } } else if (ktk_params->cmd == NL80211_WIFI_KTK_STOP) { ar->ktk_active = false; if (ath6kl_wmi_ibss_pm_caps_cmd(ar->wmi, vif->fw_vif_idx, ADHOC_PS_DISABLE, 0, 0, 0)) { printk(KERN_ERR "%s: wifi ktk set power save mode off fail\n", __func__); return -EINVAL; } } } return 0; break; #endif case ATH6KL_TM_CMD_DFS_SKIP: { struct wifi_dfs_skip_params *dfs_skip_params; struct ath6kl_vif *vif; vif = ath6kl_vif_first(ar); if (!vif) return -EINVAL; if (!tb[ATH6KL_TM_ATTR_DATA]) { printk(KERN_ERR "%s: NO DATA\n", __func__); return -EINVAL; } buf = nla_data(tb[ATH6KL_TM_ATTR_DATA]); buf_len = nla_len(tb[ATH6KL_TM_ATTR_DATA]); dfs_skip_params = (struct wifi_dfs_skip_params *)buf; if (dfs_skip_params->enable) vif->sc_params.scan_ctrl_flags |= ENABLE_DFS_SKIP_CTRL_FLAGS; else vif->sc_params.scan_ctrl_flags &= ~ENABLE_DFS_SKIP_CTRL_FLAGS; if (ath6kl_wmi_scanparams_cmd(ar->wmi, vif->fw_vif_idx, vif->sc_params.fg_start_period, vif->sc_params.fg_end_period, vif->sc_params.bg_period, vif->sc_params.minact_chdwell_time, vif->sc_params.maxact_chdwell_time, vif->sc_params.pas_chdwell_time, vif->sc_params.short_scan_ratio, vif->sc_params.scan_ctrl_flags, vif->sc_params.max_dfsch_act_time, vif->sc_params.maxact_scan_per_ssid)) { printk(KERN_ERR "%s: wifi dfs skip enable fail\n", __func__); return -EINVAL; } } return 0; break; default: return -EOPNOTSUPP; } } #endif /* CONFIG_NL80211_TESTMODE */