Commit 10509f90 authored by Kalle Valo's avatar Kalle Valo

ath6kl: implement scheduled scan

ath6kl firmware supports scheduled scan functionality with the wow ssid
filter. But the firmware does not send any events after scan results
so I had to add a timer which notifies about new scan results.

Sched scan needs firmware version 3.2.0.6 or later. If firmware doesn't
support sched scan the driver will not enable the feature.
Signed-off-by: default avatarKalle Valo <kvalo@qca.qualcomm.com>
parent 277d90f4
...@@ -123,6 +123,37 @@ static struct ieee80211_supported_band ath6kl_band_5ghz = { ...@@ -123,6 +123,37 @@ static struct ieee80211_supported_band ath6kl_band_5ghz = {
#define CCKM_KRK_CIPHER_SUITE 0x004096ff /* use for KRK */ #define CCKM_KRK_CIPHER_SUITE 0x004096ff /* use for KRK */
/* returns true if scheduled scan was stopped */
static bool __ath6kl_cfg80211_sscan_stop(struct ath6kl_vif *vif)
{
struct ath6kl *ar = vif->ar;
if (ar->state != ATH6KL_STATE_SCHED_SCAN)
return false;
del_timer_sync(&vif->sched_scan_timer);
ath6kl_wmi_set_host_sleep_mode_cmd(ar->wmi, vif->fw_vif_idx,
ATH6KL_HOST_MODE_AWAKE);
ar->state = ATH6KL_STATE_ON;
return true;
}
static void ath6kl_cfg80211_sscan_disable(struct ath6kl_vif *vif)
{
struct ath6kl *ar = vif->ar;
bool stopped;
stopped = __ath6kl_cfg80211_sscan_stop(vif);
if (!stopped)
return;
cfg80211_sched_scan_stopped(ar->wiphy);
}
static int ath6kl_set_wpa_version(struct ath6kl_vif *vif, static int ath6kl_set_wpa_version(struct ath6kl_vif *vif,
enum nl80211_wpa_versions wpa_version) enum nl80211_wpa_versions wpa_version)
{ {
...@@ -383,6 +414,8 @@ static int ath6kl_cfg80211_connect(struct wiphy *wiphy, struct net_device *dev, ...@@ -383,6 +414,8 @@ static int ath6kl_cfg80211_connect(struct wiphy *wiphy, struct net_device *dev,
struct ath6kl_vif *vif = netdev_priv(dev); struct ath6kl_vif *vif = netdev_priv(dev);
int status; int status;
ath6kl_cfg80211_sscan_disable(vif);
vif->sme_state = SME_CONNECTING; vif->sme_state = SME_CONNECTING;
if (!ath6kl_cfg80211_ready(vif)) if (!ath6kl_cfg80211_ready(vif))
...@@ -710,6 +743,8 @@ static int ath6kl_cfg80211_disconnect(struct wiphy *wiphy, ...@@ -710,6 +743,8 @@ static int ath6kl_cfg80211_disconnect(struct wiphy *wiphy,
ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, "%s: reason=%u\n", __func__, ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, "%s: reason=%u\n", __func__,
reason_code); reason_code);
ath6kl_cfg80211_sscan_disable(vif);
if (!ath6kl_cfg80211_ready(vif)) if (!ath6kl_cfg80211_ready(vif))
return -EIO; return -EIO;
...@@ -812,6 +847,8 @@ static int ath6kl_cfg80211_scan(struct wiphy *wiphy, struct net_device *ndev, ...@@ -812,6 +847,8 @@ static int ath6kl_cfg80211_scan(struct wiphy *wiphy, struct net_device *ndev,
if (!ath6kl_cfg80211_ready(vif)) if (!ath6kl_cfg80211_ready(vif))
return -EIO; return -EIO;
ath6kl_cfg80211_sscan_disable(vif);
if (!ar->usr_bss_filter) { if (!ar->usr_bss_filter) {
clear_bit(CLEAR_BSSFILTER_ON_BEACON, &vif->flags); clear_bit(CLEAR_BSSFILTER_ON_BEACON, &vif->flags);
ret = ath6kl_wmi_bssfilter_cmd( ret = ath6kl_wmi_bssfilter_cmd(
...@@ -837,6 +874,10 @@ static int ath6kl_cfg80211_scan(struct wiphy *wiphy, struct net_device *ndev, ...@@ -837,6 +874,10 @@ static int ath6kl_cfg80211_scan(struct wiphy *wiphy, struct net_device *ndev,
request->ssids[i].ssid); request->ssids[i].ssid);
} }
/*
* FIXME: we should clear the IE in fw if it's not set so just
* remove the check altogether
*/
if (request->ie) { if (request->ie) {
ret = ath6kl_wmi_set_appie_cmd(ar->wmi, vif->fw_vif_idx, ret = ath6kl_wmi_set_appie_cmd(ar->wmi, vif->fw_vif_idx,
WMI_FRAME_PROBE_REQ, WMI_FRAME_PROBE_REQ,
...@@ -1835,6 +1876,13 @@ int ath6kl_cfg80211_suspend(struct ath6kl *ar, ...@@ -1835,6 +1876,13 @@ int ath6kl_cfg80211_suspend(struct ath6kl *ar,
break; break;
case ATH6KL_CFG_SUSPEND_SCHED_SCAN:
/*
* Nothing needed for schedule scan, firmware is already in
* wow mode and sleeping most of the time.
*/
break;
default: default:
break; break;
} }
...@@ -1883,6 +1931,9 @@ int ath6kl_cfg80211_resume(struct ath6kl *ar) ...@@ -1883,6 +1931,9 @@ int ath6kl_cfg80211_resume(struct ath6kl *ar)
} }
break; break;
case ATH6KL_STATE_SCHED_SCAN:
break;
default: default:
break; break;
} }
...@@ -2329,6 +2380,90 @@ static void ath6kl_mgmt_frame_register(struct wiphy *wiphy, ...@@ -2329,6 +2380,90 @@ static void ath6kl_mgmt_frame_register(struct wiphy *wiphy,
} }
} }
static int ath6kl_cfg80211_sscan_start(struct wiphy *wiphy,
struct net_device *dev,
struct cfg80211_sched_scan_request *request)
{
struct ath6kl *ar = ath6kl_priv(dev);
struct ath6kl_vif *vif = netdev_priv(dev);
u16 interval;
int ret;
u8 i;
if (ar->state != ATH6KL_STATE_ON)
return -EIO;
if (vif->sme_state != SME_DISCONNECTED)
return -EBUSY;
for (i = 0; i < ar->wiphy->max_sched_scan_ssids; i++) {
ath6kl_wmi_probedssid_cmd(ar->wmi, vif->fw_vif_idx,
i, DISABLE_SSID_FLAG,
0, NULL);
}
/* fw uses seconds, also make sure that it's >0 */
interval = max_t(u16, 1, request->interval / 1000);
ath6kl_wmi_scanparams_cmd(ar->wmi, vif->fw_vif_idx,
interval, interval,
10, 0, 0, 0, 3, 0, 0, 0);
if (request->n_ssids && request->ssids[0].ssid_len) {
for (i = 0; i < request->n_ssids; i++) {
ath6kl_wmi_probedssid_cmd(ar->wmi, vif->fw_vif_idx,
i, SPECIFIC_SSID_FLAG,
request->ssids[i].ssid_len,
request->ssids[i].ssid);
}
}
ret = ath6kl_wmi_set_wow_mode_cmd(ar->wmi, vif->fw_vif_idx,
ATH6KL_WOW_MODE_ENABLE,
WOW_FILTER_SSID,
WOW_HOST_REQ_DELAY);
if (ret) {
ath6kl_warn("Failed to enable wow with ssid filter: %d\n", ret);
return ret;
}
/* this also clears IE in fw if it's not set */
ret = ath6kl_wmi_set_appie_cmd(ar->wmi, vif->fw_vif_idx,
WMI_FRAME_PROBE_REQ,
request->ie, request->ie_len);
if (ret) {
ath6kl_warn("Failed to set probe request IE for scheduled scan: %d",
ret);
return ret;
}
ret = ath6kl_wmi_set_host_sleep_mode_cmd(ar->wmi, vif->fw_vif_idx,
ATH6KL_HOST_MODE_ASLEEP);
if (ret) {
ath6kl_warn("Failed to enable host sleep mode for sched scan: %d\n",
ret);
return ret;
}
ar->state = ATH6KL_STATE_SCHED_SCAN;
return ret;
}
static int ath6kl_cfg80211_sscan_stop(struct wiphy *wiphy,
struct net_device *dev)
{
struct ath6kl_vif *vif = netdev_priv(dev);
bool stopped;
stopped = __ath6kl_cfg80211_sscan_stop(vif);
if (!stopped)
return -EIO;
return 0;
}
static const struct ieee80211_txrx_stypes static const struct ieee80211_txrx_stypes
ath6kl_mgmt_stypes[NUM_NL80211_IFTYPES] = { ath6kl_mgmt_stypes[NUM_NL80211_IFTYPES] = {
[NL80211_IFTYPE_STATION] = { [NL80211_IFTYPE_STATION] = {
...@@ -2386,10 +2521,14 @@ static struct cfg80211_ops ath6kl_cfg80211_ops = { ...@@ -2386,10 +2521,14 @@ static struct cfg80211_ops ath6kl_cfg80211_ops = {
.cancel_remain_on_channel = ath6kl_cancel_remain_on_channel, .cancel_remain_on_channel = ath6kl_cancel_remain_on_channel,
.mgmt_tx = ath6kl_mgmt_tx, .mgmt_tx = ath6kl_mgmt_tx,
.mgmt_frame_register = ath6kl_mgmt_frame_register, .mgmt_frame_register = ath6kl_mgmt_frame_register,
.sched_scan_start = ath6kl_cfg80211_sscan_start,
.sched_scan_stop = ath6kl_cfg80211_sscan_stop,
}; };
void ath6kl_cfg80211_stop(struct ath6kl_vif *vif) void ath6kl_cfg80211_stop(struct ath6kl_vif *vif)
{ {
ath6kl_cfg80211_sscan_disable(vif);
switch (vif->sme_state) { switch (vif->sme_state) {
case SME_DISCONNECTED: case SME_DISCONNECTED:
break; break;
...@@ -2546,6 +2685,8 @@ int ath6kl_register_ieee80211_hw(struct ath6kl *ar) ...@@ -2546,6 +2685,8 @@ int ath6kl_register_ieee80211_hw(struct ath6kl *ar)
wiphy->wowlan.pattern_min_len = 1; wiphy->wowlan.pattern_min_len = 1;
wiphy->wowlan.pattern_max_len = WOW_PATTERN_SIZE; wiphy->wowlan.pattern_max_len = WOW_PATTERN_SIZE;
wiphy->max_sched_scan_ssids = 10;
ret = wiphy_register(wiphy); ret = wiphy_register(wiphy);
if (ret < 0) { if (ret < 0) {
ath6kl_err("couldn't register wiphy device\n"); ath6kl_err("couldn't register wiphy device\n");
...@@ -2565,6 +2706,9 @@ static int ath6kl_init_if_data(struct ath6kl_vif *vif) ...@@ -2565,6 +2706,9 @@ static int ath6kl_init_if_data(struct ath6kl_vif *vif)
setup_timer(&vif->disconnect_timer, disconnect_timer_handler, setup_timer(&vif->disconnect_timer, disconnect_timer_handler,
(unsigned long) vif->ndev); (unsigned long) vif->ndev);
setup_timer(&vif->sched_scan_timer, ath6kl_wmi_sscan_timer,
(unsigned long) vif);
set_bit(WMM_ENABLED, &vif->flags); set_bit(WMM_ENABLED, &vif->flags);
spin_lock_init(&vif->if_lock); spin_lock_init(&vif->if_lock);
......
...@@ -20,7 +20,8 @@ ...@@ -20,7 +20,8 @@
enum ath6kl_cfg_suspend_mode { enum ath6kl_cfg_suspend_mode {
ATH6KL_CFG_SUSPEND_DEEPSLEEP, ATH6KL_CFG_SUSPEND_DEEPSLEEP,
ATH6KL_CFG_SUSPEND_CUTPOWER, ATH6KL_CFG_SUSPEND_CUTPOWER,
ATH6KL_CFG_SUSPEND_WOW ATH6KL_CFG_SUSPEND_WOW,
ATH6KL_CFG_SUSPEND_SCHED_SCAN,
}; };
struct net_device *ath6kl_interface_add(struct ath6kl *ar, char *name, struct net_device *ath6kl_interface_add(struct ath6kl *ar, char *name,
......
...@@ -76,6 +76,7 @@ enum ath6kl_fw_ie_type { ...@@ -76,6 +76,7 @@ enum ath6kl_fw_ie_type {
enum ath6kl_fw_capability { enum ath6kl_fw_capability {
ATH6KL_FW_CAPABILITY_HOST_P2P = 0, ATH6KL_FW_CAPABILITY_HOST_P2P = 0,
ATH6KL_FW_CAPABILITY_SCHED_SCAN = 1,
/* this needs to be last */ /* this needs to be last */
ATH6KL_FW_CAPABILITY_MAX, ATH6KL_FW_CAPABILITY_MAX,
...@@ -447,7 +448,10 @@ struct ath6kl_vif { ...@@ -447,7 +448,10 @@ struct ath6kl_vif {
struct ath6kl_wep_key wep_key_list[WMI_MAX_KEY_INDEX + 1]; struct ath6kl_wep_key wep_key_list[WMI_MAX_KEY_INDEX + 1];
struct ath6kl_key keys[WMI_MAX_KEY_INDEX + 1]; struct ath6kl_key keys[WMI_MAX_KEY_INDEX + 1];
struct aggr_info *aggr_cntxt; struct aggr_info *aggr_cntxt;
struct timer_list disconnect_timer; struct timer_list disconnect_timer;
struct timer_list sched_scan_timer;
struct cfg80211_scan_request *scan_req; struct cfg80211_scan_request *scan_req;
enum sme_state sme_state; enum sme_state sme_state;
int reconnect_flag; int reconnect_flag;
...@@ -465,6 +469,8 @@ struct ath6kl_vif { ...@@ -465,6 +469,8 @@ struct ath6kl_vif {
#define WOW_LIST_ID 0 #define WOW_LIST_ID 0
#define WOW_HOST_REQ_DELAY 500 /* ms */ #define WOW_HOST_REQ_DELAY 500 /* ms */
#define ATH6KL_SCHED_SCAN_RESULT_DELAY 5000 /* ms */
/* Flag info */ /* Flag info */
enum ath6kl_dev_state { enum ath6kl_dev_state {
WMI_ENABLED, WMI_ENABLED,
...@@ -483,6 +489,7 @@ enum ath6kl_state { ...@@ -483,6 +489,7 @@ enum ath6kl_state {
ATH6KL_STATE_DEEPSLEEP, ATH6KL_STATE_DEEPSLEEP,
ATH6KL_STATE_CUTPOWER, ATH6KL_STATE_CUTPOWER,
ATH6KL_STATE_WOW, ATH6KL_STATE_WOW,
ATH6KL_STATE_SCHED_SCAN,
}; };
struct ath6kl { struct ath6kl {
......
...@@ -1644,6 +1644,9 @@ int ath6kl_core_init(struct ath6kl *ar) ...@@ -1644,6 +1644,9 @@ int ath6kl_core_init(struct ath6kl *ar)
WIPHY_FLAG_HAVE_AP_SME | WIPHY_FLAG_HAVE_AP_SME |
WIPHY_FLAG_AP_PROBE_RESP_OFFLOAD; WIPHY_FLAG_AP_PROBE_RESP_OFFLOAD;
if (test_bit(ATH6KL_FW_CAPABILITY_SCHED_SCAN, ar->fw_capabilities))
ar->wiphy->flags |= WIPHY_FLAG_SUPPORTS_SCHED_SCAN;
ar->wiphy->probe_resp_offload = ar->wiphy->probe_resp_offload =
NL80211_PROBE_RESP_OFFLOAD_SUPPORT_WPS | NL80211_PROBE_RESP_OFFLOAD_SUPPORT_WPS |
NL80211_PROBE_RESP_OFFLOAD_SUPPORT_WPS2 | NL80211_PROBE_RESP_OFFLOAD_SUPPORT_WPS2 |
......
...@@ -806,7 +806,28 @@ static int ath6kl_sdio_suspend(struct ath6kl *ar, struct cfg80211_wowlan *wow) ...@@ -806,7 +806,28 @@ static int ath6kl_sdio_suspend(struct ath6kl *ar, struct cfg80211_wowlan *wow)
return ret; return ret;
} }
if ((flags & MMC_PM_WAKE_SDIO_IRQ) && wow) { if (!(flags & MMC_PM_WAKE_SDIO_IRQ))
goto deepsleep;
/* sdio irq wakes up host */
if (ar->state == ATH6KL_STATE_SCHED_SCAN) {
ret = ath6kl_cfg80211_suspend(ar,
ATH6KL_CFG_SUSPEND_SCHED_SCAN,
NULL);
if (ret) {
ath6kl_warn("Schedule scan suspend failed: %d", ret);
return ret;
}
ret = sdio_set_host_pm_flags(func, MMC_PM_WAKE_SDIO_IRQ);
if (ret)
ath6kl_warn("set sdio wake irq flag failed: %d\n", ret);
return ret;
}
if (wow) {
/* /*
* The host sdio controller is capable of keep power and * The host sdio controller is capable of keep power and
* sdio irq wake up at this point. It's fine to continue * sdio irq wake up at this point. It's fine to continue
...@@ -823,6 +844,7 @@ static int ath6kl_sdio_suspend(struct ath6kl *ar, struct cfg80211_wowlan *wow) ...@@ -823,6 +844,7 @@ static int ath6kl_sdio_suspend(struct ath6kl *ar, struct cfg80211_wowlan *wow)
return ret; return ret;
} }
deepsleep:
return ath6kl_cfg80211_suspend(ar, ATH6KL_CFG_SUSPEND_DEEPSLEEP, NULL); return ath6kl_cfg80211_suspend(ar, ATH6KL_CFG_SUSPEND_DEEPSLEEP, NULL);
} }
...@@ -846,6 +868,8 @@ static int ath6kl_sdio_resume(struct ath6kl *ar) ...@@ -846,6 +868,8 @@ static int ath6kl_sdio_resume(struct ath6kl *ar)
case ATH6KL_STATE_WOW: case ATH6KL_STATE_WOW:
break; break;
case ATH6KL_STATE_SCHED_SCAN:
break;
} }
ath6kl_cfg80211_resume(ar); ath6kl_cfg80211_resume(ar);
......
...@@ -977,6 +977,13 @@ static int ath6kl_wmi_tkip_micerr_event_rx(struct wmi *wmi, u8 *datap, int len, ...@@ -977,6 +977,13 @@ static int ath6kl_wmi_tkip_micerr_event_rx(struct wmi *wmi, u8 *datap, int len,
return 0; return 0;
} }
void ath6kl_wmi_sscan_timer(unsigned long ptr)
{
struct ath6kl_vif *vif = (struct ath6kl_vif *) ptr;
cfg80211_sched_scan_results(vif->ar->wiphy);
}
static int ath6kl_wmi_bssinfo_event_rx(struct wmi *wmi, u8 *datap, int len, static int ath6kl_wmi_bssinfo_event_rx(struct wmi *wmi, u8 *datap, int len,
struct ath6kl_vif *vif) struct ath6kl_vif *vif)
{ {
...@@ -1066,6 +1073,21 @@ static int ath6kl_wmi_bssinfo_event_rx(struct wmi *wmi, u8 *datap, int len, ...@@ -1066,6 +1073,21 @@ static int ath6kl_wmi_bssinfo_event_rx(struct wmi *wmi, u8 *datap, int len,
return -ENOMEM; return -ENOMEM;
cfg80211_put_bss(bss); cfg80211_put_bss(bss);
/*
* Firmware doesn't return any event when scheduled scan has
* finished, so we need to use a timer to find out when there are
* no more results.
*
* The timer is started from the first bss info received, otherwise
* the timer would not ever fire if the scan interval is short
* enough.
*/
if (ar->state == ATH6KL_STATE_SCHED_SCAN &&
!timer_pending(&vif->sched_scan_timer)) {
mod_timer(&vif->sched_scan_timer, jiffies +
msecs_to_jiffies(ATH6KL_SCHED_SCAN_RESULT_DELAY));
}
return 0; return 0;
} }
...@@ -2940,7 +2962,10 @@ int ath6kl_wmi_set_appie_cmd(struct wmi *wmi, u8 if_idx, u8 mgmt_frm_type, ...@@ -2940,7 +2962,10 @@ int ath6kl_wmi_set_appie_cmd(struct wmi *wmi, u8 if_idx, u8 mgmt_frm_type,
p = (struct wmi_set_appie_cmd *) skb->data; p = (struct wmi_set_appie_cmd *) skb->data;
p->mgmt_frm_type = mgmt_frm_type; p->mgmt_frm_type = mgmt_frm_type;
p->ie_len = ie_len; p->ie_len = ie_len;
if (ie != NULL && ie_len > 0)
memcpy(p->ie_info, ie, ie_len); memcpy(p->ie_info, ie, ie_len);
return ath6kl_wmi_cmd_send(wmi, if_idx, skb, WMI_SET_APPIE_CMDID, return ath6kl_wmi_cmd_send(wmi, if_idx, skb, WMI_SET_APPIE_CMDID,
NO_SYNC_WMIFLAG); NO_SYNC_WMIFLAG);
} }
......
...@@ -2349,6 +2349,8 @@ int ath6kl_wmi_cancel_remain_on_chnl_cmd(struct wmi *wmi, u8 if_idx); ...@@ -2349,6 +2349,8 @@ int ath6kl_wmi_cancel_remain_on_chnl_cmd(struct wmi *wmi, u8 if_idx);
int ath6kl_wmi_set_appie_cmd(struct wmi *wmi, u8 if_idx, u8 mgmt_frm_type, int ath6kl_wmi_set_appie_cmd(struct wmi *wmi, u8 if_idx, u8 mgmt_frm_type,
const u8 *ie, u8 ie_len); const u8 *ie, u8 ie_len);
void ath6kl_wmi_sscan_timer(unsigned long ptr);
struct ath6kl_vif *ath6kl_get_vif_by_index(struct ath6kl *ar, u8 if_idx); struct ath6kl_vif *ath6kl_get_vif_by_index(struct ath6kl *ar, u8 if_idx);
void *ath6kl_wmi_init(struct ath6kl *devt); void *ath6kl_wmi_init(struct ath6kl *devt);
void ath6kl_wmi_shutdown(struct wmi *wmi); void ath6kl_wmi_shutdown(struct wmi *wmi);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment