Skip to content

Commit

Permalink
ath6kl: implement scheduled scan
Browse files Browse the repository at this point in the history
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: Kalle Valo <kvalo@qca.qualcomm.com>
  • Loading branch information
Kalle Valo committed Dec 13, 2011
1 parent 277d90f commit 10509f9
Show file tree
Hide file tree
Showing 7 changed files with 209 additions and 3 deletions.
144 changes: 144 additions & 0 deletions drivers/net/wireless/ath/ath6kl/cfg80211.c
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,37 @@ static struct ieee80211_supported_band ath6kl_band_5ghz = {

#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,
enum nl80211_wpa_versions wpa_version)
{
Expand Down Expand Up @@ -383,6 +414,8 @@ static int ath6kl_cfg80211_connect(struct wiphy *wiphy, struct net_device *dev,
struct ath6kl_vif *vif = netdev_priv(dev);
int status;

ath6kl_cfg80211_sscan_disable(vif);

vif->sme_state = SME_CONNECTING;

if (!ath6kl_cfg80211_ready(vif))
Expand Down Expand Up @@ -710,6 +743,8 @@ static int ath6kl_cfg80211_disconnect(struct wiphy *wiphy,
ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, "%s: reason=%u\n", __func__,
reason_code);

ath6kl_cfg80211_sscan_disable(vif);

if (!ath6kl_cfg80211_ready(vif))
return -EIO;

Expand Down Expand Up @@ -812,6 +847,8 @@ static int ath6kl_cfg80211_scan(struct wiphy *wiphy, struct net_device *ndev,
if (!ath6kl_cfg80211_ready(vif))
return -EIO;

ath6kl_cfg80211_sscan_disable(vif);

if (!ar->usr_bss_filter) {
clear_bit(CLEAR_BSSFILTER_ON_BEACON, &vif->flags);
ret = ath6kl_wmi_bssfilter_cmd(
Expand All @@ -837,6 +874,10 @@ static int ath6kl_cfg80211_scan(struct wiphy *wiphy, struct net_device *ndev,
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) {
ret = ath6kl_wmi_set_appie_cmd(ar->wmi, vif->fw_vif_idx,
WMI_FRAME_PROBE_REQ,
Expand Down Expand Up @@ -1835,6 +1876,13 @@ int ath6kl_cfg80211_suspend(struct ath6kl *ar,

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:
break;
}
Expand Down Expand Up @@ -1883,6 +1931,9 @@ int ath6kl_cfg80211_resume(struct ath6kl *ar)
}
break;

case ATH6KL_STATE_SCHED_SCAN:
break;

default:
break;
}
Expand Down Expand Up @@ -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
ath6kl_mgmt_stypes[NUM_NL80211_IFTYPES] = {
[NL80211_IFTYPE_STATION] = {
Expand Down Expand Up @@ -2386,10 +2521,14 @@ static struct cfg80211_ops ath6kl_cfg80211_ops = {
.cancel_remain_on_channel = ath6kl_cancel_remain_on_channel,
.mgmt_tx = ath6kl_mgmt_tx,
.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)
{
ath6kl_cfg80211_sscan_disable(vif);

switch (vif->sme_state) {
case SME_DISCONNECTED:
break;
Expand Down Expand Up @@ -2546,6 +2685,8 @@ int ath6kl_register_ieee80211_hw(struct ath6kl *ar)
wiphy->wowlan.pattern_min_len = 1;
wiphy->wowlan.pattern_max_len = WOW_PATTERN_SIZE;

wiphy->max_sched_scan_ssids = 10;

ret = wiphy_register(wiphy);
if (ret < 0) {
ath6kl_err("couldn't register wiphy device\n");
Expand All @@ -2565,6 +2706,9 @@ static int ath6kl_init_if_data(struct ath6kl_vif *vif)

setup_timer(&vif->disconnect_timer, disconnect_timer_handler,
(unsigned long) vif->ndev);
setup_timer(&vif->sched_scan_timer, ath6kl_wmi_sscan_timer,
(unsigned long) vif);

set_bit(WMM_ENABLED, &vif->flags);
spin_lock_init(&vif->if_lock);

Expand Down
3 changes: 2 additions & 1 deletion drivers/net/wireless/ath/ath6kl/cfg80211.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@
enum ath6kl_cfg_suspend_mode {
ATH6KL_CFG_SUSPEND_DEEPSLEEP,
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,
Expand Down
7 changes: 7 additions & 0 deletions drivers/net/wireless/ath/ath6kl/core.h
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ enum ath6kl_fw_ie_type {

enum ath6kl_fw_capability {
ATH6KL_FW_CAPABILITY_HOST_P2P = 0,
ATH6KL_FW_CAPABILITY_SCHED_SCAN = 1,

/* this needs to be last */
ATH6KL_FW_CAPABILITY_MAX,
Expand Down Expand Up @@ -447,7 +448,10 @@ struct ath6kl_vif {
struct ath6kl_wep_key wep_key_list[WMI_MAX_KEY_INDEX + 1];
struct ath6kl_key keys[WMI_MAX_KEY_INDEX + 1];
struct aggr_info *aggr_cntxt;

struct timer_list disconnect_timer;
struct timer_list sched_scan_timer;

struct cfg80211_scan_request *scan_req;
enum sme_state sme_state;
int reconnect_flag;
Expand All @@ -465,6 +469,8 @@ struct ath6kl_vif {
#define WOW_LIST_ID 0
#define WOW_HOST_REQ_DELAY 500 /* ms */

#define ATH6KL_SCHED_SCAN_RESULT_DELAY 5000 /* ms */

/* Flag info */
enum ath6kl_dev_state {
WMI_ENABLED,
Expand All @@ -483,6 +489,7 @@ enum ath6kl_state {
ATH6KL_STATE_DEEPSLEEP,
ATH6KL_STATE_CUTPOWER,
ATH6KL_STATE_WOW,
ATH6KL_STATE_SCHED_SCAN,
};

struct ath6kl {
Expand Down
3 changes: 3 additions & 0 deletions drivers/net/wireless/ath/ath6kl/init.c
Original file line number Diff line number Diff line change
Expand Up @@ -1644,6 +1644,9 @@ int ath6kl_core_init(struct ath6kl *ar)
WIPHY_FLAG_HAVE_AP_SME |
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 =
NL80211_PROBE_RESP_OFFLOAD_SUPPORT_WPS |
NL80211_PROBE_RESP_OFFLOAD_SUPPORT_WPS2 |
Expand Down
26 changes: 25 additions & 1 deletion drivers/net/wireless/ath/ath6kl/sdio.c
Original file line number Diff line number Diff line change
Expand Up @@ -806,7 +806,28 @@ static int ath6kl_sdio_suspend(struct ath6kl *ar, struct cfg80211_wowlan *wow)
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
* sdio irq wake up at this point. It's fine to continue
Expand All @@ -823,6 +844,7 @@ static int ath6kl_sdio_suspend(struct ath6kl *ar, struct cfg80211_wowlan *wow)
return ret;
}

deepsleep:
return ath6kl_cfg80211_suspend(ar, ATH6KL_CFG_SUSPEND_DEEPSLEEP, NULL);
}

Expand All @@ -846,6 +868,8 @@ static int ath6kl_sdio_resume(struct ath6kl *ar)

case ATH6KL_STATE_WOW:
break;
case ATH6KL_STATE_SCHED_SCAN:
break;
}

ath6kl_cfg80211_resume(ar);
Expand Down
27 changes: 26 additions & 1 deletion drivers/net/wireless/ath/ath6kl/wmi.c
Original file line number Diff line number Diff line change
Expand Up @@ -977,6 +977,13 @@ static int ath6kl_wmi_tkip_micerr_event_rx(struct wmi *wmi, u8 *datap, int len,
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,
struct ath6kl_vif *vif)
{
Expand Down Expand Up @@ -1066,6 +1073,21 @@ static int ath6kl_wmi_bssinfo_event_rx(struct wmi *wmi, u8 *datap, int len,
return -ENOMEM;
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;
}

Expand Down Expand Up @@ -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->mgmt_frm_type = mgmt_frm_type;
p->ie_len = ie_len;
memcpy(p->ie_info, ie, ie_len);

if (ie != NULL && ie_len > 0)
memcpy(p->ie_info, ie, ie_len);

return ath6kl_wmi_cmd_send(wmi, if_idx, skb, WMI_SET_APPIE_CMDID,
NO_SYNC_WMIFLAG);
}
Expand Down
2 changes: 2 additions & 0 deletions drivers/net/wireless/ath/ath6kl/wmi.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
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);
void *ath6kl_wmi_init(struct ath6kl *devt);
void ath6kl_wmi_shutdown(struct wmi *wmi);
Expand Down

0 comments on commit 10509f9

Please sign in to comment.