brcmfmac: Add wowl net detect support
authorHante Meuleman <meuleman@broadcom.com>
Tue, 5 Jan 2016 10:05:45 +0000 (11:05 +0100)
committerKalle Valo <kvalo@codeaurora.org>
Fri, 8 Jan 2016 08:44:38 +0000 (10:44 +0200)
With wowl net detect it becomes possible to scan for specific ssids
and wakeup once found.

Reviewed-by: Arend Van Spriel <arend@broadcom.com>
Reviewed-by: Franky (Zhenhui) Lin <frankyl@broadcom.com>
Reviewed-by: Pieter-Paul Giesberts <pieterpg@broadcom.com>
Signed-off-by: Hante Meuleman <meuleman@broadcom.com>
Signed-off-by: Arend van Spriel <arend@broadcom.com>
Signed-off-by: Kalle Valo <kvalo@codeaurora.org>
drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c
drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.h
drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h

index 6a7759fcbd86d76794287b6ae018ebeefd69b6ac..fd54ad141965b68ebf7ab920e1fab827cbcc0876 100644 (file)
@@ -95,6 +95,8 @@
 #define BRCMF_SCAN_UNASSOC_TIME                40
 #define BRCMF_SCAN_PASSIVE_TIME                120
 
+#define BRCMF_ND_INFO_TIMEOUT          msecs_to_jiffies(2000)
+
 #define BRCMF_ASSOC_PARAMS_FIXED_SIZE \
        (sizeof(struct brcmf_assoc_params_le) - sizeof(u16))
 
@@ -236,6 +238,17 @@ struct parsed_vndr_ies {
        struct parsed_vndr_ie_info ie_info[VNDR_IE_PARSE_LIMIT];
 };
 
+/* Function prototype forward declarations */
+static int
+brcmf_cfg80211_sched_scan_start(struct wiphy *wiphy,
+                               struct net_device *ndev,
+                               struct cfg80211_sched_scan_request *request);
+static int brcmf_cfg80211_sched_scan_stop(struct wiphy *wiphy,
+                                         struct net_device *ndev);
+static s32
+brcmf_notify_sched_scan_results(struct brcmf_if *ifp,
+                               const struct brcmf_event_msg *e, void *data);
+
 
 static u16 chandef_to_chanspec(struct brcmu_d11inf *d11inf,
                               struct cfg80211_chan_def *ch)
@@ -3116,26 +3129,71 @@ static s32 brcmf_config_wowl_pattern(struct brcmf_if *ifp, u8 cmd[4],
        return ret;
 }
 
+static s32
+brcmf_wowl_nd_results(struct brcmf_if *ifp, const struct brcmf_event_msg *e,
+                     void *data)
+{
+       struct brcmf_cfg80211_info *cfg = ifp->drvr->config;
+       struct brcmf_pno_scanresults_le *pfn_result;
+       struct brcmf_pno_net_info_le *netinfo;
+
+       brcmf_dbg(SCAN, "Enter\n");
+
+       pfn_result = (struct brcmf_pno_scanresults_le *)data;
+
+       if (e->event_code == BRCMF_E_PFN_NET_LOST) {
+               brcmf_dbg(SCAN, "PFN NET LOST event. Ignore\n");
+               return 0;
+       }
+
+       if (le32_to_cpu(pfn_result->count) < 1) {
+               brcmf_err("Invalid result count, expected 1 (%d)\n",
+                         le32_to_cpu(pfn_result->count));
+               return -EINVAL;
+       }
+
+       data += sizeof(struct brcmf_pno_scanresults_le);
+       netinfo = (struct brcmf_pno_net_info_le *)data;
+       memcpy(cfg->wowl.nd->ssid.ssid, netinfo->SSID, netinfo->SSID_len);
+       cfg->wowl.nd->ssid.ssid_len = netinfo->SSID_len;
+       cfg->wowl.nd->n_channels = 1;
+       cfg->wowl.nd->channels[0] =
+               ieee80211_channel_to_frequency(netinfo->channel,
+                       netinfo->channel <= CH_MAX_2G_CHANNEL ?
+                                       NL80211_BAND_2GHZ : NL80211_BAND_5GHZ);
+       cfg->wowl.nd_info->n_matches = 1;
+       cfg->wowl.nd_info->matches[0] = cfg->wowl.nd;
+
+       /* Inform (the resume task) that the net detect information was recvd */
+       cfg->wowl.nd_data_completed = true;
+       wake_up(&cfg->wowl.nd_data_wait);
+
+       return 0;
+}
+
 #ifdef CONFIG_PM
 
 static void brcmf_report_wowl_wakeind(struct wiphy *wiphy, struct brcmf_if *ifp)
 {
+       struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy);
        struct brcmf_wowl_wakeind_le wake_ind_le;
        struct cfg80211_wowlan_wakeup wakeup_data;
        struct cfg80211_wowlan_wakeup *wakeup;
        u32 wakeind;
        s32 err;
+       int timeout;
 
        err = brcmf_fil_iovar_data_get(ifp, "wowl_wakeind", &wake_ind_le,
                                       sizeof(wake_ind_le));
-       if (!err) {
+       if (err) {
                brcmf_err("Get wowl_wakeind failed, err = %d\n", err);
                return;
        }
 
        wakeind = le32_to_cpu(wake_ind_le.ucode_wakeind);
        if (wakeind & (BRCMF_WOWL_MAGIC | BRCMF_WOWL_DIS | BRCMF_WOWL_BCN |
-                      BRCMF_WOWL_RETR | BRCMF_WOWL_NET)) {
+                      BRCMF_WOWL_RETR | BRCMF_WOWL_NET |
+                      BRCMF_WOWL_PFN_FOUND)) {
                wakeup = &wakeup_data;
                memset(&wakeup_data, 0, sizeof(wakeup_data));
                wakeup_data.pattern_idx = -1;
@@ -3163,6 +3221,16 @@ static void brcmf_report_wowl_wakeind(struct wiphy *wiphy, struct brcmf_if *ifp)
                         */
                        wakeup_data.pattern_idx = 0;
                }
+               if (wakeind & BRCMF_WOWL_PFN_FOUND) {
+                       brcmf_dbg(INFO, "WOWL Wake indicator: BRCMF_WOWL_PFN_FOUND\n");
+                       timeout = wait_event_timeout(cfg->wowl.nd_data_wait,
+                               cfg->wowl.nd_data_completed,
+                               BRCMF_ND_INFO_TIMEOUT);
+                       if (!timeout)
+                               brcmf_err("No result for wowl net detect\n");
+                       else
+                               wakeup_data.net_detect = cfg->wowl.nd_info;
+               }
        } else {
                wakeup = NULL;
        }
@@ -3185,14 +3253,21 @@ static s32 brcmf_cfg80211_resume(struct wiphy *wiphy)
 
        brcmf_dbg(TRACE, "Enter\n");
 
-       if (cfg->wowl_enabled) {
+       if (cfg->wowl.active) {
                brcmf_report_wowl_wakeind(wiphy, ifp);
                brcmf_fil_iovar_int_set(ifp, "wowl_clear", 0);
                brcmf_config_wowl_pattern(ifp, "clr", NULL, 0, NULL, 0);
                brcmf_configure_arp_offload(ifp, true);
                brcmf_fil_cmd_int_set(ifp, BRCMF_C_SET_PM,
-                                     cfg->pre_wowl_pmmode);
-               cfg->wowl_enabled = false;
+                                     cfg->wowl.pre_pmmode);
+               cfg->wowl.active = false;
+               if (cfg->wowl.nd_enabled) {
+                       brcmf_cfg80211_sched_scan_stop(cfg->wiphy, ifp->ndev);
+                       brcmf_fweh_unregister(cfg->pub, BRCMF_E_PFN_NET_FOUND);
+                       brcmf_fweh_register(cfg->pub, BRCMF_E_PFN_NET_FOUND,
+                                           brcmf_notify_sched_scan_results);
+                       cfg->wowl.nd_enabled = false;
+               }
        }
        return 0;
 }
@@ -3207,7 +3282,7 @@ static void brcmf_configure_wowl(struct brcmf_cfg80211_info *cfg,
        brcmf_dbg(TRACE, "Suspend, wowl config.\n");
 
        brcmf_configure_arp_offload(ifp, false);
-       brcmf_fil_cmd_int_get(ifp, BRCMF_C_GET_PM, &cfg->pre_wowl_pmmode);
+       brcmf_fil_cmd_int_get(ifp, BRCMF_C_GET_PM, &cfg->wowl.pre_pmmode);
        brcmf_fil_cmd_int_set(ifp, BRCMF_C_SET_PM, PM_MAX);
 
        wowl_config = 0;
@@ -3225,11 +3300,26 @@ static void brcmf_configure_wowl(struct brcmf_cfg80211_info *cfg,
                                wowl->patterns[i].pkt_offset);
                }
        }
+       if (wowl->nd_config) {
+               brcmf_cfg80211_sched_scan_start(cfg->wiphy, ifp->ndev,
+                                               wowl->nd_config);
+               wowl_config |= BRCMF_WOWL_PFN_FOUND;
+
+               cfg->wowl.nd_data_completed = false;
+               cfg->wowl.nd_enabled = true;
+               /* Now reroute the event for PFN to the wowl function. */
+               brcmf_fweh_unregister(cfg->pub, BRCMF_E_PFN_NET_FOUND);
+               brcmf_fweh_register(cfg->pub, BRCMF_E_PFN_NET_FOUND,
+                                   brcmf_wowl_nd_results);
+       }
+       if (!test_bit(BRCMF_VIF_STATUS_CONNECTED, &ifp->vif->sme_state))
+               wowl_config |= BRCMF_WOWL_UNASSOC;
+
        brcmf_fil_iovar_data_set(ifp, "wowl_wakeind", "clear", strlen("clear"));
        brcmf_fil_iovar_int_set(ifp, "wowl", wowl_config);
        brcmf_fil_iovar_int_set(ifp, "wowl_activate", 1);
        brcmf_bus_wowl_config(cfg->pub->bus_if, true);
-       cfg->wowl_enabled = true;
+       cfg->wowl.active = true;
 }
 
 static s32 brcmf_cfg80211_suspend(struct wiphy *wiphy,
@@ -3248,6 +3338,10 @@ static s32 brcmf_cfg80211_suspend(struct wiphy *wiphy,
        if (!check_vif_up(ifp->vif))
                goto exit;
 
+       /* Stop scheduled scan */
+       if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_PNO))
+               brcmf_cfg80211_sched_scan_stop(wiphy, ndev);
+
        /* end any scanning */
        if (test_bit(BRCMF_SCAN_STATUS_BUSY, &cfg->scan_status))
                brcmf_abort_scanning(cfg);
@@ -5329,6 +5423,10 @@ static void brcmf_deinit_priv_mem(struct brcmf_cfg80211_info *cfg)
        cfg->escan_ioctl_buf = NULL;
        kfree(cfg->extra_buf);
        cfg->extra_buf = NULL;
+       kfree(cfg->wowl.nd);
+       cfg->wowl.nd = NULL;
+       kfree(cfg->wowl.nd_info);
+       cfg->wowl.nd_info = NULL;
 }
 
 static s32 brcmf_init_priv_mem(struct brcmf_cfg80211_info *cfg)
@@ -5342,6 +5440,14 @@ static s32 brcmf_init_priv_mem(struct brcmf_cfg80211_info *cfg)
        cfg->extra_buf = kzalloc(WL_EXTRA_BUF_MAX, GFP_KERNEL);
        if (!cfg->extra_buf)
                goto init_priv_mem_out;
+       cfg->wowl.nd = kzalloc(sizeof(*cfg->wowl.nd) + sizeof(u32), GFP_KERNEL);
+       if (!cfg->wowl.nd)
+               goto init_priv_mem_out;
+       cfg->wowl.nd_info = kzalloc(sizeof(*cfg->wowl.nd_info) +
+                                   sizeof(struct cfg80211_wowlan_nd_match *),
+                                   GFP_KERNEL);
+       if (!cfg->wowl.nd_info)
+               goto init_priv_mem_out;
 
        return 0;
 
@@ -6018,7 +6124,7 @@ static void brcmf_wiphy_pno_params(struct wiphy *wiphy)
 }
 
 #ifdef CONFIG_PM
-static const struct wiphy_wowlan_support brcmf_wowlan_support = {
+static struct wiphy_wowlan_support brcmf_wowlan_support = {
        .flags = WIPHY_WOWLAN_MAGIC_PKT | WIPHY_WOWLAN_DISCONNECT,
        .n_patterns = BRCMF_WOWL_MAXPATTERNS,
        .pattern_max_len = BRCMF_WOWL_MAXPATTERNSIZE,
@@ -6027,10 +6133,23 @@ static const struct wiphy_wowlan_support brcmf_wowlan_support = {
 };
 #endif
 
-static void brcmf_wiphy_wowl_params(struct wiphy *wiphy)
+static void brcmf_wiphy_wowl_params(struct wiphy *wiphy, struct brcmf_if *ifp)
 {
 #ifdef CONFIG_PM
-       /* wowl settings */
+       struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy);
+       s32 err;
+       u32 wowl_cap;
+
+       if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_PNO)) {
+               err = brcmf_fil_iovar_int_get(ifp, "wowl_cap", &wowl_cap);
+               if (!err) {
+                       if (wowl_cap & BRCMF_WOWL_PFN_FOUND) {
+                               brcmf_wowlan_support.flags |=
+                                                       WIPHY_WOWLAN_NET_DETECT;
+                               init_waitqueue_head(&cfg->wowl.nd_data_wait);
+                       }
+               }
+       }
        wiphy->wowlan = &brcmf_wowlan_support;
 #endif
 }
@@ -6091,8 +6210,7 @@ static int brcmf_setup_wiphy(struct wiphy *wiphy, struct brcmf_if *ifp)
        wiphy->n_vendor_commands = BRCMF_VNDR_CMDS_LAST - 1;
 
        if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_WOWL))
-               brcmf_wiphy_wowl_params(wiphy);
-
+               brcmf_wiphy_wowl_params(wiphy, ifp);
        err = brcmf_fil_cmd_data_get(ifp, BRCMF_C_GET_BANDLIST, &bandlist,
                                     sizeof(bandlist));
        if (err) {
index c17b6d584fe00456d68ac0c4a738bde70e64a09a..69af708b43f5afad69394d0ecbef4ad9e3b19965 100644 (file)
@@ -226,6 +226,27 @@ struct brcmf_cfg80211_vif_event {
        struct brcmf_cfg80211_vif *vif;
 };
 
+/**
+ * struct brcmf_cfg80211_wowl - wowl related information.
+ *
+ * @active: set on suspend, cleared on resume.
+ * @pre_pmmode: firmware PM mode at entering suspend.
+ * @nd: net dectect data.
+ * @nd_info: helper struct to pass to cfg80211.
+ * @nd_data_wait: wait queue to sync net detect data.
+ * @nd_data_completed: completion for net detect data.
+ * @nd_enabled: net detect enabled.
+ */
+struct brcmf_cfg80211_wowl {
+       bool active;
+       u32 pre_pmmode;
+       struct cfg80211_wowlan_nd_match *nd;
+       struct cfg80211_wowlan_nd_info *nd_info;
+       wait_queue_head_t nd_data_wait;
+       bool nd_data_completed;
+       bool nd_enabled;
+};
+
 /**
  * struct brcmf_cfg80211_info - dongle private data of cfg80211 interface
  *
@@ -259,8 +280,7 @@ struct brcmf_cfg80211_vif_event {
  * @vif_list: linked list of vif instances.
  * @vif_cnt: number of vif instances.
  * @vif_event: vif event signalling.
- * @wowl_enabled; set during suspend, is wowl used.
- * @pre_wowl_pmmode: intermediate storage of pm mode during wowl.
+ * @wowl: wowl related information.
  */
 struct brcmf_cfg80211_info {
        struct wiphy *wiphy;
@@ -292,9 +312,8 @@ struct brcmf_cfg80211_info {
        struct brcmf_cfg80211_vif_event vif_event;
        struct completion vif_disabled;
        struct brcmu_d11inf d11inf;
-       bool wowl_enabled;
-       u32 pre_wowl_pmmode;
        struct brcmf_assoclist_le assoclist;
+       struct brcmf_cfg80211_wowl wowl;
 };
 
 /**
index bf2df49d7098a8f80ec162735b06e0d1472cba9b..1afc2ad83b6c77fe4306fd98bab3cf655a4185a2 100644 (file)
 #define BRCMF_WOWL_UNASSOC             (1 << 24)
 /* Wakeup if received matched secured pattern: */
 #define BRCMF_WOWL_SECURE              (1 << 25)
+/* Wakeup on finding preferred network */
+#define BRCMF_WOWL_PFN_FOUND           (1 << 26)
 /* Link Down indication in WoWL mode: */
 #define BRCMF_WOWL_LINKDOWN            (1 << 31)