2016-12-12 12:00:18

by Arend Van Spriel

[permalink] [raw]
Subject: [RFC V3 00/11] nl80211: add support for g-scan

Android employs a Wifi-HAL layer in its wireless frame. It basically abstracts
dealing with netlink messages from the framework. For some features it employs
nl80211 vendor commands. The goal I set myself is to be able to have a generic
nl80211 Wifi-HAL implementation. One of the features currently requiring the
vendor commands is g-scan. We can only guess what the 'g' stands for ;-) This
series converts the vendor command api into common nl80211 api.

This series adds basic support of g-scan (or GScan, or gscan, or something
completely different; suggestions are welcome). A basic g-scan request consists
of some common attributes and so-called buckets. Each bucket represents a
re-occurring scan request with a given interval and a set of channels. The
common attributes specify how much scans (m) should be stored and how many
BSS-es (n) should be kept per scan before an event is sent. The other option
is to specify a percentage at which an event is sent, where 100% equals (m * n).
A special case of bucket is the exponential backoff bucket, which has an
increasing interval.

The series is applied on top of master branch of wireless-testing as it relied
on patches pending in wireless-drivers-next. As such this series include two
fixes against those pending patched. Hopefully using wireless-testing allows
the nl80211 changes in this series to apply to mac80211-next. I consider it
still in RFC state so it may not matter right now.

Changes:
V2
- remove pr_err() statement from nl80211.c
- get rid of #if 0 code.
- reordered patches resolving compilation issue.
V3
- drop support for RTT info in scan results.
- cleanup attribute definitions.
- add driver implementation for brcmfmac.

Arend van Spriel (11):
nl80211: add reporting of gscan capabilities
nl80211: rename some notification functions
nl80211: add support for gscan
nl80211: add driver api for gscan notifications
brcmfmac: fix memory leak in brcmf_cfg80211_attach()
brcmfmac: fix uninitialized field in scheduled scan ssid configuration
brcmfmac: add firmware feature detection for gscan feature
brcmfmac: report gscan capabilities if firmware supports it
brcmfmac: implement gscan functionality
brcmfmac: handle gscan events from firmware
brcmfmac: allow gscan to run concurrent with scheduled scan

.../broadcom/brcm80211/brcmfmac/cfg80211.c | 266 +++++++++++--
.../broadcom/brcm80211/brcmfmac/cfg80211.h | 12 +-
.../wireless/broadcom/brcm80211/brcmfmac/core.c | 5 +-
.../wireless/broadcom/brcm80211/brcmfmac/debug.h | 2 +
.../wireless/broadcom/brcm80211/brcmfmac/feature.c | 22 +-
.../wireless/broadcom/brcm80211/brcmfmac/feature.h | 4 +-
.../wireless/broadcom/brcm80211/brcmfmac/fweh.h | 1 +
.../broadcom/brcm80211/brcmfmac/fwil_types.h | 92 +++++
.../net/wireless/broadcom/brcm80211/brcmfmac/pno.c | 381 +++++++++++++++++-
.../net/wireless/broadcom/brcm80211/brcmfmac/pno.h | 37 ++
include/net/cfg80211.h | 155 ++++++++
include/uapi/linux/nl80211.h | 207 ++++++++++
net/wireless/core.c | 33 ++
net/wireless/core.h | 6 +
net/wireless/nl80211.c | 430 ++++++++++++++++++++-
net/wireless/nl80211.h | 4 +-
net/wireless/rdev-ops.h | 25 ++
net/wireless/scan.c | 90 ++++-
net/wireless/trace.h | 19 +
19 files changed, 1726 insertions(+), 65 deletions(-)

--
1.9.1


2016-12-16 10:39:41

by Johannes Berg

[permalink] [raw]
Subject: Re: [RFC V3 04/11] nl80211: add driver api for gscan notifications


> Not sure what is meant by "through the buckets".

TBH, I was handwaving because I don't understand this part of gscan
well :-)

> Referring to your
> remark/question in the "Unversal scan proposal" thread:
>
> """
> I'm much more worried about the "bucket reporting" since that doesn't
> fit into the current full BSS reporting model at all. What's your
> suggestion for this?
> """
>
> So this is exactly the dilemma I considered so I decided to stick
> with the full BSS reporting model for gscan as well merely to get it
> discussed so glad you brought it up ;-).

Heh.

Ok, so I missed that. Somehow I thought hidden in the buckets was a
partial reporting :-)

> The problem here is that gscan is a vehicle that serves a number of
> use-cases. So ignoring hotlists, ePNO, etc. the gscan configuration
> still hold several notification types:
>
> - report after completing M scans capturing N best APs or a
>   percentage of (M * N).
> - report after completing a scan include a specific bucket.
> - report full scan results.
>
> The first two notification trigger retrieval of gscan results which
> are historic, ie. partial scan results for max M scans.
>
> As said earlier the universal scan proposal has some similarities to
> gscan. Guess you share that as you are using the term "bucket
> reporting" in that discussion ;-). The historic results are needed
> for location (if I am not mistaken) so the full BSS reporting model
> does not fit that. Question is what particular attribute in the
> historic results is needed for location (suspecting only rssi and
> possibly the timestamp, but would like to see that confirmed). I was
> thinking about have historic storage in cfg80211 so we do not need a
> per-driver solution.

Ok, now I'm starting to understand this better, I guess.

As far as I can tell from our code, for cached results we're reporting
the following data:

* some flags
* a scan ID
* and for each AP:
* RSSI
* timestamp
* channel
* BSSID
* SSID (which internally we even have a separate table for and each
AP just has an index to it, to save memory I guess)
* beacon period
* capability field

No IEs and similar things like differentiating probe response/beacon,
so we can't use the full reporting for this.

I have no problem introducing a common storage for this, if necessary
with some fields/nl attributes being optional, but I suspect this is
actually a necessary part of gscan, otherwise you're not able to report
all the necessary data?

johannes

2016-12-16 13:09:41

by Johannes Berg

[permalink] [raw]
Subject: Re: [RFC V3 04/11] nl80211: add driver api for gscan notifications

On Fri, 2016-12-16 at 13:17 +0100, Arend Van Spriel wrote:
>
> > I have no problem introducing a common storage for this, if
> > necessary with some fields/nl attributes being optional, but I
> > suspect this is actually a necessary part of gscan, otherwise
> > you're not able to report all the necessary data?
>
> If you just look at the gscan api in wifihal then yes. I was just
> wondering whether "all the necessary data" really comprises all these
> from a use-case perspective. As an example the api also has rtt
> fields, but both brcm and intel solutions do not report that.

Yeah, no idea. Sorry - my wording wasn't quite precise. By "this is
actually a necessary part of gscan" I meant the partial history
reporting itself, not any particular field thereof.

johannes

2016-12-16 12:21:23

by Arend Van Spriel

[permalink] [raw]
Subject: Re: [RFC V3 03/11] nl80211: add support for gscan

On 16-12-2016 11:13, Johannes Berg wrote:
> On Wed, 2016-12-14 at 10:01 +0100, Arend Van Spriel wrote:
>
>> Had to look for "> 16" ;-)
>
> Sorry.
>
>> Here an instance of the tab vs. space issue you mentioned. Will go
>> over the patch and fix that.
>
> There were a few, not really interesting though - git would probably
> flag it anyway, or checkpatch :)
>
>>> + if (num_chans > 16)
>>> + return -EINVAL;
>>
>> I suspect this is the restriction you were referring to.
>
> Yes.
>
>> There is no
>> reason for this although the android wifi hal has max 16 channels in
>> a bucket so I might have picked that up.
>
> I thought I saw something with a u16 bitmap that seemed related, but I
> don't see that now so I'm probably just confused.
>
>> So could a driver have a similar limit and should we add such to the
>> gscan capabilities? For instance our firmware api has a nasty
>> restriction of 64 channels for all buckets together, eg. can do 4
>> buckets of 16 channels each.
>
> We do have a limit of the maximum scan buckets, which seems to be 16
> right now. We also have a limit on the number of channels per bucket,
> which is also 16, but no combined limit afaict (so 16x16 seems fine).
>
> Maybe we do need some advertisement in that area then? Right now,
> wifihal seems to be able to read as capabilities the number of buckets
> (wifi_gscan_capabilities), but assumes the number of channels:
>
> const unsigned MAX_CHANNELS = 16;
> const unsigned MAX_BUCKETS = 16;
>
> I guess we took that and combined it, and you had more negotiation with
> Google ;-)

I was not so much involved with the initial gscan effort, but I guess
for brcm it might be true.

> We may then have to actually advertise the limit you have ("64 channels
> combined over all buckets"), unless you can get away with just
> advertising 4 buckets (and us saying 16 channels per bucket is enough?)
>
> I'm a bit tempted to make this more forward compatible though and not
> hard-limit the number of channels per bucket in the code.

Indeed so I will remove it.

Regards,
Arend

2016-12-12 12:00:18

by Arend Van Spriel

[permalink] [raw]
Subject: [RFC V3 02/11] nl80211: rename some notification functions

The functions nl80211_send_sched_scan() and nl80211_send_sched_scan_msg()
take command as parameter, which strictly speaking makes them general
purpose and not directly related to scheduled scan functionality. The
message are sent to "scan" multicast group so renaming them to
nl80211_send_scan_event{,_msg}().

Reviewed-by: Hante Meuleman <[email protected]>
Reviewed-by: Pieter-Paul Giesberts <[email protected]>
Reviewed-by: Franky Lin <[email protected]>
Signed-off-by: Arend van Spriel <[email protected]>
---
net/wireless/nl80211.c | 10 +++++-----
net/wireless/nl80211.h | 2 +-
net/wireless/scan.c | 4 ++--
3 files changed, 8 insertions(+), 8 deletions(-)

diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c
index 40209ec..14e1940 100644
--- a/net/wireless/nl80211.c
+++ b/net/wireless/nl80211.c
@@ -7247,7 +7247,7 @@ static int nl80211_start_sched_scan(struct sk_buff *skb,

rcu_assign_pointer(rdev->sched_scan_req, sched_scan_req);

- nl80211_send_sched_scan(rdev, dev,
+ nl80211_send_scan_event(rdev, dev,
NL80211_CMD_START_SCHED_SCAN);
return 0;

@@ -12878,7 +12878,7 @@ static int nl80211_send_scan_msg(struct sk_buff *msg,
}

static int
-nl80211_send_sched_scan_msg(struct sk_buff *msg,
+nl80211_send_scan_event_msg(struct sk_buff *msg,
struct cfg80211_registered_device *rdev,
struct net_device *netdev,
u32 portid, u32 seq, int flags, u32 cmd)
@@ -12958,7 +12958,7 @@ void nl80211_send_sched_scan_results(struct cfg80211_registered_device *rdev,
if (!msg)
return;

- if (nl80211_send_sched_scan_msg(msg, rdev, netdev, 0, 0, 0,
+ if (nl80211_send_scan_event_msg(msg, rdev, netdev, 0, 0, 0,
NL80211_CMD_SCHED_SCAN_RESULTS) < 0) {
nlmsg_free(msg);
return;
@@ -12968,7 +12968,7 @@ void nl80211_send_sched_scan_results(struct cfg80211_registered_device *rdev,
NL80211_MCGRP_SCAN, GFP_KERNEL);
}

-void nl80211_send_sched_scan(struct cfg80211_registered_device *rdev,
+void nl80211_send_scan_event(struct cfg80211_registered_device *rdev,
struct net_device *netdev, u32 cmd)
{
struct sk_buff *msg;
@@ -12977,7 +12977,7 @@ void nl80211_send_sched_scan(struct cfg80211_registered_device *rdev,
if (!msg)
return;

- if (nl80211_send_sched_scan_msg(msg, rdev, netdev, 0, 0, 0, cmd) < 0) {
+ if (nl80211_send_scan_event_msg(msg, rdev, netdev, 0, 0, 0, cmd) < 0) {
nlmsg_free(msg);
return;
}
diff --git a/net/wireless/nl80211.h b/net/wireless/nl80211.h
index 7e3821d..fb304ce9 100644
--- a/net/wireless/nl80211.h
+++ b/net/wireless/nl80211.h
@@ -16,7 +16,7 @@ struct sk_buff *nl80211_build_scan_msg(struct cfg80211_registered_device *rdev,
struct wireless_dev *wdev, bool aborted);
void nl80211_send_scan_result(struct cfg80211_registered_device *rdev,
struct sk_buff *msg);
-void nl80211_send_sched_scan(struct cfg80211_registered_device *rdev,
+void nl80211_send_scan_event(struct cfg80211_registered_device *rdev,
struct net_device *netdev, u32 cmd);
void nl80211_send_sched_scan_results(struct cfg80211_registered_device *rdev,
struct net_device *netdev);
diff --git a/net/wireless/scan.c b/net/wireless/scan.c
index 35ad69f..174076b 100644
--- a/net/wireless/scan.c
+++ b/net/wireless/scan.c
@@ -378,7 +378,7 @@ int __cfg80211_stop_sched_scan(struct cfg80211_registered_device *rdev,
return err;
}

- nl80211_send_sched_scan(rdev, dev, NL80211_CMD_SCHED_SCAN_STOPPED);
+ nl80211_send_scan_event(rdev, dev, NL80211_CMD_SCHED_SCAN_STOPPED);

RCU_INIT_POINTER(rdev->sched_scan_req, NULL);
kfree_rcu(sched_scan_req, rcu_head);
@@ -1147,7 +1147,7 @@ struct cfg80211_bss *
else
rcu_assign_pointer(tmp.pub.beacon_ies, ies);
rcu_assign_pointer(tmp.pub.ies, ies);
-
+
memcpy(tmp.pub.bssid, mgmt->bssid, ETH_ALEN);
tmp.pub.channel = channel;
tmp.pub.scan_width = data->scan_width;
--
1.9.1

2016-12-12 12:00:20

by Arend Van Spriel

[permalink] [raw]
Subject: [RFC V3 10/11] brcmfmac: handle gscan events from firmware

Handle the gscan events similar to scheduled scan. Collect all SSIDs found
in batch scan and add them to BSS list in cfg80211.

Reviewed-by: Hante Meuleman <[email protected]>
Reviewed-by: Pieter-Paul Giesberts <[email protected]>
Reviewed-by: Franky Lin <[email protected]>
Signed-off-by: Arend van Spriel <[email protected]>
---
.../broadcom/brcm80211/brcmfmac/cfg80211.c | 200 ++++++++++++++++++---
.../broadcom/brcm80211/brcmfmac/cfg80211.h | 10 +-
.../wireless/broadcom/brcm80211/brcmfmac/fweh.h | 1 +
.../broadcom/brcm80211/brcmfmac/fwil_types.h | 29 +++
4 files changed, 214 insertions(+), 26 deletions(-)

diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c
index 2b86c72..61636ad 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c
@@ -733,6 +733,7 @@ s32 brcmf_notify_escan_complete(struct brcmf_cfg80211_info *cfg,
{
struct brcmf_scan_params_le params_le;
struct cfg80211_scan_request *scan_request;
+ enum brcmf_internal_escan_requestor rid;
s32 err = 0;

brcmf_dbg(SCAN, "Enter\n");
@@ -763,7 +764,7 @@ s32 brcmf_notify_escan_complete(struct brcmf_cfg80211_info *cfg,
err = brcmf_fil_cmd_data_set(ifp, BRCMF_C_SCAN,
&params_le, sizeof(params_le));
if (err)
- brcmf_err("Scan abort failed\n");
+ brcmf_err("Scan abort failed\n");
}

brcmf_scan_config_mpc(ifp, 1);
@@ -772,11 +773,22 @@ s32 brcmf_notify_escan_complete(struct brcmf_cfg80211_info *cfg,
* e-scan can be initiated internally
* which takes precedence.
*/
- if (cfg->internal_escan) {
- brcmf_dbg(SCAN, "scheduled scan completed\n");
- cfg->internal_escan = false;
+ if (cfg->escan_rid) {
+ rid = cfg->escan_rid;
+ cfg->escan_rid = INT_ESCAN_REQ_NONE;
+ brcmf_dbg(SCAN, "internal scan completed (%d)\n", rid);
if (!aborted)
- cfg80211_sched_scan_results(cfg_to_wiphy(cfg));
+ switch (rid) {
+ case INT_ESCAN_REQ_SCHED_SCAN:
+ cfg80211_sched_scan_results(cfg_to_wiphy(cfg));
+ break;
+ case INT_ESCAN_REQ_GSCAN:
+ cfg80211_gscan_results(cfg_to_wiphy(cfg));
+ break;
+ default:
+ /* never happens */
+ break;
+ }
} else if (scan_request) {
struct cfg80211_scan_info info = {
.aborted = aborted,
@@ -1025,7 +1037,7 @@ static void brcmf_escan_prep(struct brcmf_cfg80211_info *cfg,
if (!ssid_le.SSID_len)
brcmf_dbg(SCAN, "%d: Broadcast scan\n", i);
else
- brcmf_dbg(SCAN, "%d: scan for %s size =%d\n",
+ brcmf_dbg(SCAN, "%d: scan for %.32s size=%d\n",
i, ssid_le.SSID, ssid_le.SSID_len);
memcpy(ptr, &ssid_le, sizeof(ssid_le));
ptr += sizeof(ssid_le);
@@ -3025,7 +3037,7 @@ void brcmf_abort_scanning(struct brcmf_cfg80211_info *cfg)
struct escan_info *escan = &cfg->escan_info;

set_bit(BRCMF_SCAN_STATUS_ABORT, &cfg->scan_status);
- if (cfg->internal_escan || cfg->scan_request) {
+ if (cfg->escan_rid || cfg->scan_request) {
escan->escan_state = WL_ESCAN_STATE_IDLE;
brcmf_notify_escan_complete(cfg, escan->ifp, true, true);
}
@@ -3048,7 +3060,7 @@ static void brcmf_escan_timeout(unsigned long data)
struct brcmf_cfg80211_info *cfg =
(struct brcmf_cfg80211_info *)data;

- if (cfg->internal_escan || cfg->scan_request) {
+ if (cfg->escan_rid || cfg->scan_request) {
brcmf_err("timer expired\n");
schedule_work(&cfg->escan_timeout_work);
}
@@ -3131,7 +3143,7 @@ static void brcmf_escan_timeout(unsigned long data)
if (brcmf_p2p_scan_finding_common_channel(cfg, bss_info_le))
goto exit;

- if (!cfg->internal_escan && !cfg->scan_request) {
+ if (!cfg->escan_rid && !cfg->scan_request) {
brcmf_dbg(SCAN, "result without cfg80211 request\n");
goto exit;
}
@@ -3177,7 +3189,7 @@ static void brcmf_escan_timeout(unsigned long data)
cfg->escan_info.escan_state = WL_ESCAN_STATE_IDLE;
if (brcmf_p2p_scan_finding_common_channel(cfg, NULL))
goto exit;
- if (cfg->internal_escan || cfg->scan_request) {
+ if (cfg->escan_rid || cfg->scan_request) {
brcmf_inform_bss(cfg);
aborted = status != BRCMF_E_STATUS_SUCCESS;
brcmf_notify_escan_complete(cfg, ifp, aborted, false);
@@ -3225,7 +3237,7 @@ static int brcmf_internal_escan_add_info(struct cfg80211_scan_request *req,
{
struct ieee80211_channel *chan;
enum nl80211_band band;
- int freq;
+ int freq, i;

if (channel <= CH_MAX_2G_CHANNEL)
band = NL80211_BAND_2GHZ;
@@ -3240,15 +3252,28 @@ static int brcmf_internal_escan_add_info(struct cfg80211_scan_request *req,
if (!chan)
return -EINVAL;

- req->channels[req->n_channels++] = chan;
- memcpy(req->ssids[req->n_ssids].ssid, ssid, ssid_len);
- req->ssids[req->n_ssids++].ssid_len = ssid_len;
+ for (i = 0; i < req->n_channels; i++) {
+ if (req->channels[i] == chan)
+ break;
+ }
+ if (i == req->n_channels)
+ req->channels[req->n_channels++] = chan;

+ for (i = 0; i < req->n_ssids; i++) {
+ if (req->ssids[i].ssid_len == ssid_len &&
+ !memcmp(req->ssids[i].ssid, ssid, ssid_len))
+ break;
+ }
+ if (i == req->n_ssids) {
+ memcpy(req->ssids[req->n_ssids].ssid, ssid, ssid_len);
+ req->ssids[req->n_ssids++].ssid_len = ssid_len;
+ }
return 0;
}

static int brcmf_start_internal_escan(struct brcmf_if *ifp,
- struct cfg80211_scan_request *request)
+ struct cfg80211_scan_request *request,
+ enum brcmf_internal_escan_requestor rid)
{
struct brcmf_cfg80211_info *cfg = ifp->drvr->config;
int err;
@@ -3265,7 +3290,7 @@ static int brcmf_start_internal_escan(struct brcmf_if *ifp,
clear_bit(BRCMF_SCAN_STATUS_BUSY, &cfg->scan_status);
return err;
}
- cfg->internal_escan = true;
+ cfg->escan_rid = rid;
return 0;
}

@@ -3335,8 +3360,8 @@ static int brcmf_start_internal_escan(struct brcmf_if *ifp,
brcmf_err("FALSE PNO Event. (pfn_count == 0)\n");
goto out_err;
}
- request = brcmf_alloc_internal_escan_request(wiphy,
- result_count);
+
+ request = brcmf_alloc_internal_escan_request(wiphy, result_count);
if (!request) {
err = -ENOMEM;
goto out_err;
@@ -3348,23 +3373,22 @@ static int brcmf_start_internal_escan(struct brcmf_if *ifp,
for (i = 0; i < result_count; i++) {
netinfo = &netinfo_start[i];
if (!netinfo) {
- brcmf_err("Invalid netinfo ptr. index: %d\n",
- i);
+ brcmf_err("Invalid netinfo ptr. index: %d\n", i);
err = -EINVAL;
goto out_err;
}

brcmf_dbg(SCAN, "SSID:%.32s Channel:%d\n",
netinfo->SSID, netinfo->channel);
- err = brcmf_internal_escan_add_info(request,
- netinfo->SSID,
+ err = brcmf_internal_escan_add_info(request, netinfo->SSID,
netinfo->SSID_len,
netinfo->channel);
if (err)
goto out_err;
}

- err = brcmf_start_internal_escan(ifp, request);
+ err = brcmf_start_internal_escan(ifp, request,
+ INT_ESCAN_REQ_SCHED_SCAN);
if (!err)
goto free_req;

@@ -3409,7 +3433,7 @@ static int brcmf_cfg80211_sched_scan_stop(struct wiphy *wiphy,

brcmf_dbg(SCAN, "enter\n");
brcmf_pno_clean(ifp);
- if (cfg->internal_escan)
+ if (cfg->escan_rid)
brcmf_notify_escan_complete(cfg, ifp, true, true);
return 0;
}
@@ -5081,6 +5105,131 @@ static int brcmf_cfg80211_tdls_oper(struct wiphy *wiphy,
return ret;
}

+#define BRCMF_GSCAN_RESULT_BUFSIZE 1024
+
+struct brcmf_batch_info {
+ struct list_head list;
+ u8 chan;
+ u8 ssid_len;
+ u8 ssid[32];
+};
+
+static int brcmf_add_batch_info(u8 ssid_len, u8 *ssid, u8 chan,
+ struct list_head *l)
+{
+ struct brcmf_batch_info *entry;
+
+ entry = kzalloc(sizeof(*entry), GFP_KERNEL);
+ if (!entry)
+ return -ENOMEM;
+ entry->chan = chan;
+ entry->ssid_len = ssid_len;
+ memcpy(entry->ssid, ssid, ssid_len);
+ list_add(&entry->list, l);
+
+ return 0;
+}
+
+static s32 brcmf_notify_best_batching(struct brcmf_if *ifp,
+ const struct brcmf_event_msg *e,
+ void *data)
+{
+ struct wiphy *wiphy = ifp->drvr->config->wiphy;
+ struct cfg80211_scan_request *request = NULL;
+ struct wl_pfn_lscanresults_v2 *pfn_bestnet;
+ struct wl_pfn_subnet_info_v2 *subnet;
+ struct list_head snlist;
+ struct brcmf_batch_info *batch, *tmp;
+ u32 ts, last_ts, n_subnet;
+ int err, cycle, i, scan_nr;
+
+ brcmf_dbg(TRACE, "Enter\n");
+ INIT_LIST_HEAD(&snlist);
+ n_subnet = 0;
+
+ pfn_bestnet = kmalloc(1024, GFP_KERNEL);
+ if (!pfn_bestnet) {
+ err = -ENOMEM;
+ goto out_err;
+ }
+
+ cycle = 0;
+ pfn_bestnet->status = 0;
+ while (!pfn_bestnet->status) {
+ memset(pfn_bestnet, 0, 1024);
+ err = brcmf_fil_iovar_data_get(ifp, "pfnlbest",
+ pfn_bestnet, 1024);
+ if (err < 0)
+ goto out_err;
+
+ brcmf_dbg(SCAN, "cycle[%d]: status=%u count=%u\n", cycle++,
+ le16_to_cpu(pfn_bestnet->status),
+ le16_to_cpu(pfn_bestnet->count));
+ for (i = 0; i < MAX_CHBKT_PER_RESULT; i++)
+ if (pfn_bestnet->scan_ch_buckets[i]) {
+ brcmf_dbg(SCAN, " buckets[%d]: %08x\n", i,
+ le32_to_cpu(pfn_bestnet->scan_ch_buckets[i]));
+ }
+ scan_nr = 0;
+ brcmf_dbg(SCAN, " #%d\n", scan_nr);
+ for (i = 0; i < le16_to_cpu(pfn_bestnet->count); i++) {
+ ts = le32_to_cpu(pfn_bestnet->netinfo[i].timestamp);
+ if (i && abs(ts - last_ts) > 3000)
+ brcmf_dbg(SCAN, " #%d\n", ++scan_nr);
+ subnet = &pfn_bestnet->netinfo[i].pfnsubnet;
+ brcmf_dbg(SCAN, " %pM: ch=%u ssid=%s (%u) rssi=%d ts=%u rtt=%u rtt-sd=%u\n",
+ subnet->BSSID, subnet->channel,
+ subnet->u.SSID, subnet->SSID_len,
+ (s16)le16_to_cpu(pfn_bestnet->netinfo[i].RSSI),
+ ts, le16_to_cpu(pfn_bestnet->netinfo[i].rtt0),
+ le16_to_cpu(pfn_bestnet->netinfo[i].rtt1));
+
+ last_ts = ts;
+ err = brcmf_add_batch_info(subnet->SSID_len,
+ subnet->u.SSID,
+ subnet->channel, &snlist);
+ if (err < 0)
+ goto out_err;
+ n_subnet++;
+ }
+ }
+ if (!n_subnet)
+ goto done;
+
+ request = brcmf_alloc_internal_escan_request(wiphy, n_subnet);
+ if (!request) {
+ err = -ENOMEM;
+ goto out_err;
+ }
+
+ list_for_each_entry_safe(batch, tmp, &snlist, list) {
+ err = brcmf_internal_escan_add_info(request,
+ batch->ssid,
+ batch->ssid_len,
+ batch->chan);
+ list_del(&batch->list);
+ kfree(batch);
+ if (err < 0)
+ goto out_err;
+ }
+
+ err = brcmf_start_internal_escan(ifp, request, INT_ESCAN_REQ_GSCAN);
+ if (!err)
+ goto free_req;
+
+out_err:
+ cfg80211_gscan_stopped(wiphy);
+ list_for_each_entry_safe(batch, tmp, &snlist, list) {
+ list_del(&batch->list);
+ kfree(batch);
+ }
+free_req:
+ kfree(request);
+done:
+ kfree(pfn_bestnet);
+ return err;
+}
+
static int brcmf_cfg80211_start_gscan(struct wiphy *wiphy,
struct net_device *ndev,
struct cfg80211_gscan_request *req)
@@ -5674,6 +5823,9 @@ static void brcmf_register_event_handlers(struct brcmf_cfg80211_info *cfg)
brcmf_p2p_notify_action_tx_complete);
brcmf_fweh_register(cfg->pub, BRCMF_E_ACTION_FRAME_OFF_CHAN_COMPLETE,
brcmf_p2p_notify_action_tx_complete);
+ brcmf_fweh_register(cfg->pub, BRCMF_E_PFN_BEST_BATCHING,
+ brcmf_notify_best_batching);
+
}

static void brcmf_deinit_priv_mem(struct brcmf_cfg80211_info *cfg)
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.h
index 0c9a708..ff65970 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.h
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.h
@@ -253,6 +253,12 @@ struct brcmf_cfg80211_wowl {
bool nd_enabled;
};

+enum brcmf_internal_escan_requestor {
+ INT_ESCAN_REQ_NONE,
+ INT_ESCAN_REQ_SCHED_SCAN,
+ INT_ESCAN_REQ_GSCAN,
+};
+
/**
* struct brcmf_cfg80211_info - dongle private data of cfg80211 interface
*
@@ -271,7 +277,7 @@ struct brcmf_cfg80211_wowl {
* @pub: common driver information.
* @channel: current channel.
* @active_scan: current scan mode.
- * @internal_escan: indicates internally initiated e-scan is running.
+ * @escan_rid: indicates current requestor of internally initiated e-scan.
* @ibss_starter: indicates this sta is ibss starter.
* @pwr_save: indicate whether dongle to support power save mode.
* @dongle_up: indicate whether dongle up or not.
@@ -303,7 +309,7 @@ struct brcmf_cfg80211_info {
struct brcmf_pub *pub;
u32 channel;
bool active_scan;
- bool internal_escan;
+ enum brcmf_internal_escan_requestor escan_rid;
bool ibss_starter;
bool pwr_save;
bool dongle_up;
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fweh.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fweh.h
index 5fba4b4..7315736 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fweh.h
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fweh.h
@@ -84,6 +84,7 @@
BRCMF_ENUM_DEF(IF, 54) \
BRCMF_ENUM_DEF(P2P_DISC_LISTEN_COMPLETE, 55) \
BRCMF_ENUM_DEF(RSSI, 56) \
+ BRCMF_ENUM_DEF(PFN_BEST_BATCHING, 57) \
BRCMF_ENUM_DEF(EXTLOG_MSG, 58) \
BRCMF_ENUM_DEF(ACTION_FRAME, 59) \
BRCMF_ENUM_DEF(ACTION_FRAME_COMPLETE, 60) \
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h
index 262642d..a1675c0 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h
@@ -898,4 +898,33 @@ struct brcmf_gscan_config {
struct brcmf_gscan_bucket_config bucket[1];
};

+struct wl_pfn_subnet_info_v2 {
+ u8 BSSID[ETH_ALEN];
+ u8 channel; /**< channel number only */
+ u8 SSID_len;
+ union {
+ u8 SSID[32];
+ __le16 index;
+ } u;
+};
+
+struct wl_pfn_lnet_info_v2 {
+ struct wl_pfn_subnet_info_v2 pfnsubnet; /**< BSSID + channel + SSID len + SSID */
+ __le16 flags; /**< partial scan, etc */
+ __le16 RSSI; /**< receive signal strength (in dBm) */
+ __le32 timestamp; /**< age in miliseconds */
+ __le16 rtt0; /**< estimated distance to this AP in centimeters */
+ __le16 rtt1; /**< standard deviation of the distance to this AP in centimeters */
+};
+
+#define MAX_CHBKT_PER_RESULT 4
+
+struct wl_pfn_lscanresults_v2 {
+ __le32 version;
+ __le16 status;
+ __le16 count;
+ __le32 scan_ch_buckets[MAX_CHBKT_PER_RESULT];
+ struct wl_pfn_lnet_info_v2 netinfo[1];
+};
+
#endif /* FWIL_TYPES_H_ */
--
1.9.1

2016-12-13 20:09:30

by Arend Van Spriel

[permalink] [raw]
Subject: Re: [RFC V3 03/11] nl80211: add support for gscan



On 13-12-2016 17:19, Johannes Berg wrote:
> On Mon, 2016-12-12 at 11:59 +0000, Arend van Spriel wrote:
>> This patch adds support for GScan which is a scan offload feature
>> used in Android.
>
> Found a few places with spaces instead of tabs as indentation, and
> spurious braces around single-statement things, but other than that it
> looks fine from a patch/nl80211 POV.

I added a check in wiphy_register() in this patch which actually is more
appropriate with the "gscan capabilities" patch.

> Haven't really looked into the details of gscan itself now though,
> sorry.
>
> There's a bit of a weird hard-coded restriction to 16 channels too,
> that's due to the bucket map?

Uhm. Is there? I will check, but if you can give me a pointer where to
look it is appreciated.

Regards,
Arend

2016-12-16 12:17:59

by Arend Van Spriel

[permalink] [raw]
Subject: Re: [RFC V3 04/11] nl80211: add driver api for gscan notifications

On 16-12-2016 11:02, Johannes Berg wrote:
>
>> Not sure what is meant by "through the buckets".
>
> TBH, I was handwaving because I don't understand this part of gscan
> well :-)
>
>> Referring to your
>> remark/question in the "Unversal scan proposal" thread:
>>
>> """
>> I'm much more worried about the "bucket reporting" since that doesn't
>> fit into the current full BSS reporting model at all. What's your
>> suggestion for this?
>> """
>>
>> So this is exactly the dilemma I considered so I decided to stick
>> with the full BSS reporting model for gscan as well merely to get it
>> discussed so glad you brought it up ;-).
>
> Heh.
>
> Ok, so I missed that. Somehow I thought hidden in the buckets was a
> partial reporting :-)
>
>> The problem here is that gscan is a vehicle that serves a number of
>> use-cases. So ignoring hotlists, ePNO, etc. the gscan configuration
>> still hold several notification types:
>>
>> - report after completing M scans capturing N best APs or a
>> percentage of (M * N).
>> - report after completing a scan include a specific bucket.
>> - report full scan results.
>>
>> The first two notification trigger retrieval of gscan results which
>> are historic, ie. partial scan results for max M scans.
>>
>> As said earlier the universal scan proposal has some similarities to
>> gscan. Guess you share that as you are using the term "bucket
>> reporting" in that discussion ;-). The historic results are needed
>> for location (if I am not mistaken) so the full BSS reporting model
>> does not fit that. Question is what particular attribute in the
>> historic results is needed for location (suspecting only rssi and
>> possibly the timestamp, but would like to see that confirmed). I was
>> thinking about have historic storage in cfg80211 so we do not need a
>> per-driver solution.
>
> Ok, now I'm starting to understand this better, I guess.
>
> As far as I can tell from our code, for cached results we're reporting
> the following data:
>
> * some flags
> * a scan ID
> * and for each AP:
> * RSSI
> * timestamp
> * channel
> * BSSID
> * SSID (which internally we even have a separate table for and each
> AP just has an index to it, to save memory I guess)
> * beacon period
> * capability field
>
> No IEs and similar things like differentiating probe response/beacon,
> so we can't use the full reporting for this.
>
> I have no problem introducing a common storage for this, if necessary
> with some fields/nl attributes being optional, but I suspect this is
> actually a necessary part of gscan, otherwise you're not able to report
> all the necessary data?

If you just look at the gscan api in wifihal then yes. I was just
wondering whether "all the necessary data" really comprises all these
from a use-case perspective. As an example the api also has rtt fields,
but both brcm and intel solutions do not report that.

Regards,
Arend

2016-12-12 12:00:20

by Arend Van Spriel

[permalink] [raw]
Subject: [RFC V3 06/11] brcmfmac: fix uninitialized field in scheduled scan ssid configuration

The scheduled scan ssid configuration in firmware has a flags field that
was not initialized resulting in unexpected behaviour.

Fixes: e3bdb7cc0300 ("brcmfmac: fix handling ssids in .sched_scan_start() callback")
Reviewed-by: Hante Meuleman <[email protected]>
Reviewed-by: Pieter-Paul Giesberts <[email protected]>
Reviewed-by: Franky Lin <[email protected]>
Signed-off-by: Arend van Spriel <[email protected]>
---
drivers/net/wireless/broadcom/brcm80211/brcmfmac/pno.c | 1 +
1 file changed, 1 insertion(+)

diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pno.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pno.c
index f273cab..9a25e79 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pno.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pno.c
@@ -137,6 +137,7 @@ static int brcmf_pno_add_ssid(struct brcmf_if *ifp, struct cfg80211_ssid *ssid,
pfn.wpa_auth = cpu_to_le32(BRCMF_PNO_WPA_AUTH_ANY);
pfn.wsec = cpu_to_le32(0);
pfn.infra = cpu_to_le32(1);
+ pfn.flags = 0;
if (active)
pfn.flags = cpu_to_le32(1 << BRCMF_PNO_HIDDEN_BIT);
pfn.ssid.SSID_len = cpu_to_le32(ssid->ssid_len);
--
1.9.1

2016-12-13 16:20:30

by Johannes Berg

[permalink] [raw]
Subject: Re: [RFC V3 03/11] nl80211: add support for gscan

On Mon, 2016-12-12 at 11:59 +0000, Arend van Spriel wrote:
> This patch adds support for GScan which is a scan offload feature
> used in Android.

Found a few places with spaces instead of tabs as indentation, and
spurious braces around single-statement things, but other than that it
looks fine from a patch/nl80211 POV.

Haven't really looked into the details of gscan itself now though,
sorry.

There's a bit of a weird hard-coded restriction to 16 channels too,
that's due to the bucket map?

johannes

2016-12-14 10:07:28

by Arend Van Spriel

[permalink] [raw]
Subject: Re: [RFC V3 04/11] nl80211: add driver api for gscan notifications

On 13-12-2016 17:20, Johannes Berg wrote:
> On Mon, 2016-12-12 at 11:59 +0000, Arend van Spriel wrote:
>> The driver can indicate gscan results are available or gscan
>> operation has stopped.
>
> This patch is renumbering the previous patches' nl80211 API, which is
> best avoided, even if I do realize it doesn't matter now. :)

Indeed. Will be more careful in upcoming revision(s).

> Even here it's not clear how things are reported though. Somehow I
> thought that gscan was reporting only partial information through the
> buckets, or is that not true?

Not sure what is meant by "through the buckets". Referring to your
remark/question in the "Unversal scan proposal" thread:

"""
I'm much more worried about the "bucket reporting" since that doesn't
fit into the current full BSS reporting model at all. What's your
suggestion for this?
"""

So this is exactly the dilemma I considered so I decided to stick with
the full BSS reporting model for gscan as well merely to get it
discussed so glad you brought it up ;-). The problem here is that
gscan is a vehicle that serves a number of use-cases. So ignoring
hotlists, ePNO, etc. the gscan configuration still hold several
notification types:

- report after completing M scans capturing N best APs or a
percentage of (M * N).
- report after completing a scan include a specific bucket.
- report full scan results.

The first two notification trigger retrieval of gscan results which are
historic, ie. partial scan results for max M scans.

As said earlier the universal scan proposal has some similarities to
gscan. Guess you share that as you are using the term "bucket reporting"
in that discussion ;-). The historic results are needed for location (if
I am not mistaken) so the full BSS reporting model does not fit that.
Question is what particular attribute in the historic results is needed
for location (suspecting only rssi and possibly the timestamp, but would
like to see that confirmed). I was thinking about have historic storage
in cfg80211 so we do not need a per-driver solution.

Regards,
Arend

2016-12-13 16:20:51

by Johannes Berg

[permalink] [raw]
Subject: Re: [RFC V3 04/11] nl80211: add driver api for gscan notifications

On Mon, 2016-12-12 at 11:59 +0000, Arend van Spriel wrote:
> The driver can indicate gscan results are available or gscan
> operation has stopped.

This patch is renumbering the previous patches' nl80211 API, which is
best avoided, even if I do realize it doesn't matter now. :)

Even here it's not clear how things are reported though. Somehow I
thought that gscan was reporting only partial information through the
buckets, or is that not true?

johannes

2016-12-12 12:00:20

by Arend Van Spriel

[permalink] [raw]
Subject: [RFC V3 09/11] brcmfmac: implement gscan functionality

This patch implements configuration of gscan in the device. Handling
the results is done in subsequent change. This initial implementation
does not support running scheduled scan and gscan simultaneously.

Reviewed-by: Hante Meuleman <[email protected]>
Reviewed-by: Pieter-Paul Giesberts <[email protected]>
Reviewed-by: Franky Lin <[email protected]>
Signed-off-by: Arend van Spriel <[email protected]>
---
.../broadcom/brcm80211/brcmfmac/cfg80211.c | 31 ++++
.../broadcom/brcm80211/brcmfmac/fwil_types.h | 18 +-
.../net/wireless/broadcom/brcm80211/brcmfmac/pno.c | 184 ++++++++++++++++++++-
.../net/wireless/broadcom/brcm80211/brcmfmac/pno.h | 9 +
4 files changed, 233 insertions(+), 9 deletions(-)

diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c
index 374b72c..2b86c72 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c
@@ -5081,6 +5081,35 @@ static int brcmf_cfg80211_tdls_oper(struct wiphy *wiphy,
return ret;
}

+static int brcmf_cfg80211_start_gscan(struct wiphy *wiphy,
+ struct net_device *ndev,
+ struct cfg80211_gscan_request *req)
+{
+ struct brcmf_if *ifp = netdev_priv(ndev);
+ struct brcmf_cfg80211_info *cfg = wiphy_priv(wiphy);
+
+ brcmf_dbg(SCAN, "Enter: n_buckets=%d\n", req->n_buckets);
+
+ if (test_bit(BRCMF_SCAN_STATUS_SUPPRESS, &cfg->scan_status)) {
+ brcmf_err("Scanning suppressed: status (%lu)\n",
+ cfg->scan_status);
+ return -EAGAIN;
+ }
+
+ /* configure gscan */
+ return brcmf_pno_start_gscan(ifp, req);
+}
+
+static int brcmf_cfg80211_stop_gscan(struct wiphy *wiphy,
+ struct net_device *ndev)
+{
+ struct brcmf_if *ifp = netdev_priv(ndev);
+
+ brcmf_dbg(TRACE, "Enter, bssidx=%d\n", ifp->bsscfgidx);
+
+ return brcmf_pno_clean(ifp);
+}
+
#ifdef CONFIG_PM
static int
brcmf_cfg80211_set_rekey_data(struct wiphy *wiphy, struct net_device *ndev,
@@ -5148,6 +5177,8 @@ static int brcmf_cfg80211_tdls_oper(struct wiphy *wiphy,
.crit_proto_start = brcmf_cfg80211_crit_proto_start,
.crit_proto_stop = brcmf_cfg80211_crit_proto_stop,
.tdls_oper = brcmf_cfg80211_tdls_oper,
+ .start_gscan = brcmf_cfg80211_start_gscan,
+ .stop_gscan = brcmf_cfg80211_stop_gscan,
};

struct brcmf_cfg80211_vif *brcmf_alloc_vif(struct brcmf_cfg80211_info *cfg,
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h
index 8c18fad..262642d 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h
@@ -855,16 +855,20 @@ struct brcmf_gscan_bucket_config {
};

/* version supported which must match firmware */
-#define BRCMF_GSCAN_CFG_VERSION 1
+#define BRCMF_GSCAN_CFG_VERSION 2

/**
* enum brcmf_gscan_cfg_flags - bit values for gscan flags.
*
* @BRCMF_GSCAN_CFG_FLAGS_ALL_RESULTS: send probe responses/beacons to host.
+ * @BRCMF_GSCAN_CFG_ALL_BUCKETS_IN_1ST_SCAN: all buckets will be included in
+ * first scan cycle.
* @BRCMF_GSCAN_CFG_FLAGS_CHANGE_ONLY: indicated only flags member is changed.
+ *
*/
enum brcmf_gscan_cfg_flags {
BRCMF_GSCAN_CFG_FLAGS_ALL_RESULTS = BIT(0),
+ BRCMF_GSCAN_CFG_ALL_BUCKETS_IN_1ST_SCAN = BIT(3),
BRCMF_GSCAN_CFG_FLAGS_CHANGE_ONLY = BIT(7),
};

@@ -884,12 +888,12 @@ enum brcmf_gscan_cfg_flags {
*/
struct brcmf_gscan_config {
__le16 version;
- u8 flags;
- u8 buffer_threshold;
- u8 swc_nbssid_threshold;
- u8 swc_rssi_window_size;
- u8 count_of_channel_buckets;
- u8 retry_threshold;
+ u8 flags;
+ u8 buffer_threshold;
+ u8 swc_nbssid_threshold;
+ u8 swc_rssi_window_size;
+ u8 count_of_channel_buckets;
+ u8 retry_threshold;
__le16 lost_ap_window;
struct brcmf_gscan_bucket_config bucket[1];
};
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pno.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pno.c
index 9a25e79..b868997 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pno.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pno.c
@@ -35,6 +35,9 @@
#define BRCMF_PNO_HIDDEN_BIT 2
#define BRCMF_PNO_SCHED_SCAN_PERIOD 30

+#define GSCAN_BATCH_NO_THR_SET 101
+#define GSCAN_RETRY_THRESHOLD 3
+
static int brcmf_pno_channel_config(struct brcmf_if *ifp,
struct brcmf_pno_config_le *cfg)
{
@@ -182,7 +185,6 @@ int brcmf_pno_clean(struct brcmf_if *ifp)
int brcmf_pno_start_sched_scan(struct brcmf_if *ifp,
struct cfg80211_sched_scan_request *req)
{
- struct brcmu_d11inf *d11inf;
struct brcmf_pno_config_le pno_cfg;
struct cfg80211_ssid *ssid;
u16 chan;
@@ -209,7 +211,6 @@ int brcmf_pno_start_sched_scan(struct brcmf_if *ifp,
}

/* configure channels to use */
- d11inf = &ifp->drvr->config->d11inf;
for (i = 0; i < req->n_channels; i++) {
chan = req->channels[i]->hw_value;
pno_cfg.channel_list[i] = cpu_to_le16(chan);
@@ -241,3 +242,182 @@ int brcmf_pno_start_sched_scan(struct brcmf_if *ifp,
return ret;
}

+static int brcmf_pno_get_bucket_channels(struct brcmf_if *ifp,
+ struct cfg80211_gscan_bucket *b,
+ struct brcmf_pno_config_le *pno_cfg)
+{
+ struct wiphy *wiphy;
+ struct ieee80211_supported_band *band;
+ u32 n_chan = le32_to_cpu(pno_cfg->channel_num);
+ u16 chan;
+ int i, err = 0;
+
+ wiphy = ifp->drvr->config->wiphy;
+
+ for (i = 0; i < b->n_channels; i++) {
+ if (n_chan >= BRCMF_NUMCHANNELS) {
+ err = -ENOSPC;
+ goto done;
+ }
+ chan = b->channels[i].ch->hw_value;
+ brcmf_dbg(INFO, "[%d] Chan : %u\n",
+ n_chan, chan);
+ pno_cfg->channel_list[n_chan++] = cpu_to_le16(chan);
+ }
+ if (b->band & NL80211_BUCKET_BAND_2GHZ) {
+ band = wiphy->bands[NL80211_BAND_2GHZ];
+ for (i = 0; i < band->n_channels; i++) {
+ if (band->channels[i].flags & IEEE80211_CHAN_DISABLED)
+ continue;
+ if (n_chan >= BRCMF_NUMCHANNELS) {
+ err = -ENOSPC;
+ goto done;
+ }
+ chan = band->channels[i].hw_value;
+ brcmf_dbg(INFO, "[%d] Chan : %u\n",
+ n_chan, chan);
+ pno_cfg->channel_list[n_chan++] = cpu_to_le16(chan);
+ }
+ }
+ if (b->band & NL80211_BUCKET_BAND_5GHZ) {
+ band = wiphy->bands[NL80211_BAND_5GHZ];
+ for (i = 0; i < band->n_channels; i++) {
+ if (band->channels[i].flags & IEEE80211_CHAN_DISABLED)
+ continue;
+ if (band->channels[i].flags & IEEE80211_CHAN_RADAR) {
+ if (b->band & NL80211_BUCKET_BAND_NODFS)
+ continue;
+ } else {
+ if (b->band & NL80211_BUCKET_BAND_DFS_ONLY)
+ continue;
+ }
+ if (n_chan >= BRCMF_NUMCHANNELS) {
+ err = -ENOSPC;
+ goto done;
+ }
+ chan = band->channels[i].hw_value;
+ brcmf_dbg(INFO, "[%d] Chan : %u\n",
+ n_chan, chan);
+ pno_cfg->channel_list[n_chan++] = cpu_to_le16(chan);
+ }
+ }
+ /* return number of channels */
+ err = n_chan;
+done:
+ pno_cfg->channel_num = cpu_to_le32(n_chan);
+ return err;
+}
+
+static int brcmf_pno_prepare_gscan(struct brcmf_if *ifp,
+ struct cfg80211_gscan_request *req,
+ struct brcmf_pno_config_le *pno_cfg,
+ struct brcmf_gscan_bucket_config **buckets)
+{
+ struct brcmf_gscan_bucket_config *fw_buckets;
+ struct cfg80211_gscan_bucket *bucket;
+ int i, err, chidx;
+
+ *buckets = NULL;
+ fw_buckets = kcalloc(req->n_buckets, sizeof(*buckets[0]), GFP_KERNEL);
+ if (!fw_buckets)
+ return -ENOMEM;
+
+ memset(pno_cfg, 0, sizeof(*pno_cfg));
+ bucket = &req->buckets[0];
+ for (i = 0; i < req->n_buckets; i++) {
+ chidx = brcmf_pno_get_bucket_channels(ifp, bucket, pno_cfg);
+ if (chidx < 0) {
+ err = chidx;
+ goto fail;
+ }
+ fw_buckets[i].bucket_end_index = chidx - 1;
+ fw_buckets[i].bucket_freq_multiple = bucket->period / req->base_period;
+ fw_buckets[i].repeat = cpu_to_le16(bucket->step_count);
+ fw_buckets[i].max_freq_multiple = cpu_to_le16(bucket->max_period / req->base_period);
+ fw_buckets[i].flag = bucket->report_events ^ NL80211_BUCKET_REPORT_NO_BATCH;
+ bucket++;
+ }
+ *buckets = fw_buckets;
+ return 0;
+
+fail:
+ kfree(fw_buckets);
+ return err;
+}
+
+int brcmf_pno_start_gscan(struct brcmf_if *ifp,
+ struct cfg80211_gscan_request *req)
+{
+ struct brcmf_gscan_config *gscan_cfg;
+ struct brcmf_gscan_bucket_config *buckets;
+ struct brcmf_pno_config_le pno_cfg;
+ size_t gscan_cfg_size;
+ int err;
+
+ /* clean up everything */
+ err = brcmf_pno_clean(ifp);
+ if (err < 0) {
+ brcmf_err("failed error=%d\n", err);
+ return err;
+ }
+
+ /* configure pno */
+ err = brcmf_pno_config(ifp, req->base_period / 1000,
+ req->report_threshold_num_scans,
+ req->max_ap_per_scan);
+ if (err < 0)
+ return err;
+
+ /* configure random mac */
+ if (req->flags & NL80211_SCAN_FLAG_RANDOM_ADDR) {
+ err = brcmf_pno_set_random(ifp, req->mac, req->mac_mask);
+ if (err < 0)
+ return err;
+ }
+
+ err = brcmf_pno_prepare_gscan(ifp, req, &pno_cfg, &buckets);
+ if (err < 0)
+ return err;
+
+ gscan_cfg_size = sizeof(*gscan_cfg) +
+ (req->n_buckets - 1) * sizeof(*buckets);
+ gscan_cfg = kzalloc(gscan_cfg_size, GFP_KERNEL);
+ if (!gscan_cfg) {
+ err = -ENOMEM;
+ goto free_buckets;
+ }
+
+ err = brcmf_pno_channel_config(ifp, &pno_cfg);
+ if (err < 0)
+ goto free_gscan;
+
+ gscan_cfg->version = cpu_to_le16(BRCMF_GSCAN_CFG_VERSION);
+ gscan_cfg->retry_threshold = GSCAN_RETRY_THRESHOLD;
+
+ if (!req->report_threshold_percent)
+ gscan_cfg->buffer_threshold = GSCAN_BATCH_NO_THR_SET;
+ else
+ gscan_cfg->buffer_threshold = req->report_threshold_percent;
+
+ gscan_cfg->flags = BRCMF_GSCAN_CFG_ALL_BUCKETS_IN_1ST_SCAN;
+
+ gscan_cfg->count_of_channel_buckets = req->n_buckets;
+ memcpy(&gscan_cfg->bucket[0], buckets,
+ req->n_buckets * sizeof(*buckets));
+
+ err = brcmf_fil_iovar_data_set(ifp, "pfn_gscan_cfg", gscan_cfg,
+ gscan_cfg_size);
+ if (err < 0)
+ goto free_gscan;
+
+ /* Enable the PNO */
+ err = brcmf_fil_iovar_int_set(ifp, "pfn", 1);
+ if (err < 0)
+ brcmf_err("PNO enable failed!! ret=%d\n", err);
+free_gscan:
+ kfree(gscan_cfg);
+free_buckets:
+ kfree(buckets);
+ return err;
+}
+
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pno.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pno.h
index bae55b2..06ad3b0 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pno.h
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pno.h
@@ -37,4 +37,13 @@
int brcmf_pno_start_sched_scan(struct brcmf_if *ifp,
struct cfg80211_sched_scan_request *req);

+/**
+ * brcmf_pno_start_gscan - initiate gscan on device.
+ *
+ * @ifp: interface object used.
+ * @req: GScan request parameters.
+ */
+int brcmf_pno_start_gscan(struct brcmf_if *ifp,
+ struct cfg80211_gscan_request *req);
+
#endif /* _BRCMF_PNO_H */
--
1.9.1

2016-12-13 16:39:32

by Johannes Berg

[permalink] [raw]
Subject: Re: [RFC V3 01/11] nl80211: add reporting of gscan capabilities


> + case 14:
> + if (!rdev->wiphy.gscan) {
> + /* done */
> + state->split_start = 0;
> + break;
> + }
>

Nit, but I'm not really happy with this - this assumes that case 14 is
the last case, if anyone ever adds one we break this code but it would
still work if the device has gscan. Move the gscan stuff into a new
function and make that return immediately if gscan is NULL or so?

johannes

2016-12-12 12:00:20

by Arend Van Spriel

[permalink] [raw]
Subject: [RFC V3 05/11] brcmfmac: fix memory leak in brcmf_cfg80211_attach()

In brcmf_cfg80211_attach() there was one error path not properly
handled as it leaked memory allocated in brcmf_btcoex_attach().

Reviewed-by: Hante Meuleman <[email protected]>
Reviewed-by: Pieter-Paul Giesberts <[email protected]>
Reviewed-by: Franky Lin <[email protected]>
Signed-off-by: Arend van Spriel <[email protected]>
---
drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c
index ccae3bb..7ffc4ab 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c
@@ -6868,7 +6868,7 @@ struct brcmf_cfg80211_info *brcmf_cfg80211_attach(struct brcmf_pub *drvr,

err = brcmf_p2p_attach(cfg, p2pdev_forced);
if (err) {
- brcmf_err("P2P initilisation failed (%d)\n", err);
+ brcmf_err("P2P initialisation failed (%d)\n", err);
goto wiphy_unreg_out;
}
err = brcmf_btcoex_attach(cfg);
@@ -6893,7 +6893,7 @@ struct brcmf_cfg80211_info *brcmf_cfg80211_attach(struct brcmf_pub *drvr,
err = brcmf_fweh_activate_events(ifp);
if (err) {
brcmf_err("FWEH activation failed (%d)\n", err);
- goto wiphy_unreg_out;
+ goto detach;
}

/* Fill in some of the advertised nl80211 supported features */
@@ -6908,6 +6908,9 @@ struct brcmf_cfg80211_info *brcmf_cfg80211_attach(struct brcmf_pub *drvr,

return cfg;

+detach:
+ brcmf_btcoex_detach(cfg);
+ brcmf_p2p_detach(&cfg->p2p);
wiphy_unreg_out:
wiphy_unregister(cfg->wiphy);
priv_out:
--
1.9.1

2016-12-12 12:00:20

by Arend Van Spriel

[permalink] [raw]
Subject: [RFC V3 07/11] brcmfmac: add firmware feature detection for gscan feature

From: Arend van Spriel <[email protected]>

Detect gscan support in firmware by doing pfn_gscan_cfg iovar with
invalid version.

Reviewed-by: Hante Meuleman <[email protected]>
Reviewed-by: Pieter-Paul Giesberts <[email protected]>
Reviewed-by: Franky Lin <[email protected]>
Signed-off-by: Arend van Spriel <[email protected]>
---
.../wireless/broadcom/brcm80211/brcmfmac/feature.c | 22 +++++++-
.../wireless/broadcom/brcm80211/brcmfmac/feature.h | 4 +-
.../broadcom/brcm80211/brcmfmac/fwil_types.h | 59 ++++++++++++++++++++++
3 files changed, 83 insertions(+), 2 deletions(-)

diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.c
index 62985f2..8c7ef59 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.c
@@ -27,6 +27,7 @@
#include "feature.h"
#include "common.h"

+#define BRCMF_FW_UNSUPPORTED 23

/*
* expand feature list to array of feature strings.
@@ -113,6 +114,22 @@ static void brcmf_feat_iovar_int_get(struct brcmf_if *ifp,
}
}

+static void brcmf_feat_iovar_data_set(struct brcmf_if *ifp,
+ enum brcmf_feat_id id, char *name,
+ const void *data, size_t len)
+{
+ int err;
+
+ err = brcmf_fil_iovar_data_set(ifp, name, data, len);
+ if (err != -BRCMF_FW_UNSUPPORTED) {
+ brcmf_dbg(INFO, "enabling feature: %s\n", brcmf_feat_names[id]);
+ ifp->drvr->feat_flags |= BIT(id);
+ } else {
+ brcmf_dbg(TRACE, "%s feature check failed: %d\n",
+ brcmf_feat_names[id], err);
+ }
+}
+
static void brcmf_feat_firmware_capabilities(struct brcmf_if *ifp)
{
char caps[256];
@@ -136,11 +153,14 @@ void brcmf_feat_attach(struct brcmf_pub *drvr)
{
struct brcmf_if *ifp = brcmf_get_ifp(drvr, 0);
struct brcmf_pno_macaddr_le pfn_mac;
+ struct brcmf_gscan_config gscan_cfg;
u32 wowl_cap;
s32 err;

brcmf_feat_firmware_capabilities(ifp);
-
+ memset(&gscan_cfg, 0, sizeof(gscan_cfg));
+ brcmf_feat_iovar_data_set(ifp, BRCMF_FEAT_GSCAN, "pfn_gscan_cfg",
+ &gscan_cfg, sizeof(gscan_cfg));
brcmf_feat_iovar_int_get(ifp, BRCMF_FEAT_PNO, "pfn");
if (drvr->bus_if->wowl_supported)
brcmf_feat_iovar_int_get(ifp, BRCMF_FEAT_WOWL, "wowl");
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.h
index db4733a..c1dbd17 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.h
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.h
@@ -31,6 +31,7 @@
* WOWL_GTK: (WOWL) GTK rekeying offload
* WOWL_ARP_ND: ARP and Neighbor Discovery offload support during WOWL.
* MFP: 802.11w Management Frame Protection.
+ * GSCAN: enhanced scan offload feature.
*/
#define BRCMF_FEAT_LIST \
BRCMF_FEAT_DEF(MBSS) \
@@ -44,7 +45,8 @@
BRCMF_FEAT_DEF(WOWL_ND) \
BRCMF_FEAT_DEF(WOWL_GTK) \
BRCMF_FEAT_DEF(WOWL_ARP_ND) \
- BRCMF_FEAT_DEF(MFP)
+ BRCMF_FEAT_DEF(MFP) \
+ BRCMF_FEAT_DEF(GSCAN)

/*
* Quirks:
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h
index 9a1eb5a..8c18fad 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h
@@ -835,4 +835,63 @@ struct brcmf_gtk_keyinfo_le {
u8 replay_counter[BRCMF_RSN_REPLAY_LEN];
};

+/**
+ * struct brcmf_gscan_bucket_config - configuration data for channel bucket.
+ *
+ * @bucket_end_index: !unknown!
+ * @bucket_freq_multiple: !unknown!
+ * @flag: !unknown!
+ * @reserved: !unknown!
+ * @repeat: !unknown!
+ * @max_freq_multiple: !unknown!
+ */
+struct brcmf_gscan_bucket_config {
+ u8 bucket_end_index;
+ u8 bucket_freq_multiple;
+ u8 flag;
+ u8 reserved;
+ __le16 repeat;
+ __le16 max_freq_multiple;
+};
+
+/* version supported which must match firmware */
+#define BRCMF_GSCAN_CFG_VERSION 1
+
+/**
+ * enum brcmf_gscan_cfg_flags - bit values for gscan flags.
+ *
+ * @BRCMF_GSCAN_CFG_FLAGS_ALL_RESULTS: send probe responses/beacons to host.
+ * @BRCMF_GSCAN_CFG_FLAGS_CHANGE_ONLY: indicated only flags member is changed.
+ */
+enum brcmf_gscan_cfg_flags {
+ BRCMF_GSCAN_CFG_FLAGS_ALL_RESULTS = BIT(0),
+ BRCMF_GSCAN_CFG_FLAGS_CHANGE_ONLY = BIT(7),
+};
+
+/**
+ * struct brcmf_gscan_config - configuration data for gscan.
+ *
+ * @version: version of the api to match firmware.
+ * @flags: flags according %enum brcmf_gscan_cfg_flags.
+ * @buffer_threshold: percentage threshold of buffer to generate an event.
+ * @swc_nbssid_threshold: number of BSSIDs with significant change that
+ * will generate an event.
+ * @swc_rssi_window_size: size of rssi cache buffer (max=8).
+ * @count_of_channel_buckets: number of array members in @bucket.
+ * @retry_threshold: !unknown!
+ * @lost_ap_window: !unknown!
+ * @bucket: array of channel buckets.
+ */
+struct brcmf_gscan_config {
+ __le16 version;
+ u8 flags;
+ u8 buffer_threshold;
+ u8 swc_nbssid_threshold;
+ u8 swc_rssi_window_size;
+ u8 count_of_channel_buckets;
+ u8 retry_threshold;
+ __le16 lost_ap_window;
+ struct brcmf_gscan_bucket_config bucket[1];
+};
+
#endif /* FWIL_TYPES_H_ */
--
1.9.1

2016-12-12 12:00:17

by Arend Van Spriel

[permalink] [raw]
Subject: [RFC V3 01/11] nl80211: add reporting of gscan capabilities

From: Arend van Spriel <[email protected]>

GScan is a scan offload feature used in recent Android releases. This
patch adds possibility for wireless device drivers to report their
capabilities and provide it to user-space.

Reviewed-by: Hante Meuleman <[email protected]>
Reviewed-by: Pieter-Paul Giesberts <[email protected]>
Reviewed-by: Franky Lin <[email protected]>
Signed-off-by: Arend van Spriel <[email protected]>
---
Changes:
V3:
- change storage types for capabilities.
---
include/net/cfg80211.h | 36 +++++++++++++++++++++++++++
include/uapi/linux/nl80211.h | 59 ++++++++++++++++++++++++++++++++++++++++++++
net/wireless/nl80211.c | 45 ++++++++++++++++++++++++++++++++-
3 files changed, 139 insertions(+), 1 deletion(-)

diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h
index ef42749..b78377f 100644
--- a/include/net/cfg80211.h
+++ b/include/net/cfg80211.h
@@ -3361,6 +3361,40 @@ struct wiphy_iftype_ext_capab {
};

/**
+ * struct wiphy_gscan_caps - gscan capabilities of the device.
+ *
+ * @max_scan_cache_size: total space allocated for scan results (in bytes).
+ * @max_scan_buckets: maximum number of channel buckets.
+ * @max_ap_cache_per_scan: maximum number of APs that can be stored per scan.
+ * @max_rssi_sample_size: number of RSSI samples used for averaging RSSI.
+ * @max_scan_reporting_threshold: max possible report threshold. in percentage.
+ * @max_hotlist_bssids: maximum number of entries for hotlist BSSIDs.
+ * @max_hotlist_ssids: maximum number of entries for hotlist SSIDs.
+ * @max_significant_wifi_change_aps: maximum number of entries for significant
+ * change APs.
+ * @max_bssid_history_entries: number of BSSID/RSSI entries that device can
+ * hold.
+ * @max_epno_networks: max number of hashed epno entries.
+ * @max_epno_networks_by_ssid: max number of epno entries for which an
+ * exact match of SSID is required or which are hidded SSIDs.
+ * @max_white_list_ssid: max number of white listed SSIDs.
+ */
+struct wiphy_gscan_caps {
+ u16 max_scan_cache_size;
+ u8 max_scan_buckets;
+ u8 max_ap_cache_per_scan;
+ u8 max_rssi_sample_size;
+ u8 max_scan_reporting_threshold;
+ u8 max_hotlist_bssids;
+ u8 max_hotlist_ssids;
+ u8 max_significant_wifi_change_aps;
+ u8 max_bssid_history_entries;
+ u8 max_epno_hashed_networks;
+ u8 max_epno_exact_networks;
+ u8 max_white_list_ssid;
+};
+
+/**
* struct wiphy - wireless hardware description
* @reg_notifier: the driver's regulatory notification callback,
* note that if your driver uses wiphy_apply_custom_regulatory()
@@ -3513,6 +3547,7 @@ struct wiphy_iftype_ext_capab {
* @bss_select_support: bitmask indicating the BSS selection criteria supported
* by the driver in the .connect() callback. The bit position maps to the
* attribute indices defined in &enum nl80211_bss_select_attr.
+ * @gscan: structure holding the hardware capabilities for gscan.
*
* @cookie_counter: unique generic cookie counter, used to identify objects.
*/
@@ -3643,6 +3678,7 @@ struct wiphy {
u8 max_adj_channel_rssi_comp;

u32 bss_select_support;
+ const struct wiphy_gscan_caps *gscan;

u64 cookie_counter;

diff --git a/include/uapi/linux/nl80211.h b/include/uapi/linux/nl80211.h
index 6b76e3b..01ab2f7 100644
--- a/include/uapi/linux/nl80211.h
+++ b/include/uapi/linux/nl80211.h
@@ -1980,6 +1980,9 @@ enum nl80211_commands {
* @NL80211_ATTR_BSSID: The BSSID of the AP. Note that %NL80211_ATTR_MAC is also
* used in various commands/events for specifying the BSSID.
*
+ * @NL80211_ATTR_GSCAN_CAPS: indicating capabilities of GScan functionality
+ * of the device. This is a nested attribute.
+ *
* @NUM_NL80211_ATTR: total number of nl80211_attrs available
* @NL80211_ATTR_MAX: highest attribute number currently defined
* @__NL80211_ATTR_AFTER_LAST: internal use
@@ -2385,6 +2388,7 @@ enum nl80211_attrs {
NL80211_ATTR_MULTICAST_TO_UNICAST_ENABLED,

NL80211_ATTR_BSSID,
+ NL80211_ATTR_GSCAN_CAPS,

/* add attributes here, update the policy in nl80211.c */

@@ -5187,4 +5191,59 @@ enum nl80211_nan_match_attributes {
NL80211_NAN_MATCH_ATTR_MAX = NUM_NL80211_NAN_MATCH_ATTR - 1
};

+/**
+ * enum nl80211_gscan_caps_attr - GScan capabilities attributes.
+ *
+ * @__NL80211_GSCAN_CAPS_ATTR_INVALID: reserved.
+ * @NL80211_GSCAN_CAPS_ATTR_MAX_SCAN_CACHE_SIZE: total space allocated for
+ * scan results in bytes (u16).
+ * @NL80211_GSCAN_CAPS_ATTR_MAX_SCAN_BUCKETS: maximum number of channel buckets
+ * allowed (u8).
+ * @NL80211_GSCAN_CAPS_ATTR_MAX_AP_CACHE_PER_SCAN: maximum number of APs that
+ * can be stored per scan (u8).
+ * @NL80211_GSCAN_CAPS_ATTR_MAX_RSSI_SAMPLE_SIZE: maximum number of RSSI samples
+ * used for averaging RSSI (u8).
+ * @NL80211_GSCAN_CAPS_ATTR_MAX_SCAN_REPORTING_THRESHOLD: maximum allowed
+ * percentile report threshold (u8).
+ * @NL80211_GSCAN_CAPS_ATTR_MAX_HOTLIST_BSSID: maximum number of entries for
+ * hotlist BSSIDs (u8).
+ * @NL80211_GSCAN_CAPS_ATTR_MAX_HOTLIST_SSID: maximum number of entries for
+ * hotlist SSIDs (u8).
+ * @NL80211_GSCAN_CAPS_ATTR_MAX_SIGNIFICANT_WIFI_CHANGE_APS: maximum number of
+ * entries for significant change APs (u8).
+ * @NL80211_GSCAN_CAPS_ATTR_MAX_BSSID_HISTORY: number of BSSID/RSSI entries that
+ * device can hold (u8).
+ * @NL80211_GSCAN_CAPS_ATTR_MAX_EPNO_HASHED_NETWORKS: maximum number of hashed
+ * epno entries (u8).
+ * @NL80211_GSCAN_CAPS_ATTR_MAX_EPNO_EXACT_NETWORKS: maximum number of epno
+ * entries for which an exact match of SSID is required or which are hidden
+ * SSIDs (u8).
+ * @NL80211_GSCAN_CAPS_ATTR_MAX_WHITE_LIST_SSID: maximum number of white listed
+ * SSIDs (u8).
+ * @NL80211_GSCAN_CAPS_ATTR_MAX: highest GScan capability attribute.
+ *
+ * @__NL80211_GSCAN_CAPS_ATTR_AFTER_LAST: internal use.
+ *
+ * All attributes are u32 type.
+ */
+enum nl80211_gscan_caps_attr {
+ __NL80211_GSCAN_CAPS_ATTR_INVALID,
+ NL80211_GSCAN_CAPS_ATTR_MAX_SCAN_CACHE_SIZE,
+ NL80211_GSCAN_CAPS_ATTR_MAX_SCAN_BUCKETS,
+ NL80211_GSCAN_CAPS_ATTR_MAX_AP_CACHE_PER_SCAN,
+ NL80211_GSCAN_CAPS_ATTR_MAX_RSSI_SAMPLE_SIZE,
+ NL80211_GSCAN_CAPS_ATTR_MAX_SCAN_REPORTING_THRESHOLD,
+ NL80211_GSCAN_CAPS_ATTR_MAX_HOTLIST_BSSID,
+ NL80211_GSCAN_CAPS_ATTR_MAX_HOTLIST_SSID,
+ NL80211_GSCAN_CAPS_ATTR_MAX_SIGNIFICANT_WIFI_CHANGE_APS,
+ NL80211_GSCAN_CAPS_ATTR_MAX_BSSID_HISTORY,
+ NL80211_GSCAN_CAPS_ATTR_MAX_EPNO_HASHED_NETWORKS,
+ NL80211_GSCAN_CAPS_ATTR_MAX_EPNO_EXACT_NETWORKS,
+ NL80211_GSCAN_CAPS_ATTR_MAX_WHITE_LIST_SSID,
+
+ /* keep last */
+ __NL80211_GSCAN_CAPS_ATTR_AFTER_LAST,
+ NL80211_GSCAN_CAPS_ATTR_MAX = __NL80211_GSCAN_CAPS_ATTR_AFTER_LAST - 1
+};
+
#endif /* __LINUX_NL80211_H */
diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c
index 7762231..40209ec 100644
--- a/net/wireless/nl80211.c
+++ b/net/wireless/nl80211.c
@@ -1424,9 +1424,10 @@ static int nl80211_send_wiphy(struct cfg80211_registered_device *rdev,
void *hdr;
struct nlattr *nl_bands, *nl_band;
struct nlattr *nl_freqs, *nl_freq;
- struct nlattr *nl_cmds;
+ struct nlattr *nl_cmds, *nl_gscan;
enum nl80211_band band;
struct ieee80211_channel *chan;
+ const struct wiphy_gscan_caps *gscan;
int i;
const struct ieee80211_txrx_stypes *mgmt_stypes =
rdev->wiphy.mgmt_stypes;
@@ -1881,6 +1882,48 @@ static int nl80211_send_wiphy(struct cfg80211_registered_device *rdev,
}
}

+ state->split_start++;
+ break;
+ case 14:
+ if (!rdev->wiphy.gscan) {
+ /* done */
+ state->split_start = 0;
+ break;
+ }
+ gscan = rdev->wiphy.gscan;
+ nl_gscan = nla_nest_start(msg, NL80211_ATTR_GSCAN_CAPS);
+ if (!nl_gscan ||
+ nla_put_u16(msg, NL80211_GSCAN_CAPS_ATTR_MAX_SCAN_CACHE_SIZE,
+ gscan->max_scan_cache_size) ||
+ nla_put_u8(msg, NL80211_GSCAN_CAPS_ATTR_MAX_SCAN_BUCKETS,
+ gscan->max_scan_buckets) ||
+ nla_put_u8(msg, NL80211_GSCAN_CAPS_ATTR_MAX_AP_CACHE_PER_SCAN,
+ gscan->max_ap_cache_per_scan) ||
+ nla_put_u8(msg, NL80211_GSCAN_CAPS_ATTR_MAX_RSSI_SAMPLE_SIZE,
+ gscan->max_rssi_sample_size) ||
+ nla_put_u8(msg,
+ NL80211_GSCAN_CAPS_ATTR_MAX_SCAN_REPORTING_THRESHOLD,
+ gscan->max_scan_reporting_threshold) ||
+ nla_put_u8(msg, NL80211_GSCAN_CAPS_ATTR_MAX_HOTLIST_BSSID,
+ gscan->max_hotlist_bssids) ||
+ nla_put_u8(msg, NL80211_GSCAN_CAPS_ATTR_MAX_HOTLIST_SSID,
+ gscan->max_hotlist_ssids) ||
+ nla_put_u8(msg,
+ NL80211_GSCAN_CAPS_ATTR_MAX_SIGNIFICANT_WIFI_CHANGE_APS,
+ gscan->max_significant_wifi_change_aps) ||
+ nla_put_u8(msg, NL80211_GSCAN_CAPS_ATTR_MAX_BSSID_HISTORY,
+ gscan->max_bssid_history_entries) ||
+ nla_put_u8(msg,
+ NL80211_GSCAN_CAPS_ATTR_MAX_EPNO_HASHED_NETWORKS,
+ gscan->max_epno_hashed_networks) ||
+ nla_put_u8(msg,
+ NL80211_GSCAN_CAPS_ATTR_MAX_EPNO_EXACT_NETWORKS,
+ gscan->max_epno_exact_networks) ||
+ nla_put_u8(msg, NL80211_GSCAN_CAPS_ATTR_MAX_WHITE_LIST_SSID,
+ gscan->max_white_list_ssid))
+ goto nla_put_failure;
+ nla_nest_end(msg, nl_gscan);
+
/* done */
state->split_start = 0;
break;
--
1.9.1

2016-12-12 17:43:15

by Dan Williams

[permalink] [raw]
Subject: Re: [RFC V3 03/11] nl80211: add support for gscan

On Mon, 2016-12-12 at 11:59 +0000, Arend van Spriel wrote:
> This patch adds support for GScan which is a scan offload feature
> used in Android.
>
> Reviewed-by: Hante Meuleman <[email protected]>
> Reviewed-by: Pieter-Paul Giesberts <[email protected]
> m>
> Reviewed-by: Franky Lin <[email protected]>
> Signed-off-by: Arend van Spriel <[email protected]>
> ---
> Changes:
>  V2
>   - remove pr_err() statement from nl80211.c
>   - get rid of #if 0 code.
>  V3
>   - change and document storage type of gscan attributes.
>   - remove base period attribute from nl80211.
>   - bucket periods are changed to be seconds.
>   - change NO_IR attribute to PASSIVE.
>   - check for NL80211_ATTR_MAC{,_MASK} if random mac support is
> requested.
>   - remove NL80211_SCAN_FLAG_IE_DATA.
> ---
>  include/net/cfg80211.h       |  91 +++++++++++
>  include/uapi/linux/nl80211.h | 146 ++++++++++++++++++
>  net/wireless/core.c          |  31 ++++
>  net/wireless/core.h          |   4 +
>  net/wireless/nl80211.c       | 356
> ++++++++++++++++++++++++++++++++++++++++++-
>  net/wireless/rdev-ops.h      |  25 +++
>  net/wireless/scan.c          |  28 ++++
>  net/wireless/trace.h         |   9 ++
>  8 files changed, 685 insertions(+), 5 deletions(-)
>
> diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h
> index b78377f..8bc8842 100644
> --- a/include/net/cfg80211.h
> +++ b/include/net/cfg80211.h
> @@ -2453,6 +2453,92 @@ struct cfg80211_nan_func {
>  };
>
>  /**
> + * struct cfg80211_gscan_channel - GScan channel parameters.
> + *
> +
> + * @ch: specific channel.
> + * @dwell_time: hint for dwell time in milliseconds.
> + * @passive: indicates passive scan is requested.
> + */
> +struct cfg80211_gscan_channel {
> +    struct ieee80211_channel *ch;
> +    u8 dwell_time;
> +    bool passive;
> +};
> +
> +/**
> + * struct cfg80211_gscan_bucket - GScan bucket parameters.
> + *
> + * @idx: unique bucket index.
> + * @band: bit flags for band(s) to use, see %enum
> nl80211_bucket_band.
> + * @report_events: This is a bit field according %enum
> nl80211_bucket_report_event.
> + * @period: period in which the bucket is scheduled to be scanned.
> If the
> + * period is too small for driver it should not fail but
> report results
> + * as fast as it can. For exponential backoff bucket this is
> the minimum
> + * period.
> + * @max_period: used only for the exponential backoff bucket whose
> scan period
> + * will grow exponentially to a maximum period of max_period.
> + * @exponent: used only for the exponential backoff bucket.
> + * @step_count: used only for the exponential backoff bucket.
> + * @n_channels: number of channels in @channels array.
> + * @channels: channels to scan which may include DFS channels.
> + */
> +struct cfg80211_gscan_bucket {
> + u32 idx;
> + u16 period;
> + u8 band;
> + u8 report_events;
> + u16 max_period;
> + u8 exponent;
> + u8 step_count;
> + u8 n_channels;
> + struct cfg80211_gscan_channel *channels;
> +};
> +
> +/**
> + * struct cfg80211_gscan_request - GScan request parameters.
> + *
> + * @flags: scan request flags according %enum nl80211_scan_flags.
> + * @base_period: base timer period in milliseconds.
> + * @max_ap_per_scan: number of APs to store in each scan entry in
> the BSSID/RSSI
> + * history buffer (keep APS with highest RSSI).
> + * @report_threshold_percent: wake up system when scan buffer is
> filled to this
> + * percentage.
> + * @report_threshold_num_scans: wake up system when this many scans
> are stored
> + * in scan buffer.
> + * @mac: MAC address used for randomisation.
> + * @mac_mask: MAC address mask. bits that are 0 in the mask should
> be
> + * randomised, bits that are 1 should be taken as is from
> @mac.
> + * @n_buckets: number of entries in @buckets array.
> + * @buckets: array of GScan buckets.
> + *
> + * @dev: net device for which GScan is requested.
> + * @rcu_head: RCU callback used to free the struct.
> + * @owner_nlportid: netlink port which initiated this request.
> + * @scan_start: start time of this scan in jiffies.
> + */
> +struct cfg80211_gscan_request {
> + u32 flags;
> + u16 base_period;
> + u8 max_ap_per_scan;
> + u8 report_threshold_percent;
> + u8 report_threshold_num_scans;
> + u8 mac[ETH_ALEN];
> + u8 mac_mask[ETH_ALEN];
> +
> + u8 n_buckets;
> +
> + /* internal */
> + struct net_device *dev;
> + struct rcu_head rcu_head;
> + u32 owner_nlportid;
> + unsigned long scan_start;
> +
> + /* keep last */
> + struct cfg80211_gscan_bucket buckets[0];
> +};
> +
> +/**
>   * struct cfg80211_ops - backend description for wireless
> configuration
>   *
>   * This struct is registered by fullmac card drivers and/or wireless
> stacks
> @@ -2764,6 +2850,8 @@ struct cfg80211_nan_func {
>   * All other parameters must be ignored.
>   *
>   * @set_multicast_to_unicast: configure multicast to unicast
> conversion for BSS
> + * @start_gscan: start the GSCAN scanning offload.
> + * @stop_gscan: stop the GSCAN scanning offload.
>   */
>  struct cfg80211_ops {
>   int (*suspend)(struct wiphy *wiphy, struct
> cfg80211_wowlan *wow);
> @@ -3048,6 +3136,9 @@ struct cfg80211_ops {
>   int (*set_multicast_to_unicast)(struct wiphy *wiphy,
>       struct net_device *dev,
>       const bool enabled);
> + int (*start_gscan)(struct wiphy *wiphy, struct
> net_device *dev,
> +        struct cfg80211_gscan_request
> *gscan_req);
> + int (*stop_gscan)(struct wiphy *wiphy, struct
> net_device *dev);
>  };
>
>  /*
> diff --git a/include/uapi/linux/nl80211.h
> b/include/uapi/linux/nl80211.h
> index 01ab2f7..5e42383 100644
> --- a/include/uapi/linux/nl80211.h
> +++ b/include/uapi/linux/nl80211.h
> @@ -894,6 +894,12 @@
>   * does not result in a change for the current association.
> Currently,
>   * only the %NL80211_ATTR_IE data is used and updated with
> this command.
>   *
> + * @NL80211_CMD_START_GSCAN: start GScan.
> + * @NL80211_CMD_STOP_GSCAN: request to stop current GScan.
> + * @NL80211_CMD_GSCAN_STOPPED: indicates that the currently running
> GScan
> + * has stopped. This event is generated upon
> @NL80211_CMD_STOP_GSCAN and
> + * the driver may issue this event at any time when a GScan
> is running.
> + *
>   * @NL80211_CMD_MAX: highest used command number
>   * @__NL80211_CMD_AFTER_LAST: internal use
>   */
> @@ -1093,6 +1099,10 @@ enum nl80211_commands {
>
>   NL80211_CMD_UPDATE_CONNECT_PARAMS,
>
> + NL80211_CMD_START_GSCAN,
> + NL80211_CMD_STOP_GSCAN,
> + NL80211_CMD_GSCAN_STOPPED,
> +
>   /* add new commands above here */
>
>   /* used to define NL80211_CMD_MAX below */
> @@ -2389,6 +2399,7 @@ enum nl80211_attrs {
>
>   NL80211_ATTR_BSSID,
>   NL80211_ATTR_GSCAN_CAPS,
> + NL80211_ATTR_GSCAN_PARAMS,
>
>   /* add attributes here, update the policy in nl80211.c */
>
> @@ -5246,4 +5257,139 @@ enum nl80211_gscan_caps_attr {
>   NL80211_GSCAN_CAPS_ATTR_MAX =
> __NL80211_GSCAN_CAPS_ATTR_AFTER_LAST - 1
>  };
>
> +/**
> + * enum nl80211_gscan_attr - common GScan parameters.
> + *
> + * @__NL80211_GSCAN_ATTR_INVALID: reserved.
> + * @NL80211_GSCAN_ATTR_MAX_AP_PER_SCAN: number of APs that are kept
> per
> + * scan. The kept APs are the ones with strongest RSSI level
> (u8).
> + * @NL80211_GSCAN_ATTR_REPORT_PERC: threshold specifying percentage
> of
> + * scan cache filled that should trigger event for scan
> results (u8).
> + * @NL80211_GSCAN_ATTR_REPORT_SCANS: threshold specifying number of
> scans
> + * after which an event is expected for scan results (u8).
> + * @NL80211_GSCAN_ATTR_BUCKETS: nested attribute specifying
> + * per-bucket parameters for GScan. See %enum
> nl80211_gscan_bucket_attr
> + * for description.
> + * @NL80211_GSCAN_ATTR_MAX: highest GScan attribute.
> + * @__NL80211_GSCAN_ATTR_AFTER_LAST: internal use.
> + */
> +enum nl80211_gscan_attr {
> + __NL80211_GSCAN_ATTR_INVALID,
> + NL80211_GSCAN_ATTR_MAX_AP_PER_SCAN,
> + NL80211_GSCAN_ATTR_REPORT_PERC,
> + NL80211_GSCAN_ATTR_REPORT_SCANS,
> + NL80211_GSCAN_ATTR_BUCKETS,
> +
> + /* keep last */
> + __NL80211_GSCAN_ATTR_AFTER_LAST,
> + NL80211_GSCAN_ATTR_MAX = __NL80211_GSCAN_ATTR_AFTER_LAST - 1
> +};
> +
> +/**
> + * enum nl80211_gscan_bucket_attr - per-bucket GScan parameters.
> + *
> + * @__NL80211_GSCAN_BUCKET_ATTR_INVALID,
> + * @NL80211_GSCAN_BUCKET_ATTR_ID: unique bucket id (u32).
> + * @NL80211_GSCAN_BUCKET_ATTR_BAND: specifies the band to be scanned
> + * according %enum nl80211_bucket_band. If specified
> + * @NL80211_GSCAN_BUCKET_ATTR_CHANNELS is ignored (u8).
> + * @NL80211_GSCAN_BUCKET_ATTR_PERIOD: specifies the period between
> consecutive
> + * scans of this bucket in seconds. For the backoff bucket
> this is
> + * period(0) (u16).
> + * @NL80211_GSCAN_BUCKET_ATTR_REPORT: specifies reporting flags
> according
> + * %enum nl80211_bucket_report_event (u8).
> + * @NL80211_GSCAN_BUCKET_ATTR_MAX_PERIOD: maximum period between
> + * consecutive scans. If specified this is a backoff bucket
> in
> + * which the period increases according formula:
> + * period(N) = period(0) * (base ^ (N/step_count)) (u16)
> + * @NL80211_GSCAN_BUCKET_ATTR_EXPONENT: exponential base value as
> used
> + * in given formula. This attribute is required when
> + * @NL80211_GSCAN_BUCKET_ATTR_MAX_PERIOD is specified (u8).
> + * @NL80211_GSCAN_BUCKET_ATTR_STEPS: step count as used in given
> formula.
> + * This attribute is required when
> @NL80211_GSCAN_BUCKET_ATTR_MAX_PERIOD
> + * is specified (u8).
> + * @NL80211_GSCAN_BUCKET_ATTR_CHANNELS: nested attribute specifying
> the
> + * channels that are to be scanned for this bucket.
> + * @NL80211_GSCAN_BUCKET_ATTR_MAX: highest GScan bucket attribute.
> + * @__NL80211_GSCAN_BUCKET_ATTR_AFTER_LAST: internal use.
> + */
> +enum nl80211_gscan_bucket_attr {
> + __NL80211_GSCAN_BUCKET_ATTR_INVALID,
> + NL80211_GSCAN_BUCKET_ATTR_ID,
> + NL80211_GSCAN_BUCKET_ATTR_BAND,
> + NL80211_GSCAN_BUCKET_ATTR_PERIOD,
> + NL80211_GSCAN_BUCKET_ATTR_REPORT,
> + NL80211_GSCAN_BUCKET_ATTR_MAX_PERIOD,
> + NL80211_GSCAN_BUCKET_ATTR_EXPONENT,
> + NL80211_GSCAN_BUCKET_ATTR_STEPS,
> + NL80211_GSCAN_BUCKET_ATTR_CHANNELS,
> +
> + /* keep last */
> + __NL80211_GSCAN_BUCKET_ATTR_AFTER_LAST,
> + NL80211_GSCAN_BUCKET_ATTR_MAX =
> __NL80211_GSCAN_BUCKET_ATTR_AFTER_LAST - 1
> +};
> +
> +/**
> + * enum nl80211_gscan_chan_attr - GScan bucket channel parameters.
> + *
> + * @__NL80211_GSCAN_CHAN_ATTR_INVALID: reserved.
> + * @NL80211_GSCAN_CHAN_ATTR_FREQ: frequency of channel to be scanned
> (u32).
> + * @NL80211_GSCAN_CHAN_ATTR_DWELL_TIME: dwell time in milliseconds
> to stay
> + * on this channel during scanning (u8).
> + * @NL80211_GSCAN_CHAN_ATTR_PASSIVE: flag attribute indicating that
> scanning
> + * should be done passive for this channel.
> + * @NL80211_GSCAN_CHAN_ATTR_MAX: highest GScan channel attribute.
> + * @__NL80211_GSCAN_CHAN_ATTR_AFTER_LAST: internal use.
> + *
> + * Apart from the channel itself the attributes
> %NL80211_GSCAN_CHAN_ATTR_DWELL_TIME
> + * and %NL80211_GSCAN_CHAN_ATTR_PASSIVE are advisory values. The
> driver may or
> + * may not comply.
> + */
> +enum nl80211_gscan_chan_attr {
> + __NL80211_GSCAN_CHAN_ATTR_INVALID,
> + NL80211_GSCAN_CHAN_ATTR_FREQ,
> + NL80211_GSCAN_CHAN_ATTR_DWELL_TIME,
> + NL80211_GSCAN_CHAN_ATTR_PASSIVE,
> +
> + /* keep last */
> + __NL80211_GSCAN_CHAN_ATTR_AFTER_LAST,
> + NL80211_GSCAN_CHAN_ATTR_MAX =
> __NL80211_GSCAN_CHAN_ATTR_AFTER_LAST - 1
> +};
> +
> +/**
> + * enum nl80211_bucket_band - GScan bucket band selection.

Quite possibly this was already covered and somebody requested you to
change this to the current name.  If that's the case, ignore this.

But shouldn't this enum and bucket_event_report include "gscan" in
their name, like the other gscan specific stuff does?  Are these going
to get used for something else too, and will that thing make sense with
the word "bucket"?  Just "nl80211_bucket_band" is pretty generic.

Dan

> + * @NL80211_BUCKET_BAND_2GHZ: consider all device supported channels
> + * in 2G band.
> + * @NL80211_BUCKET_BAND_5GHZ: consider all device supported channels
> + * in 5G band, ie. both DFS and non-DFS when
> @NL80211_BUCKET_BAND_NODFS
> + * and @NL80211_BUCKET_BAND_DFS_ONLY are not set.
> + * @NL80211_BUCKET_BAND_NODFS: only consider non-DFS channels. Only
> + * applicable when 5G band is selected, otherwise ignored.
> + * @NL80211_BUCKET_BAND_DFS_ONLY: only consider DFS channels. Only
> + * applicable when 5G band is selected, otherwise ignored.
> + *
> + * Setting both @NL80211_BUCKET_BAND_NODFS and
> @NL80211_BUCKET_BAND_DFS_ONLY
> + * is considerd invalid.
> + */
> +enum nl80211_bucket_band {
> + NL80211_BUCKET_BAND_2GHZ = (1 << 0),
> + NL80211_BUCKET_BAND_5GHZ = (1 << 1),
> + NL80211_BUCKET_BAND_NODFS = (1 << 2),
> + NL80211_BUCKET_BAND_DFS_ONLY = (1 << 3),
> +};
> +
> +/**
> + * enum nl80211_bucket_report_event - GScan bucket report flags.
> + *
> + * @NL80211_BUCKET_REPORT_EACH_SCAN: report each bucket scan
> completion.
> + * @NL80211_BUCKET_REPORT_FULL_RESULTS: report full scan results.
> + * @NL80211_BUCKET_REPORT_NO_BATCH: no batching required.
> + */
> +enum nl80211_bucket_report_event {
> + NL80211_BUCKET_REPORT_EACH_SCAN = (1 << 0),
> + NL80211_BUCKET_REPORT_FULL_RESULTS = (1 << 1),
> + NL80211_BUCKET_REPORT_NO_BATCH = (1 << 2),
> +};
> +
>  #endif /* __LINUX_NL80211_H */
> diff --git a/net/wireless/core.c b/net/wireless/core.c
> index 158c59e..760a2fb 100644
> --- a/net/wireless/core.c
> +++ b/net/wireless/core.c
> @@ -357,6 +357,20 @@ static void cfg80211_sched_scan_stop_wk(struct
> work_struct *work)
>   rtnl_unlock();
>  }
>
> +static void cfg80211_gscan_stop_wk(struct work_struct *work)
> +{
> + struct cfg80211_registered_device *rdev;
> +
> + rdev = container_of(work, struct cfg80211_registered_device,
> +     gscan_stop_wk);
> +
> + rtnl_lock();
> +
> + __cfg80211_stop_gscan(rdev, false);
> +
> + rtnl_unlock();
> +}
> +
>  /* exported functions */
>
>  struct wiphy *wiphy_new_nm(const struct cfg80211_ops *ops, int
> sizeof_priv,
> @@ -383,6 +397,7 @@ struct wiphy *wiphy_new_nm(const struct
> cfg80211_ops *ops, int sizeof_priv,
>   WARN_ON(ops->remain_on_channel && !ops-
> >cancel_remain_on_channel);
>   WARN_ON(ops->tdls_channel_switch && !ops-
> >tdls_cancel_channel_switch);
>   WARN_ON(ops->add_tx_ts && !ops->del_tx_ts);
> + WARN_ON(ops->start_gscan && !ops->stop_gscan);
>
>   alloc_size = sizeof(*rdev) + sizeof_priv;
>
> @@ -456,6 +471,7 @@ struct wiphy *wiphy_new_nm(const struct
> cfg80211_ops *ops, int sizeof_priv,
>   spin_lock_init(&rdev->destroy_list_lock);
>   INIT_WORK(&rdev->destroy_work, cfg80211_destroy_iface_wk);
>   INIT_WORK(&rdev->sched_scan_stop_wk,
> cfg80211_sched_scan_stop_wk);
> + INIT_WORK(&rdev->gscan_stop_wk, cfg80211_gscan_stop_wk);
>
>  #ifdef CONFIG_CFG80211_DEFAULT_PS
>   rdev->wiphy.flags |= WIPHY_FLAG_PS_ON_BY_DEFAULT;
> @@ -690,6 +706,12 @@ int wiphy_register(struct wiphy *wiphy)
>       (wiphy->bss_select_support &
> ~(BIT(__NL80211_BSS_SELECT_ATTR_AFTER_LAST) - 2))))
>   return -EINVAL;
>
> + /* buckets must have unique index and in nl80211 parsing
> +  * a u32 is used to verify that hence this limit.
> +  */
> + if (WARN_ON(wiphy->gscan && wiphy->gscan->max_scan_buckets >
> 32))
> + return -EINVAL;
> +
>   if (wiphy->addresses)
>   memcpy(wiphy->perm_addr, wiphy->addresses[0].addr,
> ETH_ALEN);
>
> @@ -1001,6 +1023,7 @@ void __cfg80211_leave(struct
> cfg80211_registered_device *rdev,
>  {
>   struct net_device *dev = wdev->netdev;
>   struct cfg80211_sched_scan_request *sched_scan_req;
> + struct cfg80211_gscan_request *gscan_req;
>
>   ASSERT_RTNL();
>   ASSERT_WDEV_LOCK(wdev);
> @@ -1014,6 +1037,9 @@ void __cfg80211_leave(struct
> cfg80211_registered_device *rdev,
>   sched_scan_req = rtnl_dereference(rdev-
> >sched_scan_req);
>   if (sched_scan_req && dev == sched_scan_req->dev)
>   __cfg80211_stop_sched_scan(rdev, false);
> + gscan_req = rtnl_dereference(rdev->gscan_req);
> + if (gscan_req && dev == gscan_req->dev)
> + __cfg80211_stop_gscan(rdev, false);
>
>  #ifdef CONFIG_CFG80211_WEXT
>   kfree(wdev->wext.ie);
> @@ -1089,6 +1115,7 @@ static int cfg80211_netdev_notifier_call(struct
> notifier_block *nb,
>   struct wireless_dev *wdev = dev->ieee80211_ptr;
>   struct cfg80211_registered_device *rdev;
>   struct cfg80211_sched_scan_request *sched_scan_req;
> + struct cfg80211_gscan_request *gscan_req;
>
>   if (!wdev)
>   return NOTIFY_DONE;
> @@ -1160,6 +1187,10 @@ static int
> cfg80211_netdev_notifier_call(struct notifier_block *nb,
>       sched_scan_req->dev == wdev->netdev)) {
>   __cfg80211_stop_sched_scan(rdev, false);
>   }
> + gscan_req = rtnl_dereference(rdev->gscan_req);
> + if (WARN_ON(gscan_req && gscan_req->dev == wdev-
> >netdev)) {
> + __cfg80211_stop_gscan(rdev, false);
> + }
>
>   rdev->opencount--;
>   wake_up(&rdev->dev_wait);
> diff --git a/net/wireless/core.h b/net/wireless/core.h
> index ec5f333..ee1d162 100644
> --- a/net/wireless/core.h
> +++ b/net/wireless/core.h
> @@ -75,6 +75,7 @@ struct cfg80211_registered_device {
>   struct cfg80211_scan_request *scan_req; /* protected by RTNL
> */
>   struct sk_buff *scan_msg;
>   struct cfg80211_sched_scan_request __rcu *sched_scan_req;
> + struct cfg80211_gscan_request __rcu *gscan_req;
>   unsigned long suspend_at;
>   struct work_struct scan_done_wk;
>   struct work_struct sched_scan_results_wk;
> @@ -96,6 +97,7 @@ struct cfg80211_registered_device {
>   struct work_struct destroy_work;
>
>   struct work_struct sched_scan_stop_wk;
> + struct work_struct gscan_stop_wk;
>
>   /* must be last because of the way we do wiphy_priv(),
>    * and it should at least be aligned to NETDEV_ALIGN */
> @@ -422,6 +424,8 @@ void ___cfg80211_scan_done(struct
> cfg80211_registered_device *rdev,
>  void __cfg80211_sched_scan_results(struct work_struct *wk);
>  int __cfg80211_stop_sched_scan(struct cfg80211_registered_device
> *rdev,
>          bool driver_initiated);
> +int __cfg80211_stop_gscan(struct cfg80211_registered_device *rdev,
> +   bool driver_initiated);
>  void cfg80211_upload_connect_keys(struct wireless_dev *wdev);
>  int cfg80211_change_iface(struct cfg80211_registered_device *rdev,
>     struct net_device *dev, enum
> nl80211_iftype ntype,
> diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c
> index 14e1940..4186ece 100644
> --- a/net/wireless/nl80211.c
> +++ b/net/wireless/nl80211.c
> @@ -9,6 +9,7 @@
>  #include <linux/if.h>
>  #include <linux/module.h>
>  #include <linux/err.h>
> +#include <linux/gcd.h>
>  #include <linux/slab.h>
>  #include <linux/list.h>
>  #include <linux/if_ether.h>
> @@ -405,6 +406,7 @@ enum nl80211_multicast_groups {
>   [NL80211_ATTR_FILS_NONCES] = { .len = 2 * FILS_NONCE_LEN },
>   [NL80211_ATTR_MULTICAST_TO_UNICAST_ENABLED] = { .type =
> NLA_FLAG, },
>   [NL80211_ATTR_BSSID] = { .len = ETH_ALEN },
> + [NL80211_ATTR_GSCAN_PARAMS] = { .type = NLA_NESTED },
>  };
>
>  /* policy for the key attributes */
> @@ -11860,6 +11862,322 @@ static int
> nl80211_set_multicast_to_unicast(struct sk_buff *skb,
>   return rdev_set_multicast_to_unicast(rdev, dev, enabled);
>  }
>
> +static const
> +struct nla_policy nl80211_gscan_policy[NL80211_GSCAN_ATTR_MAX + 1] =
> {
> + [NL80211_GSCAN_ATTR_MAX_AP_PER_SCAN] = { .type = NLA_U8 },
> + [NL80211_GSCAN_ATTR_REPORT_PERC] = { .type = NLA_U8 },
> + [NL80211_GSCAN_ATTR_REPORT_SCANS] = { .type = NLA_U8 },
> + [NL80211_GSCAN_ATTR_BUCKETS] = { .type = NLA_NESTED },
> +};
> +
> +static const struct nla_policy
> +nl80211_gscan_bucket_policy[NL80211_GSCAN_BUCKET_ATTR_MAX + 1] = {
> + [NL80211_GSCAN_BUCKET_ATTR_ID] = { .type = NLA_U32 },
> + [NL80211_GSCAN_BUCKET_ATTR_BAND] = { .type = NLA_U8 },
> + [NL80211_GSCAN_BUCKET_ATTR_PERIOD] = { .type = NLA_U16 },
> + [NL80211_GSCAN_BUCKET_ATTR_REPORT] = { .type = NLA_U8 },
> + [NL80211_GSCAN_BUCKET_ATTR_MAX_PERIOD] = { .type = NLA_U16
> },
> + [NL80211_GSCAN_BUCKET_ATTR_EXPONENT] = { .type = NLA_U8 },
> + [NL80211_GSCAN_BUCKET_ATTR_STEPS] = { .type = NLA_U8 },
> + [NL80211_GSCAN_BUCKET_ATTR_CHANNELS] = { .type = NLA_NESTED
> },
> +};
> +
> +static const struct nla_policy
> +nl80211_gscan_channel_policy[NL80211_GSCAN_CHAN_ATTR_MAX + 1] = {
> +        [NL80211_GSCAN_CHAN_ATTR_FREQ] = { .type = NLA_U32 },
> +        [NL80211_GSCAN_CHAN_ATTR_DWELL_TIME] = { .type = NLA_U8 },
> +        [NL80211_GSCAN_CHAN_ATTR_PASSIVE] = { .type = NLA_FLAG },
> +};
> +
> +static int nl80211_parse_gscan_channel(struct
> cfg80211_registered_device *rdev,
> +        struct nlattr *nattr,
> +        struct cfg80211_gscan_channel
> *chan)
> +{
> + struct nlattr *tb[NL80211_GSCAN_CHAN_ATTR_MAX + 1];
> + struct ieee80211_channel *ch;
> + int err;
> +
> + err = nla_parse(tb, NL80211_GSCAN_CHAN_ATTR_MAX,
> nla_data(nattr),
> + nla_len(nattr),
> nl80211_gscan_channel_policy);
> + if (err)
> + return err;
> +
> + if (!tb[NL80211_GSCAN_CHAN_ATTR_FREQ])
> + return -EINVAL;
> +
> + ch = ieee80211_get_channel(&rdev->wiphy,
> +    nla_get_u32(tb[NL80211_GSCAN_CHAN
> _ATTR_FREQ]));
> + if (!ch || (ch->flags & IEEE80211_CHAN_DISABLED))
> + return -EINVAL;
> +
> + chan->ch = ch;
> +
> + if (tb[NL80211_GSCAN_CHAN_ATTR_DWELL_TIME])
> + chan->dwell_time =
> nla_get_u8(tb[NL80211_GSCAN_CHAN_ATTR_DWELL_TIME]);
> +
> + chan->passive =
> nla_get_flag(tb[NL80211_GSCAN_CHAN_ATTR_PASSIVE]);
> + return 0;
> +}
> +
> +static int nl80211_parse_gscan_bucket(struct
> cfg80211_registered_device *rdev,
> +       struct nlattr *nattr,
> +       struct cfg80211_gscan_bucket
> *bucket,
> +       struct cfg80211_gscan_channel
> *channels)
> +{
> + struct nlattr *tb[NL80211_GSCAN_BUCKET_ATTR_MAX + 1];
> + struct nlattr *chan;
> + struct cfg80211_gscan_channel *ch;
> + int err, rem;
> + int num_chans = 0;
> + u32 band_select = 0;
> + u32 dfs_invalid_mask;
> +
> + err = nla_parse(tb, NL80211_GSCAN_BUCKET_ATTR_MAX,
> nla_data(nattr),
> + nla_len(nattr),
> nl80211_gscan_bucket_policy);
> + if (err)
> + return err;
> +
> + if (!tb[NL80211_GSCAN_BUCKET_ATTR_ID] ||
> +     !tb[NL80211_GSCAN_BUCKET_ATTR_PERIOD])
> + return -EINVAL;
> +
> + bucket->idx = nla_get_u32(tb[NL80211_GSCAN_BUCKET_ATTR_ID]);
> + if (tb[NL80211_GSCAN_BUCKET_ATTR_BAND]) {
> + band_select =
> nla_get_u8(tb[NL80211_GSCAN_BUCKET_ATTR_BAND]);
> +
> + /* only makes sense if a band is selected */
> + if (!(band_select & (NL80211_BUCKET_BAND_2GHZ |
> NL80211_BUCKET_BAND_5GHZ)))
> + return -EINVAL;
> + } else if (!tb[NL80211_GSCAN_BUCKET_ATTR_CHANNELS]) {
> + return -EINVAL;
> + }
> +
> + dfs_invalid_mask = NL80211_BUCKET_BAND_5GHZ |
> NL80211_BUCKET_BAND_NODFS |
> +    NL80211_BUCKET_BAND_DFS_ONLY;
> + if ((band_select & dfs_invalid_mask) == dfs_invalid_mask)
> + return -EINVAL;
> +
> + bucket->band = band_select;
> + bucket->period =
> nla_get_u16(tb[NL80211_GSCAN_BUCKET_ATTR_PERIOD]);
> +
> + if (tb[NL80211_GSCAN_BUCKET_ATTR_REPORT])
> + bucket->report_events =
> nla_get_u8(tb[NL80211_GSCAN_BUCKET_ATTR_REPORT]);
> +
> + if (tb[NL80211_GSCAN_BUCKET_ATTR_MAX_PERIOD])
> + bucket->max_period =
> nla_get_u16(tb[NL80211_GSCAN_BUCKET_ATTR_MAX_PERIOD]);
> +
> + if (bucket->max_period) {
> + if (bucket->max_period < bucket->period)
> + return -EINVAL;
> + /* additional attributes required for backoff bucket
> */
> + if (bucket->max_period > bucket->period) {
> + if (!tb[NL80211_GSCAN_BUCKET_ATTR_EXPONENT]
> ||
> +     !tb[NL80211_GSCAN_BUCKET_ATTR_STEPS])
> + return -EINVAL;
> +
> + bucket->exponent =
> nla_get_u8(tb[NL80211_GSCAN_BUCKET_ATTR_EXPONENT]);
> + bucket->step_count =
> nla_get_u8(tb[NL80211_GSCAN_BUCKET_ATTR_STEPS]);
> + }
> + }
> +
> + /* ignore channels if band is specified */
> + if (band_select)
> + return 0;
> +
> +        nla_for_each_nested(chan,
> tb[NL80211_GSCAN_BUCKET_ATTR_CHANNELS], rem) {
> +                num_chans++;
> +        }
> + if (num_chans > 16)
> + return -EINVAL;
> +
> + bucket->n_channels = num_chans;
> + if (!num_chans)
> + return 0;
> +
> + bucket->channels = channels;
> + ch = &bucket->channels[0];
> +        nla_for_each_nested(chan,
> tb[NL80211_GSCAN_BUCKET_ATTR_CHANNELS], rem) {
> + err = nl80211_parse_gscan_channel(rdev, chan, ch);
> + if (err) {
> + return err;
> + }
> + ch++;
> +        }
> +
> + return 0;
> +}
> +
> +static struct cfg80211_gscan_request *
> +nl80211_alloc_gscan_request(struct cfg80211_registered_device *rdev,
> +     struct nlattr *buckets_attr)
> +{
> + struct cfg80211_gscan_request *req;
> + struct cfg80211_gscan_bucket *b;
> + struct cfg80211_gscan_channel *ch;
> + int n_buckets, n_channels;
> + struct nlattr *attr, *bucket, *channel;
> + int rem, rem_b, rem_c;
> + size_t reqsize;
> +
> + if (!buckets_attr)
> + return ERR_PTR(-EINVAL);
> +
> + n_buckets = 0;
> + n_channels = 0;
> + nla_for_each_nested(bucket, buckets_attr, rem) {
> + n_buckets++;
> + if (n_buckets > rdev->wiphy.gscan->max_scan_buckets)
> + return ERR_PTR(-EINVAL);
> +
> + nla_for_each_nested(attr, bucket, rem_b) {
> + if (nla_type(attr) ==
> NL80211_GSCAN_BUCKET_ATTR_CHANNELS) {
> + nla_for_each_nested(channel, attr,
> rem_c)
> + n_channels++;
> + }
> + }
> + }
> +
> + reqsize = sizeof(*req) +
> +   sizeof(*b) * n_buckets +
> +   sizeof(*ch) * n_channels;
> +
> + req = kzalloc(reqsize, GFP_KERNEL);
> + if (!req)
> + return ERR_PTR(-ENOMEM);
> +
> + req->n_buckets = n_buckets;
> + return req;
> +}
> +
> +static int nl80211_parse_gscan_params(struct
> cfg80211_registered_device *rdev,
> +       struct nlattr *attrs[],
> +       struct cfg80211_gscan_request
> **request)
> +{
> + struct cfg80211_gscan_request *req;
> + struct nlattr *tb[NL80211_GSCAN_ATTR_MAX + 1];
> + struct nlattr *bucket;
> + struct cfg80211_gscan_bucket *b;
> + struct cfg80211_gscan_channel *ch;
> + int err, rem, i;
> + u32 bucket_map;
> +
> + if (!attrs[NL80211_ATTR_GSCAN_PARAMS])
> + return -EINVAL;
> +
> + err = nla_parse(tb, NL80211_GSCAN_ATTR_MAX,
> + nla_data(attrs[NL80211_ATTR_GSCAN_PARAMS]),
> + nla_len(attrs[NL80211_ATTR_GSCAN_PARAMS]),
> + nl80211_gscan_policy);
> + if (err)
> + return err;
> +
> + req = nl80211_alloc_gscan_request(rdev,
> tb[NL80211_GSCAN_ATTR_BUCKETS]);
> + if (IS_ERR(req))
> + return PTR_ERR(req);
> +
> + if (tb[NL80211_GSCAN_ATTR_MAX_AP_PER_SCAN])
> + req->max_ap_per_scan =
> nla_get_u8(tb[NL80211_GSCAN_ATTR_MAX_AP_PER_SCAN]);
> + if (tb[NL80211_GSCAN_ATTR_REPORT_PERC])
> + req->report_threshold_percent =
> nla_get_u8(tb[NL80211_GSCAN_ATTR_REPORT_PERC]);
> + if (tb[NL80211_GSCAN_ATTR_REPORT_SCANS])
> + req->report_threshold_num_scans =
> nla_get_u8(tb[NL80211_GSCAN_ATTR_REPORT_SCANS]);
> +
> + b = &req->buckets[0];
> + ch = (struct cfg80211_gscan_channel *)(&req->buckets[req-
> >n_buckets]);
> + nla_for_each_nested(bucket, tb[NL80211_GSCAN_ATTR_BUCKETS],
> rem) {
> + err = nl80211_parse_gscan_bucket(rdev, bucket, b,
> ch);
> + if (err)
> + goto free_req;
> + ch += b->n_channels;
> + b++;
> + }
> + bucket_map = 0;
> + for (i = 0; i < req->n_buckets; i++) {
> + if (BIT(req->buckets[i].idx) & bucket_map) {
> + err = -EINVAL;
> + goto free_req;
> + }
> + bucket_map |= BIT(req->buckets[i].idx);
> +
> + if (req->base_period)
> + req->base_period = gcd(req-
> >buckets[i].period,
> +        req->base_period);
> + else
> + req->base_period = req->buckets[i].period;
> + }
> + *request = req;
> + return 0;
> +
> +free_req:
> + kfree(req);
> + return err;
> +}
> +
> +static int nl80211_start_gscan(struct sk_buff *skb, struct genl_info
> *info)
> +{
> + struct cfg80211_gscan_request *request;
> + struct cfg80211_registered_device *rdev = info->user_ptr[0];
> + struct net_device *dev = info->user_ptr[1];
> + struct wireless_dev *wdev = dev->ieee80211_ptr;
> + int err;
> +
> + if (!rdev->wiphy.gscan ||
> +     !rdev->ops->start_gscan)
> + return -EOPNOTSUPP;
> +
> + if (rdev->gscan_req)
> + return -EINPROGRESS;
> +
> + err = nl80211_parse_gscan_params(rdev, info->attrs,
> &request);
> + if (err)
> + return err;
> +
> + if (info->attrs[NL80211_ATTR_MAC])
> + memcpy(request->mac, nla_data(info-
> >attrs[NL80211_ATTR_MAC]),
> +        ETH_ALEN);
> + if (info->attrs[NL80211_ATTR_MAC_MASK])
> + memcpy(request->mac_mask,
> +        nla_data(info->attrs[NL80211_ATTR_MAC_MASK]),
> ETH_ALEN);
> + if (info->attrs[NL80211_ATTR_SCAN_FLAGS]) {
> + request->flags = nla_get_u32(info-
> >attrs[NL80211_ATTR_SCAN_FLAGS]);
> + if (request->flags & NL80211_SCAN_FLAG_RANDOM_ADDR
> &&
> +     (!info->attrs[NL80211_ATTR_MAC] ||
> +      !info->attrs[NL80211_ATTR_MAC_MASK])) {
> + kfree(request);
> + return -EINVAL;
> + }
> + }
> +
> + wdev_lock(wdev);
> + err = rdev_start_gscan(rdev, dev, request);
> + wdev_unlock(wdev);
> + if (err) {
> + kfree(request);
> + return err;
> + }
> +
> + request->scan_start = jiffies;
> + request->dev = dev;
> + if (info->attrs[NL80211_ATTR_SOCKET_OWNER])
> + request->owner_nlportid = info->snd_portid;
> +
> + rcu_assign_pointer(rdev->gscan_req, request);
> +
> + nl80211_send_scan_event(rdev, dev, NL80211_CMD_START_GSCAN);
> + return 0;
> +}
> +
> +static int nl80211_stop_gscan(struct sk_buff *skb, struct genl_info
> *info)
> +{
> + struct cfg80211_registered_device *rdev = info->user_ptr[0];
> +
> + if (!rdev->wiphy.gscan ||
> +     !rdev->ops->stop_gscan)
> + return -EOPNOTSUPP;
> +
> + return __cfg80211_stop_gscan(rdev, false);
> +}
> +
>  #define NL80211_FLAG_NEED_WIPHY 0x01
>  #define NL80211_FLAG_NEED_NETDEV 0x02
>  #define NL80211_FLAG_NEED_RTNL 0x04
> @@ -12735,6 +13053,22 @@ static void nl80211_post_doit(const struct
> genl_ops *ops, struct sk_buff *skb,
>   .internal_flags = NL80211_FLAG_NEED_NETDEV |
>     NL80211_FLAG_NEED_RTNL,
>   },
> + {
> + .cmd = NL80211_CMD_START_GSCAN,
> + .doit = nl80211_start_gscan,
> + .policy = nl80211_policy,
> + .flags = GENL_UNS_ADMIN_PERM,
> + .internal_flags = NL80211_FLAG_NEED_NETDEV_UP |
> +   NL80211_FLAG_NEED_RTNL,
> + },
> + {
> + .cmd = NL80211_CMD_STOP_GSCAN,
> + .doit = nl80211_stop_gscan,
> + .policy = nl80211_policy,
> + .flags = GENL_UNS_ADMIN_PERM,
> + .internal_flags = NL80211_FLAG_NEED_NETDEV_UP |
> +   NL80211_FLAG_NEED_RTNL,
> + },
>  };
>
>  static struct genl_family nl80211_fam __ro_after_init = {
> @@ -14540,12 +14874,18 @@ static int nl80211_netlink_notify(struct
> notifier_block * nb,
>   list_for_each_entry_rcu(rdev, &cfg80211_rdev_list, list) {
>   bool schedule_destroy_work = false;
>   bool schedule_scan_stop = false;
> + bool schedule_gscan_stop = false;
>   struct cfg80211_sched_scan_request *sched_scan_req =
>   rcu_dereference(rdev->sched_scan_req);
> + struct cfg80211_gscan_request *gscan_req =
> + rcu_dereference(rdev->gscan_req);
>
>   if (sched_scan_req && notify->portid &&
>       sched_scan_req->owner_nlportid == notify-
> >portid)
>   schedule_scan_stop = true;
> + if (gscan_req && notify->portid &&
> +     gscan_req->owner_nlportid == notify->portid)
> + schedule_gscan_stop = true;
>
>   list_for_each_entry_rcu(wdev, &rdev-
> >wiphy.wdev_list, list) {
>   cfg80211_mlme_unregister_socket(wdev,
> notify->portid);
> @@ -14576,12 +14916,18 @@ static int nl80211_netlink_notify(struct
> notifier_block * nb,
>   spin_unlock(&rdev-
> >destroy_list_lock);
>   schedule_work(&rdev->destroy_work);
>   }
> - } else if (schedule_scan_stop) {
> - sched_scan_req->owner_nlportid = 0;
> + } else {
> + if (schedule_scan_stop) {
> + sched_scan_req->owner_nlportid = 0;
>
> - if (rdev->ops->sched_scan_stop &&
> -     rdev->wiphy.flags &
> WIPHY_FLAG_SUPPORTS_SCHED_SCAN)
> - schedule_work(&rdev-
> >sched_scan_stop_wk);
> + if (rdev->ops->sched_scan_stop &&
> +     rdev->wiphy.flags &
> WIPHY_FLAG_SUPPORTS_SCHED_SCAN)
> + schedule_work(&rdev-
> >sched_scan_stop_wk);
> + }
> + if (schedule_gscan_stop) {
> + gscan_req->owner_nlportid = 0;
> + schedule_work(&rdev->gscan_stop_wk);
> + }
>   }
>   }
>
> diff --git a/net/wireless/rdev-ops.h b/net/wireless/rdev-ops.h
> index 2f42507..196e6a7 100644
> --- a/net/wireless/rdev-ops.h
> +++ b/net/wireless/rdev-ops.h
> @@ -1153,4 +1153,29 @@ static inline int rdev_set_qos_map(struct
> cfg80211_registered_device *rdev,
>   trace_rdev_return_int(&rdev->wiphy, ret);
>   return ret;
>  }
> +
> +static inline int
> +rdev_start_gscan(struct cfg80211_registered_device *rdev,
> +  struct net_device *dev,
> +  struct cfg80211_gscan_request *request)
> +{
> + int ret;
> +
> + trace_rdev_start_gscan(&rdev->wiphy, dev);
> + ret = rdev->ops->start_gscan(&rdev->wiphy, dev, request);
> + trace_rdev_return_int(&rdev->wiphy, ret);
> + return ret;
> +}
> +
> +static inline int
> +rdev_stop_gscan(struct cfg80211_registered_device *rdev,
> + struct net_device *dev)
> +{
> + int ret;
> +
> + trace_rdev_stop_gscan(&rdev->wiphy, dev);
> + ret = rdev->ops->stop_gscan(&rdev->wiphy, dev);
> + trace_rdev_return_int(&rdev->wiphy, ret);
> + return ret;
> +}
>  #endif /* __CFG80211_RDEV_OPS */
> diff --git a/net/wireless/scan.c b/net/wireless/scan.c
> index 174076b..8c141c2 100644
> --- a/net/wireless/scan.c
> +++ b/net/wireless/scan.c
> @@ -386,6 +386,34 @@ int __cfg80211_stop_sched_scan(struct
> cfg80211_registered_device *rdev,
>   return 0;
>  }
>
> +int __cfg80211_stop_gscan(struct cfg80211_registered_device *rdev,
> +   bool driver_initiated)
> +{
> + struct cfg80211_gscan_request *gscan_req;
> + struct net_device *dev;
> +
> + ASSERT_RTNL();
> +
> + if (!rdev->gscan_req)
> + return -ENOENT;
> +
> + gscan_req = rtnl_dereference(rdev->gscan_req);
> + dev = gscan_req->dev;
> +
> + if (!driver_initiated) {
> + int err = rdev_stop_gscan(rdev, dev);
> + if (err)
> + return err;
> + }
> +
> + nl80211_send_scan_event(rdev, dev,
> NL80211_CMD_GSCAN_STOPPED);
> +
> + RCU_INIT_POINTER(rdev->gscan_req, NULL);
> + kfree_rcu(gscan_req, rcu_head);
> +
> + return 0;
> +}
> +
>  void cfg80211_bss_age(struct cfg80211_registered_device *rdev,
>                        unsigned long age_secs)
>  {
> diff --git a/net/wireless/trace.h b/net/wireless/trace.h
> index ea1b47e..1d0fde9 100644
> --- a/net/wireless/trace.h
> +++ b/net/wireless/trace.h
> @@ -3067,6 +3067,15 @@
>     WIPHY_PR_ARG, NETDEV_PR_ARG,
>     BOOL_TO_STR(__entry->enabled))
>  );
> +
> +DEFINE_EVENT(wiphy_netdev_evt, rdev_start_gscan,
> + TP_PROTO(struct wiphy *wiphy, struct net_device *netdev),
> + TP_ARGS(wiphy, netdev)
> +);
> +DEFINE_EVENT(wiphy_netdev_evt, rdev_stop_gscan,
> + TP_PROTO(struct wiphy *wiphy, struct net_device *netdev),
> + TP_ARGS(wiphy, netdev)
> +);
>  #endif /* !__RDEV_OPS_TRACE || TRACE_HEADER_MULTI_READ */
>
>  #undef TRACE_INCLUDE_PATH
> --
> 1.9.1
>

2016-12-12 12:00:21

by Arend Van Spriel

[permalink] [raw]
Subject: [RFC V3 11/11] brcmfmac: allow gscan to run concurrent with scheduled scan

On Android the gscan functionality is initiated by the wifi framework
whereas the scheduled scan can be initiated by wpa_supplicant. As such
it is required that these two scan types can run simultaneously.

Reviewed-by: Hante Meuleman <[email protected]>
Reviewed-by: Pieter-Paul Giesberts <[email protected]>
Reviewed-by: Franky Lin <[email protected]>
Signed-off-by: Arend van Spriel <[email protected]>
---
.../broadcom/brcm80211/brcmfmac/cfg80211.c | 12 +-
.../broadcom/brcm80211/brcmfmac/cfg80211.h | 2 +
.../wireless/broadcom/brcm80211/brcmfmac/core.c | 5 +-
.../wireless/broadcom/brcm80211/brcmfmac/debug.h | 2 +
.../net/wireless/broadcom/brcm80211/brcmfmac/pno.c | 278 ++++++++++++++++-----
.../net/wireless/broadcom/brcm80211/brcmfmac/pno.h | 28 +++
6 files changed, 267 insertions(+), 60 deletions(-)

diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c
index 61636ad..7749610 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c
@@ -3432,7 +3432,7 @@ static int brcmf_cfg80211_sched_scan_stop(struct wiphy *wiphy,
struct brcmf_if *ifp = netdev_priv(ndev);

brcmf_dbg(SCAN, "enter\n");
- brcmf_pno_clean(ifp);
+ brcmf_pno_stop_sched_scan(ifp);
if (cfg->escan_rid)
brcmf_notify_escan_complete(cfg, ifp, true, true);
return 0;
@@ -5256,7 +5256,7 @@ static int brcmf_cfg80211_stop_gscan(struct wiphy *wiphy,

brcmf_dbg(TRACE, "Enter, bssidx=%d\n", ifp->bsscfgidx);

- return brcmf_pno_clean(ifp);
+ return brcmf_pno_stop_gscan(ifp);
}

#ifdef CONFIG_PM
@@ -7076,6 +7076,13 @@ struct brcmf_cfg80211_info *brcmf_cfg80211_attach(struct brcmf_pub *drvr,
brcmf_p2p_detach(&cfg->p2p);
goto wiphy_unreg_out;
}
+ err = brcmf_pno_attach(ifp);
+ if (err) {
+ brcmf_err("PNO initialisation failed (%d)\n", err);
+ brcmf_btcoex_detach(cfg);
+ brcmf_p2p_detach(&cfg->p2p);
+ goto wiphy_unreg_out;
+ }

if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_TDLS)) {
err = brcmf_fil_iovar_int_set(ifp, "tdls_enable", 1);
@@ -7108,6 +7115,7 @@ struct brcmf_cfg80211_info *brcmf_cfg80211_attach(struct brcmf_pub *drvr,
return cfg;

detach:
+ brcmf_pno_detach(ifp);
brcmf_btcoex_detach(cfg);
brcmf_p2p_detach(&cfg->p2p);
wiphy_unreg_out:
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.h
index ff65970..b9f9375 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.h
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.h
@@ -293,6 +293,7 @@ enum brcmf_internal_escan_requestor {
* @vif_cnt: number of vif instances.
* @vif_event: vif event signalling.
* @wowl: wowl related information.
+ * @pi: information of pno module.
*/
struct brcmf_cfg80211_info {
struct wiphy *wiphy;
@@ -326,6 +327,7 @@ struct brcmf_cfg80211_info {
struct brcmu_d11inf d11inf;
struct brcmf_assoclist_le assoclist;
struct brcmf_cfg80211_wowl wowl;
+ struct brcmf_pno_info *pi;
};

/**
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c
index 9e6f60a..c72653d1 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c
@@ -30,6 +30,7 @@
#include "debug.h"
#include "fwil_types.h"
#include "p2p.h"
+#include "pno.h"
#include "cfg80211.h"
#include "fwil.h"
#include "fwsignal.h"
@@ -1118,8 +1119,10 @@ void brcmf_detach(struct device *dev)

/* stop firmware event handling */
brcmf_fweh_detach(drvr);
- if (drvr->config)
+ if (drvr->config) {
+ brcmf_pno_detach(bus_if->drvr->iflist[0]);
brcmf_p2p_detach(&drvr->config->p2p);
+ }

brcmf_bus_change_state(bus_if, BRCMF_BUS_DOWN);

diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/debug.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/debug.h
index 6687812..efc17b9 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/debug.h
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/debug.h
@@ -82,6 +82,7 @@
#define BRCMF_EVENT_ON() (brcmf_msg_level & BRCMF_EVENT_VAL)
#define BRCMF_FIL_ON() (brcmf_msg_level & BRCMF_FIL_VAL)
#define BRCMF_FWCON_ON() (brcmf_msg_level & BRCMF_FWCON_VAL)
+#define BRCMF_SCAN_ON() (brcmf_msg_level & BRCMF_SCAN_VAL)

#else /* defined(DEBUG) || defined(CONFIG_BRCM_TRACING) */

@@ -95,6 +96,7 @@
#define BRCMF_EVENT_ON() 0
#define BRCMF_FIL_ON() 0
#define BRCMF_FWCON_ON() 0
+#define BRCMF_SCAN_ON() 0

#endif /* defined(DEBUG) || defined(CONFIG_BRCM_TRACING) */

diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pno.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pno.c
index b868997..c687f21 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pno.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pno.c
@@ -14,6 +14,7 @@
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <linux/netdevice.h>
+#include <linux/gcd.h>
#include <net/cfg80211.h>

#include "core.h"
@@ -38,6 +39,13 @@
#define GSCAN_BATCH_NO_THR_SET 101
#define GSCAN_RETRY_THRESHOLD 3

+struct brcmf_pno_info {
+ struct cfg80211_sched_scan_request *sched;
+ struct cfg80211_gscan_request *gscan;
+};
+
+#define ifp_to_pno(_ifp) (_ifp)->drvr->config->pi
+
static int brcmf_pno_channel_config(struct brcmf_if *ifp,
struct brcmf_pno_config_le *cfg)
{
@@ -182,14 +190,46 @@ int brcmf_pno_clean(struct brcmf_if *ifp)
return ret;
}

+static void brcmf_pno_config_ssids(struct brcmf_if *ifp,
+ struct cfg80211_sched_scan_request *req)
+{
+ struct cfg80211_ssid *ssid;
+ int ret, i;
+
+ /* configure each match set */
+ for (i = 0; i < req->n_match_sets; i++) {
+
+ ssid = &req->match_sets[i].ssid;
+
+ if (!ssid->ssid_len) {
+ brcmf_err("skip broadcast ssid\n");
+ continue;
+ }
+
+ ret = brcmf_pno_add_ssid(ifp, ssid,
+ brcmf_is_ssid_active(ssid, req));
+ if (ret < 0)
+ brcmf_dbg(SCAN, ">>> PNO filter %s for ssid (%s)\n",
+ ret == 0 ? "set" : "failed", ssid->ssid);
+ }
+}
+
int brcmf_pno_start_sched_scan(struct brcmf_if *ifp,
struct cfg80211_sched_scan_request *req)
{
+ struct brcmf_pno_info *pi;
struct brcmf_pno_config_le pno_cfg;
- struct cfg80211_ssid *ssid;
u16 chan;
int i, ret;

+ pi = ifp_to_pno(ifp);
+
+ /* g-scan + scheduled scan is handled separately */
+ if (pi->gscan) {
+ pi->sched = req;
+ return brcmf_pno_start_gscan(ifp, pi->gscan);
+ }
+
/* clean up everything */
ret = brcmf_pno_clean(ifp);
if (ret < 0) {
@@ -217,31 +257,49 @@ int brcmf_pno_start_sched_scan(struct brcmf_if *ifp,
}
if (req->n_channels) {
pno_cfg.channel_num = cpu_to_le32(req->n_channels);
- brcmf_pno_channel_config(ifp, &pno_cfg);
+ ret = brcmf_pno_channel_config(ifp, &pno_cfg);
+ if (ret < 0)
+ return ret;
}

- /* configure each match set */
- for (i = 0; i < req->n_match_sets; i++) {
- ssid = &req->match_sets[i].ssid;
- if (!ssid->ssid_len) {
- brcmf_err("skip broadcast ssid\n");
- continue;
- }
+ brcmf_pno_config_ssids(ifp, req);

- ret = brcmf_pno_add_ssid(ifp, ssid,
- brcmf_is_ssid_active(ssid, req));
- if (ret < 0)
- brcmf_dbg(SCAN, ">>> PNO filter %s for ssid (%s)\n",
- ret == 0 ? "set" : "failed", ssid->ssid);
- }
/* Enable the PNO */
ret = brcmf_fil_iovar_int_set(ifp, "pfn", 1);
- if (ret < 0)
+ if (ret < 0) {
brcmf_err("PNO enable failed!! ret=%d\n", ret);
+ return ret;
+ }

+ /* keep reference of request */
+ ifp_to_pno(ifp)->sched = req;
return ret;
}

+int brcmf_pno_stop_sched_scan(struct brcmf_if *ifp)
+{
+ struct brcmf_cfg80211_info *cfg;
+ int ret;
+
+ cfg = ifp->drvr->config;
+ if (!cfg->pi)
+ return 0;
+
+ /* may need to reconfigure gscan */
+ cfg->pi->sched = NULL;
+ if (cfg->pi->gscan) {
+ brcmf_dbg(SCAN, "reconfigure gscan\n");
+ ret = brcmf_pno_start_gscan(ifp, cfg->pi->gscan);
+ if (ret < 0) {
+ brcmf_err("gscan reconfiguration failed: err=%d\n", ret);
+ cfg80211_gscan_stopped_rtnl(cfg->wiphy);
+ }
+ } else {
+ brcmf_pno_clean(ifp);
+ }
+ return 0;
+}
+
static int brcmf_pno_get_bucket_channels(struct brcmf_if *ifp,
struct cfg80211_gscan_bucket *b,
struct brcmf_pno_config_le *pno_cfg)
@@ -260,8 +318,7 @@ static int brcmf_pno_get_bucket_channels(struct brcmf_if *ifp,
goto done;
}
chan = b->channels[i].ch->hw_value;
- brcmf_dbg(INFO, "[%d] Chan : %u\n",
- n_chan, chan);
+ brcmf_dbg(INFO, "[%d] Chan : %u\n", n_chan, chan);
pno_cfg->channel_list[n_chan++] = cpu_to_le16(chan);
}
if (b->band & NL80211_BUCKET_BAND_2GHZ) {
@@ -274,8 +331,7 @@ static int brcmf_pno_get_bucket_channels(struct brcmf_if *ifp,
goto done;
}
chan = band->channels[i].hw_value;
- brcmf_dbg(INFO, "[%d] Chan : %u\n",
- n_chan, chan);
+ brcmf_dbg(INFO, "[%d] Chan : %u\n", n_chan, chan);
pno_cfg->channel_list[n_chan++] = cpu_to_le16(chan);
}
}
@@ -296,8 +352,7 @@ static int brcmf_pno_get_bucket_channels(struct brcmf_if *ifp,
goto done;
}
chan = band->channels[i].hw_value;
- brcmf_dbg(INFO, "[%d] Chan : %u\n",
- n_chan, chan);
+ brcmf_dbg(INFO, "[%d] Chan : %u\n", n_chan, chan);
pno_cfg->channel_list[n_chan++] = cpu_to_le16(chan);
}
}
@@ -311,14 +366,31 @@ static int brcmf_pno_get_bucket_channels(struct brcmf_if *ifp,
static int brcmf_pno_prepare_gscan(struct brcmf_if *ifp,
struct cfg80211_gscan_request *req,
struct brcmf_pno_config_le *pno_cfg,
- struct brcmf_gscan_bucket_config **buckets)
+ struct brcmf_gscan_bucket_config **buckets,
+ u32 *scan_freq)
{
+ struct cfg80211_sched_scan_request *sr;
struct brcmf_gscan_bucket_config *fw_buckets;
struct cfg80211_gscan_bucket *bucket;
- int i, err, chidx;
+ int i, err, chidx, n_fw_buckets;
+ u32 n_chan;
+ u16 chan;
+
+ sr = ifp_to_pno(ifp)->sched;
+ *scan_freq = req->base_period;
+ n_fw_buckets = req->n_buckets;
+ /*
+ * scheduled scan uses an additional bucket in firmware
+ * and the actual scan period must be the gcd.
+ */
+ if (sr) {
+ *scan_freq = gcd(sr->scan_plans[0].interval, *scan_freq);
+ n_fw_buckets++;
+ brcmf_dbg(SCAN, "g-scan+scheduled: period=%u\n", *scan_freq);
+ }

*buckets = NULL;
- fw_buckets = kcalloc(req->n_buckets, sizeof(*buckets[0]), GFP_KERNEL);
+ fw_buckets = kcalloc(n_fw_buckets, sizeof(*buckets[0]), GFP_KERNEL);
if (!fw_buckets)
return -ENOMEM;

@@ -331,14 +403,46 @@ static int brcmf_pno_prepare_gscan(struct brcmf_if *ifp,
goto fail;
}
fw_buckets[i].bucket_end_index = chidx - 1;
- fw_buckets[i].bucket_freq_multiple = bucket->period / req->base_period;
+ fw_buckets[i].bucket_freq_multiple =
+ bucket->period / *scan_freq;
fw_buckets[i].repeat = cpu_to_le16(bucket->step_count);
- fw_buckets[i].max_freq_multiple = cpu_to_le16(bucket->max_period / req->base_period);
- fw_buckets[i].flag = bucket->report_events ^ NL80211_BUCKET_REPORT_NO_BATCH;
+ fw_buckets[i].max_freq_multiple =
+ cpu_to_le16(bucket->max_period / *scan_freq);
+ fw_buckets[i].flag =
+ bucket->report_events ^ NL80211_BUCKET_REPORT_NO_BATCH;
bucket++;
}
+
+ /* additional scheduled scan bucket */
+ if (sr) {
+ fw_buckets[i].bucket_freq_multiple =
+ sr->scan_plans[0].interval / *scan_freq;
+ n_chan = le32_to_cpu(pno_cfg->channel_num);
+ for (chidx = 0; chidx < sr->n_channels; chidx++) {
+ if (n_chan >= BRCMF_NUMCHANNELS) {
+ err = -ENOSPC;
+ goto fail;
+ }
+ chan = sr->channels[chidx]->hw_value;
+ brcmf_dbg(INFO, "[%d] Chan : %u\n", n_chan, chan);
+ pno_cfg->channel_list[n_chan++] = cpu_to_le16(chan);
+ }
+ pno_cfg->channel_num = cpu_to_le32(n_chan);
+ fw_buckets[i].bucket_end_index = n_chan - 1;
+ }
+
+ if (BRCMF_SCAN_ON()) {
+ brcmf_err("base period=%u\n", *scan_freq);
+ for (i = 0; i < n_fw_buckets; i++) {
+ brcmf_err("[%d] period %u max %u repeat %u flag %x idx %u\n",
+ i, fw_buckets[i].bucket_freq_multiple,
+ le16_to_cpu(fw_buckets[i].max_freq_multiple),
+ fw_buckets[i].repeat, fw_buckets[i].flag,
+ fw_buckets[i].bucket_end_index);
+ }
+ }
*buckets = fw_buckets;
- return 0;
+ return n_fw_buckets;

fail:
kfree(fw_buckets);
@@ -348,11 +452,25 @@ static int brcmf_pno_prepare_gscan(struct brcmf_if *ifp,
int brcmf_pno_start_gscan(struct brcmf_if *ifp,
struct cfg80211_gscan_request *req)
{
+ struct brcmf_pno_info *pi;
struct brcmf_gscan_config *gscan_cfg;
struct brcmf_gscan_bucket_config *buckets;
struct brcmf_pno_config_le pno_cfg;
- size_t gscan_cfg_size;
- int err;
+ size_t gsz;
+ u32 scan_freq;
+ int err, n_buckets;
+
+ n_buckets = brcmf_pno_prepare_gscan(ifp, req, &pno_cfg, &buckets,
+ &scan_freq);
+ if (n_buckets < 0)
+ return n_buckets;
+
+ gsz = sizeof(*gscan_cfg) + (n_buckets - 1) * sizeof(*buckets);
+ gscan_cfg = kzalloc(gsz, GFP_KERNEL);
+ if (!gscan_cfg) {
+ err = -ENOMEM;
+ goto free_buckets;
+ }

/* clean up everything */
err = brcmf_pno_clean(ifp);
@@ -362,31 +480,11 @@ int brcmf_pno_start_gscan(struct brcmf_if *ifp,
}

/* configure pno */
- err = brcmf_pno_config(ifp, req->base_period / 1000,
- req->report_threshold_num_scans,
+ err = brcmf_pno_config(ifp, scan_freq, req->report_threshold_num_scans,
req->max_ap_per_scan);
if (err < 0)
return err;

- /* configure random mac */
- if (req->flags & NL80211_SCAN_FLAG_RANDOM_ADDR) {
- err = brcmf_pno_set_random(ifp, req->mac, req->mac_mask);
- if (err < 0)
- return err;
- }
-
- err = brcmf_pno_prepare_gscan(ifp, req, &pno_cfg, &buckets);
- if (err < 0)
- return err;
-
- gscan_cfg_size = sizeof(*gscan_cfg) +
- (req->n_buckets - 1) * sizeof(*buckets);
- gscan_cfg = kzalloc(gscan_cfg_size, GFP_KERNEL);
- if (!gscan_cfg) {
- err = -ENOMEM;
- goto free_buckets;
- }
-
err = brcmf_pno_channel_config(ifp, &pno_cfg);
if (err < 0)
goto free_gscan;
@@ -401,19 +499,35 @@ int brcmf_pno_start_gscan(struct brcmf_if *ifp,

gscan_cfg->flags = BRCMF_GSCAN_CFG_ALL_BUCKETS_IN_1ST_SCAN;

- gscan_cfg->count_of_channel_buckets = req->n_buckets;
+ gscan_cfg->count_of_channel_buckets = n_buckets;
memcpy(&gscan_cfg->bucket[0], buckets,
- req->n_buckets * sizeof(*buckets));
+ n_buckets * sizeof(*buckets));

- err = brcmf_fil_iovar_data_set(ifp, "pfn_gscan_cfg", gscan_cfg,
- gscan_cfg_size);
+ err = brcmf_fil_iovar_data_set(ifp, "pfn_gscan_cfg", gscan_cfg, gsz);
if (err < 0)
goto free_gscan;

+ /* configure random mac */
+ if (req->flags & NL80211_SCAN_FLAG_RANDOM_ADDR) {
+ err = brcmf_pno_set_random(ifp, req->mac, req->mac_mask);
+ if (err < 0)
+ return err;
+ }
+
+ pi = ifp_to_pno(ifp);
+ if (pi->sched)
+ brcmf_pno_config_ssids(ifp, pi->sched);
+
/* Enable the PNO */
err = brcmf_fil_iovar_int_set(ifp, "pfn", 1);
- if (err < 0)
+ if (err < 0) {
brcmf_err("PNO enable failed!! ret=%d\n", err);
+ goto free_gscan;
+ }
+
+ /* keep reference of request */
+ pi->gscan = req;
+
free_gscan:
kfree(gscan_cfg);
free_buckets:
@@ -421,3 +535,53 @@ int brcmf_pno_start_gscan(struct brcmf_if *ifp,
return err;
}

+int brcmf_pno_stop_gscan(struct brcmf_if *ifp)
+{
+ struct brcmf_cfg80211_info *cfg;
+ int ret;
+
+ cfg = ifp->drvr->config;
+ if (!cfg->pi)
+ return 0;
+
+ cfg->pi->gscan = NULL;
+ if (cfg->pi->sched) {
+ brcmf_dbg(SCAN, "reconfigure scheduled scan\n");
+ ret = brcmf_pno_start_sched_scan(ifp, cfg->pi->sched);
+ if (ret < 0) {
+ brcmf_err("scheduled scan reconfiguration failed: err=%d\n",
+ ret);
+ cfg80211_sched_scan_stopped_rtnl(cfg->wiphy);
+ }
+ } else {
+ brcmf_pno_clean(ifp);
+ }
+ return 0;
+}
+
+int brcmf_pno_attach(struct brcmf_if *ifp)
+{
+ struct brcmf_pno_info *pi;
+
+ brcmf_err("enter\n");
+ pi = kzalloc(sizeof(*pi), GFP_KERNEL);
+ if (!pi)
+ return -ENOMEM;
+
+ ifp_to_pno(ifp) = pi;
+ return 0;
+}
+
+void brcmf_pno_detach(struct brcmf_if *ifp)
+{
+ struct brcmf_cfg80211_info *cfg;
+ struct brcmf_pno_info *pi;
+
+ brcmf_err("enter\n");
+ cfg = ifp->drvr->config;
+ pi = cfg->pi;
+ cfg->pi = NULL;
+
+ kfree(pi);
+}
+
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pno.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pno.h
index 06ad3b0..5690ac2 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pno.h
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pno.h
@@ -38,6 +38,13 @@ int brcmf_pno_start_sched_scan(struct brcmf_if *ifp,
struct cfg80211_sched_scan_request *req);

/**
+ * brcmf_pno_stop_sched_scan - terminate scheduled scan on device.
+ *
+ * @ifp: interface object used.
+ */
+int brcmf_pno_stop_sched_scan(struct brcmf_if *ifp);
+
+/**
* brcmf_pno_start_gscan - initiate gscan on device.
*
* @ifp: interface object used.
@@ -46,4 +53,25 @@ int brcmf_pno_start_sched_scan(struct brcmf_if *ifp,
int brcmf_pno_start_gscan(struct brcmf_if *ifp,
struct cfg80211_gscan_request *req);

+/**
+ * brcmf_pno_stop_gscan - terminate gscan on device.
+ *
+ * @ifp: interface object used.
+ */
+int brcmf_pno_stop_gscan(struct brcmf_if *ifp);
+
+/**
+ * brcmf_pno_attach - allocate and attach module information.
+ *
+ * @ifp: interface object used.
+ */
+int brcmf_pno_attach(struct brcmf_if *ifp);
+
+/**
+ * brcmf_pno_detach - detach and free module information.
+ *
+ * @ifp: interface object used.
+ */
+void brcmf_pno_detach(struct brcmf_if *ifp);
+
#endif /* _BRCMF_PNO_H */
--
1.9.1

2016-12-12 20:01:05

by Arend Van Spriel

[permalink] [raw]
Subject: Re: [RFC V3 03/11] nl80211: add support for gscan

On 12-12-2016 18:43, Dan Williams wrote:
>> +
>> +/**
>> + * enum nl80211_bucket_band - GScan bucket band selection.
> Quite possibly this was already covered and somebody requested you to
> change this to the current name. If that's the case, ignore this.

Nope. You are the first ;-)

> But shouldn't this enum and bucket_event_report include "gscan" in
> their name, like the other gscan specific stuff does? Are these going
> to get used for something else too, and will that thing make sense with
> the word "bucket"? Just "nl80211_bucket_band" is pretty generic.

I figured the term bucket was making it gscan specific, but you are
right. Will change it.

Thanks,
Arend

2016-12-14 09:01:32

by Arend Van Spriel

[permalink] [raw]
Subject: Re: [RFC V3 03/11] nl80211: add support for gscan



On 13-12-2016 23:29, Johannes Berg wrote:
> On Tue, 2016-12-13 at 21:09 +0100, Arend Van Spriel wrote:
>>
>>> There's a bit of a weird hard-coded restriction to 16 channels too,
>>> that's due to the bucket map?
>>
>> Uhm. Is there? I will check, but if you can give me a pointer where
>> to look it is appreciated.
>
> Just look for "< 16" or "<= 16" or so in the patch. I do think that's
> because the channel map is a u16 though, not sure we'd want to change
> that.

Had to look for "> 16" ;-)

> + /* ignore channels if band is specified */
> + if (band_select)
> + return 0;
> +
> + nla_for_each_nested(chan,
tb[NL80211_GSCAN_BUCKET_ATTR_CHANNELS], rem) {
> + num_chans++;
> + }

Here an instance of the tab vs. space issue you mentioned. Will go over
the patch and fix that.

> + if (num_chans > 16)
> + return -EINVAL;

I suspect this is the restriction you were referring to. There is no
reason for this although the android wifi hal has max 16 channels in a
bucket so I might have picked that up. So could a driver have a similar
limit and should we add such to the gscan capabilities? For instance our
firmware api has a nasty restriction of 64 channels for all buckets
together, eg. can do 4 buckets of 16 channels each.

Regards,
Arend

2016-12-12 12:00:18

by Arend Van Spriel

[permalink] [raw]
Subject: [RFC V3 03/11] nl80211: add support for gscan

This patch adds support for GScan which is a scan offload feature
used in Android.

Reviewed-by: Hante Meuleman <[email protected]>
Reviewed-by: Pieter-Paul Giesberts <[email protected]>
Reviewed-by: Franky Lin <[email protected]>
Signed-off-by: Arend van Spriel <[email protected]>
---
Changes:
V2
- remove pr_err() statement from nl80211.c
- get rid of #if 0 code.
V3
- change and document storage type of gscan attributes.
- remove base period attribute from nl80211.
- bucket periods are changed to be seconds.
- change NO_IR attribute to PASSIVE.
- check for NL80211_ATTR_MAC{,_MASK} if random mac support is requested.
- remove NL80211_SCAN_FLAG_IE_DATA.
---
include/net/cfg80211.h | 91 +++++++++++
include/uapi/linux/nl80211.h | 146 ++++++++++++++++++
net/wireless/core.c | 31 ++++
net/wireless/core.h | 4 +
net/wireless/nl80211.c | 356 ++++++++++++++++++++++++++++++++++++++++++-
net/wireless/rdev-ops.h | 25 +++
net/wireless/scan.c | 28 ++++
net/wireless/trace.h | 9 ++
8 files changed, 685 insertions(+), 5 deletions(-)

diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h
index b78377f..8bc8842 100644
--- a/include/net/cfg80211.h
+++ b/include/net/cfg80211.h
@@ -2453,6 +2453,92 @@ struct cfg80211_nan_func {
};

/**
+ * struct cfg80211_gscan_channel - GScan channel parameters.
+ *
+
+ * @ch: specific channel.
+ * @dwell_time: hint for dwell time in milliseconds.
+ * @passive: indicates passive scan is requested.
+ */
+struct cfg80211_gscan_channel {
+ struct ieee80211_channel *ch;
+ u8 dwell_time;
+ bool passive;
+};
+
+/**
+ * struct cfg80211_gscan_bucket - GScan bucket parameters.
+ *
+ * @idx: unique bucket index.
+ * @band: bit flags for band(s) to use, see %enum nl80211_bucket_band.
+ * @report_events: This is a bit field according %enum nl80211_bucket_report_event.
+ * @period: period in which the bucket is scheduled to be scanned. If the
+ * period is too small for driver it should not fail but report results
+ * as fast as it can. For exponential backoff bucket this is the minimum
+ * period.
+ * @max_period: used only for the exponential backoff bucket whose scan period
+ * will grow exponentially to a maximum period of max_period.
+ * @exponent: used only for the exponential backoff bucket.
+ * @step_count: used only for the exponential backoff bucket.
+ * @n_channels: number of channels in @channels array.
+ * @channels: channels to scan which may include DFS channels.
+ */
+struct cfg80211_gscan_bucket {
+ u32 idx;
+ u16 period;
+ u8 band;
+ u8 report_events;
+ u16 max_period;
+ u8 exponent;
+ u8 step_count;
+ u8 n_channels;
+ struct cfg80211_gscan_channel *channels;
+};
+
+/**
+ * struct cfg80211_gscan_request - GScan request parameters.
+ *
+ * @flags: scan request flags according %enum nl80211_scan_flags.
+ * @base_period: base timer period in milliseconds.
+ * @max_ap_per_scan: number of APs to store in each scan entry in the BSSID/RSSI
+ * history buffer (keep APS with highest RSSI).
+ * @report_threshold_percent: wake up system when scan buffer is filled to this
+ * percentage.
+ * @report_threshold_num_scans: wake up system when this many scans are stored
+ * in scan buffer.
+ * @mac: MAC address used for randomisation.
+ * @mac_mask: MAC address mask. bits that are 0 in the mask should be
+ * randomised, bits that are 1 should be taken as is from @mac.
+ * @n_buckets: number of entries in @buckets array.
+ * @buckets: array of GScan buckets.
+ *
+ * @dev: net device for which GScan is requested.
+ * @rcu_head: RCU callback used to free the struct.
+ * @owner_nlportid: netlink port which initiated this request.
+ * @scan_start: start time of this scan in jiffies.
+ */
+struct cfg80211_gscan_request {
+ u32 flags;
+ u16 base_period;
+ u8 max_ap_per_scan;
+ u8 report_threshold_percent;
+ u8 report_threshold_num_scans;
+ u8 mac[ETH_ALEN];
+ u8 mac_mask[ETH_ALEN];
+
+ u8 n_buckets;
+
+ /* internal */
+ struct net_device *dev;
+ struct rcu_head rcu_head;
+ u32 owner_nlportid;
+ unsigned long scan_start;
+
+ /* keep last */
+ struct cfg80211_gscan_bucket buckets[0];
+};
+
+/**
* struct cfg80211_ops - backend description for wireless configuration
*
* This struct is registered by fullmac card drivers and/or wireless stacks
@@ -2764,6 +2850,8 @@ struct cfg80211_nan_func {
* All other parameters must be ignored.
*
* @set_multicast_to_unicast: configure multicast to unicast conversion for BSS
+ * @start_gscan: start the GSCAN scanning offload.
+ * @stop_gscan: stop the GSCAN scanning offload.
*/
struct cfg80211_ops {
int (*suspend)(struct wiphy *wiphy, struct cfg80211_wowlan *wow);
@@ -3048,6 +3136,9 @@ struct cfg80211_ops {
int (*set_multicast_to_unicast)(struct wiphy *wiphy,
struct net_device *dev,
const bool enabled);
+ int (*start_gscan)(struct wiphy *wiphy, struct net_device *dev,
+ struct cfg80211_gscan_request *gscan_req);
+ int (*stop_gscan)(struct wiphy *wiphy, struct net_device *dev);
};

/*
diff --git a/include/uapi/linux/nl80211.h b/include/uapi/linux/nl80211.h
index 01ab2f7..5e42383 100644
--- a/include/uapi/linux/nl80211.h
+++ b/include/uapi/linux/nl80211.h
@@ -894,6 +894,12 @@
* does not result in a change for the current association. Currently,
* only the %NL80211_ATTR_IE data is used and updated with this command.
*
+ * @NL80211_CMD_START_GSCAN: start GScan.
+ * @NL80211_CMD_STOP_GSCAN: request to stop current GScan.
+ * @NL80211_CMD_GSCAN_STOPPED: indicates that the currently running GScan
+ * has stopped. This event is generated upon @NL80211_CMD_STOP_GSCAN and
+ * the driver may issue this event at any time when a GScan is running.
+ *
* @NL80211_CMD_MAX: highest used command number
* @__NL80211_CMD_AFTER_LAST: internal use
*/
@@ -1093,6 +1099,10 @@ enum nl80211_commands {

NL80211_CMD_UPDATE_CONNECT_PARAMS,

+ NL80211_CMD_START_GSCAN,
+ NL80211_CMD_STOP_GSCAN,
+ NL80211_CMD_GSCAN_STOPPED,
+
/* add new commands above here */

/* used to define NL80211_CMD_MAX below */
@@ -2389,6 +2399,7 @@ enum nl80211_attrs {

NL80211_ATTR_BSSID,
NL80211_ATTR_GSCAN_CAPS,
+ NL80211_ATTR_GSCAN_PARAMS,

/* add attributes here, update the policy in nl80211.c */

@@ -5246,4 +5257,139 @@ enum nl80211_gscan_caps_attr {
NL80211_GSCAN_CAPS_ATTR_MAX = __NL80211_GSCAN_CAPS_ATTR_AFTER_LAST - 1
};

+/**
+ * enum nl80211_gscan_attr - common GScan parameters.
+ *
+ * @__NL80211_GSCAN_ATTR_INVALID: reserved.
+ * @NL80211_GSCAN_ATTR_MAX_AP_PER_SCAN: number of APs that are kept per
+ * scan. The kept APs are the ones with strongest RSSI level (u8).
+ * @NL80211_GSCAN_ATTR_REPORT_PERC: threshold specifying percentage of
+ * scan cache filled that should trigger event for scan results (u8).
+ * @NL80211_GSCAN_ATTR_REPORT_SCANS: threshold specifying number of scans
+ * after which an event is expected for scan results (u8).
+ * @NL80211_GSCAN_ATTR_BUCKETS: nested attribute specifying
+ * per-bucket parameters for GScan. See %enum nl80211_gscan_bucket_attr
+ * for description.
+ * @NL80211_GSCAN_ATTR_MAX: highest GScan attribute.
+ * @__NL80211_GSCAN_ATTR_AFTER_LAST: internal use.
+ */
+enum nl80211_gscan_attr {
+ __NL80211_GSCAN_ATTR_INVALID,
+ NL80211_GSCAN_ATTR_MAX_AP_PER_SCAN,
+ NL80211_GSCAN_ATTR_REPORT_PERC,
+ NL80211_GSCAN_ATTR_REPORT_SCANS,
+ NL80211_GSCAN_ATTR_BUCKETS,
+
+ /* keep last */
+ __NL80211_GSCAN_ATTR_AFTER_LAST,
+ NL80211_GSCAN_ATTR_MAX = __NL80211_GSCAN_ATTR_AFTER_LAST - 1
+};
+
+/**
+ * enum nl80211_gscan_bucket_attr - per-bucket GScan parameters.
+ *
+ * @__NL80211_GSCAN_BUCKET_ATTR_INVALID,
+ * @NL80211_GSCAN_BUCKET_ATTR_ID: unique bucket id (u32).
+ * @NL80211_GSCAN_BUCKET_ATTR_BAND: specifies the band to be scanned
+ * according %enum nl80211_bucket_band. If specified
+ * @NL80211_GSCAN_BUCKET_ATTR_CHANNELS is ignored (u8).
+ * @NL80211_GSCAN_BUCKET_ATTR_PERIOD: specifies the period between consecutive
+ * scans of this bucket in seconds. For the backoff bucket this is
+ * period(0) (u16).
+ * @NL80211_GSCAN_BUCKET_ATTR_REPORT: specifies reporting flags according
+ * %enum nl80211_bucket_report_event (u8).
+ * @NL80211_GSCAN_BUCKET_ATTR_MAX_PERIOD: maximum period between
+ * consecutive scans. If specified this is a backoff bucket in
+ * which the period increases according formula:
+ * period(N) = period(0) * (base ^ (N/step_count)) (u16)
+ * @NL80211_GSCAN_BUCKET_ATTR_EXPONENT: exponential base value as used
+ * in given formula. This attribute is required when
+ * @NL80211_GSCAN_BUCKET_ATTR_MAX_PERIOD is specified (u8).
+ * @NL80211_GSCAN_BUCKET_ATTR_STEPS: step count as used in given formula.
+ * This attribute is required when @NL80211_GSCAN_BUCKET_ATTR_MAX_PERIOD
+ * is specified (u8).
+ * @NL80211_GSCAN_BUCKET_ATTR_CHANNELS: nested attribute specifying the
+ * channels that are to be scanned for this bucket.
+ * @NL80211_GSCAN_BUCKET_ATTR_MAX: highest GScan bucket attribute.
+ * @__NL80211_GSCAN_BUCKET_ATTR_AFTER_LAST: internal use.
+ */
+enum nl80211_gscan_bucket_attr {
+ __NL80211_GSCAN_BUCKET_ATTR_INVALID,
+ NL80211_GSCAN_BUCKET_ATTR_ID,
+ NL80211_GSCAN_BUCKET_ATTR_BAND,
+ NL80211_GSCAN_BUCKET_ATTR_PERIOD,
+ NL80211_GSCAN_BUCKET_ATTR_REPORT,
+ NL80211_GSCAN_BUCKET_ATTR_MAX_PERIOD,
+ NL80211_GSCAN_BUCKET_ATTR_EXPONENT,
+ NL80211_GSCAN_BUCKET_ATTR_STEPS,
+ NL80211_GSCAN_BUCKET_ATTR_CHANNELS,
+
+ /* keep last */
+ __NL80211_GSCAN_BUCKET_ATTR_AFTER_LAST,
+ NL80211_GSCAN_BUCKET_ATTR_MAX = __NL80211_GSCAN_BUCKET_ATTR_AFTER_LAST - 1
+};
+
+/**
+ * enum nl80211_gscan_chan_attr - GScan bucket channel parameters.
+ *
+ * @__NL80211_GSCAN_CHAN_ATTR_INVALID: reserved.
+ * @NL80211_GSCAN_CHAN_ATTR_FREQ: frequency of channel to be scanned (u32).
+ * @NL80211_GSCAN_CHAN_ATTR_DWELL_TIME: dwell time in milliseconds to stay
+ * on this channel during scanning (u8).
+ * @NL80211_GSCAN_CHAN_ATTR_PASSIVE: flag attribute indicating that scanning
+ * should be done passive for this channel.
+ * @NL80211_GSCAN_CHAN_ATTR_MAX: highest GScan channel attribute.
+ * @__NL80211_GSCAN_CHAN_ATTR_AFTER_LAST: internal use.
+ *
+ * Apart from the channel itself the attributes %NL80211_GSCAN_CHAN_ATTR_DWELL_TIME
+ * and %NL80211_GSCAN_CHAN_ATTR_PASSIVE are advisory values. The driver may or
+ * may not comply.
+ */
+enum nl80211_gscan_chan_attr {
+ __NL80211_GSCAN_CHAN_ATTR_INVALID,
+ NL80211_GSCAN_CHAN_ATTR_FREQ,
+ NL80211_GSCAN_CHAN_ATTR_DWELL_TIME,
+ NL80211_GSCAN_CHAN_ATTR_PASSIVE,
+
+ /* keep last */
+ __NL80211_GSCAN_CHAN_ATTR_AFTER_LAST,
+ NL80211_GSCAN_CHAN_ATTR_MAX = __NL80211_GSCAN_CHAN_ATTR_AFTER_LAST - 1
+};
+
+/**
+ * enum nl80211_bucket_band - GScan bucket band selection.
+ *
+ * @NL80211_BUCKET_BAND_2GHZ: consider all device supported channels
+ * in 2G band.
+ * @NL80211_BUCKET_BAND_5GHZ: consider all device supported channels
+ * in 5G band, ie. both DFS and non-DFS when @NL80211_BUCKET_BAND_NODFS
+ * and @NL80211_BUCKET_BAND_DFS_ONLY are not set.
+ * @NL80211_BUCKET_BAND_NODFS: only consider non-DFS channels. Only
+ * applicable when 5G band is selected, otherwise ignored.
+ * @NL80211_BUCKET_BAND_DFS_ONLY: only consider DFS channels. Only
+ * applicable when 5G band is selected, otherwise ignored.
+ *
+ * Setting both @NL80211_BUCKET_BAND_NODFS and @NL80211_BUCKET_BAND_DFS_ONLY
+ * is considerd invalid.
+ */
+enum nl80211_bucket_band {
+ NL80211_BUCKET_BAND_2GHZ = (1 << 0),
+ NL80211_BUCKET_BAND_5GHZ = (1 << 1),
+ NL80211_BUCKET_BAND_NODFS = (1 << 2),
+ NL80211_BUCKET_BAND_DFS_ONLY = (1 << 3),
+};
+
+/**
+ * enum nl80211_bucket_report_event - GScan bucket report flags.
+ *
+ * @NL80211_BUCKET_REPORT_EACH_SCAN: report each bucket scan completion.
+ * @NL80211_BUCKET_REPORT_FULL_RESULTS: report full scan results.
+ * @NL80211_BUCKET_REPORT_NO_BATCH: no batching required.
+ */
+enum nl80211_bucket_report_event {
+ NL80211_BUCKET_REPORT_EACH_SCAN = (1 << 0),
+ NL80211_BUCKET_REPORT_FULL_RESULTS = (1 << 1),
+ NL80211_BUCKET_REPORT_NO_BATCH = (1 << 2),
+};
+
#endif /* __LINUX_NL80211_H */
diff --git a/net/wireless/core.c b/net/wireless/core.c
index 158c59e..760a2fb 100644
--- a/net/wireless/core.c
+++ b/net/wireless/core.c
@@ -357,6 +357,20 @@ static void cfg80211_sched_scan_stop_wk(struct work_struct *work)
rtnl_unlock();
}

+static void cfg80211_gscan_stop_wk(struct work_struct *work)
+{
+ struct cfg80211_registered_device *rdev;
+
+ rdev = container_of(work, struct cfg80211_registered_device,
+ gscan_stop_wk);
+
+ rtnl_lock();
+
+ __cfg80211_stop_gscan(rdev, false);
+
+ rtnl_unlock();
+}
+
/* exported functions */

struct wiphy *wiphy_new_nm(const struct cfg80211_ops *ops, int sizeof_priv,
@@ -383,6 +397,7 @@ struct wiphy *wiphy_new_nm(const struct cfg80211_ops *ops, int sizeof_priv,
WARN_ON(ops->remain_on_channel && !ops->cancel_remain_on_channel);
WARN_ON(ops->tdls_channel_switch && !ops->tdls_cancel_channel_switch);
WARN_ON(ops->add_tx_ts && !ops->del_tx_ts);
+ WARN_ON(ops->start_gscan && !ops->stop_gscan);

alloc_size = sizeof(*rdev) + sizeof_priv;

@@ -456,6 +471,7 @@ struct wiphy *wiphy_new_nm(const struct cfg80211_ops *ops, int sizeof_priv,
spin_lock_init(&rdev->destroy_list_lock);
INIT_WORK(&rdev->destroy_work, cfg80211_destroy_iface_wk);
INIT_WORK(&rdev->sched_scan_stop_wk, cfg80211_sched_scan_stop_wk);
+ INIT_WORK(&rdev->gscan_stop_wk, cfg80211_gscan_stop_wk);

#ifdef CONFIG_CFG80211_DEFAULT_PS
rdev->wiphy.flags |= WIPHY_FLAG_PS_ON_BY_DEFAULT;
@@ -690,6 +706,12 @@ int wiphy_register(struct wiphy *wiphy)
(wiphy->bss_select_support & ~(BIT(__NL80211_BSS_SELECT_ATTR_AFTER_LAST) - 2))))
return -EINVAL;

+ /* buckets must have unique index and in nl80211 parsing
+ * a u32 is used to verify that hence this limit.
+ */
+ if (WARN_ON(wiphy->gscan && wiphy->gscan->max_scan_buckets > 32))
+ return -EINVAL;
+
if (wiphy->addresses)
memcpy(wiphy->perm_addr, wiphy->addresses[0].addr, ETH_ALEN);

@@ -1001,6 +1023,7 @@ void __cfg80211_leave(struct cfg80211_registered_device *rdev,
{
struct net_device *dev = wdev->netdev;
struct cfg80211_sched_scan_request *sched_scan_req;
+ struct cfg80211_gscan_request *gscan_req;

ASSERT_RTNL();
ASSERT_WDEV_LOCK(wdev);
@@ -1014,6 +1037,9 @@ void __cfg80211_leave(struct cfg80211_registered_device *rdev,
sched_scan_req = rtnl_dereference(rdev->sched_scan_req);
if (sched_scan_req && dev == sched_scan_req->dev)
__cfg80211_stop_sched_scan(rdev, false);
+ gscan_req = rtnl_dereference(rdev->gscan_req);
+ if (gscan_req && dev == gscan_req->dev)
+ __cfg80211_stop_gscan(rdev, false);

#ifdef CONFIG_CFG80211_WEXT
kfree(wdev->wext.ie);
@@ -1089,6 +1115,7 @@ static int cfg80211_netdev_notifier_call(struct notifier_block *nb,
struct wireless_dev *wdev = dev->ieee80211_ptr;
struct cfg80211_registered_device *rdev;
struct cfg80211_sched_scan_request *sched_scan_req;
+ struct cfg80211_gscan_request *gscan_req;

if (!wdev)
return NOTIFY_DONE;
@@ -1160,6 +1187,10 @@ static int cfg80211_netdev_notifier_call(struct notifier_block *nb,
sched_scan_req->dev == wdev->netdev)) {
__cfg80211_stop_sched_scan(rdev, false);
}
+ gscan_req = rtnl_dereference(rdev->gscan_req);
+ if (WARN_ON(gscan_req && gscan_req->dev == wdev->netdev)) {
+ __cfg80211_stop_gscan(rdev, false);
+ }

rdev->opencount--;
wake_up(&rdev->dev_wait);
diff --git a/net/wireless/core.h b/net/wireless/core.h
index ec5f333..ee1d162 100644
--- a/net/wireless/core.h
+++ b/net/wireless/core.h
@@ -75,6 +75,7 @@ struct cfg80211_registered_device {
struct cfg80211_scan_request *scan_req; /* protected by RTNL */
struct sk_buff *scan_msg;
struct cfg80211_sched_scan_request __rcu *sched_scan_req;
+ struct cfg80211_gscan_request __rcu *gscan_req;
unsigned long suspend_at;
struct work_struct scan_done_wk;
struct work_struct sched_scan_results_wk;
@@ -96,6 +97,7 @@ struct cfg80211_registered_device {
struct work_struct destroy_work;

struct work_struct sched_scan_stop_wk;
+ struct work_struct gscan_stop_wk;

/* must be last because of the way we do wiphy_priv(),
* and it should at least be aligned to NETDEV_ALIGN */
@@ -422,6 +424,8 @@ void ___cfg80211_scan_done(struct cfg80211_registered_device *rdev,
void __cfg80211_sched_scan_results(struct work_struct *wk);
int __cfg80211_stop_sched_scan(struct cfg80211_registered_device *rdev,
bool driver_initiated);
+int __cfg80211_stop_gscan(struct cfg80211_registered_device *rdev,
+ bool driver_initiated);
void cfg80211_upload_connect_keys(struct wireless_dev *wdev);
int cfg80211_change_iface(struct cfg80211_registered_device *rdev,
struct net_device *dev, enum nl80211_iftype ntype,
diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c
index 14e1940..4186ece 100644
--- a/net/wireless/nl80211.c
+++ b/net/wireless/nl80211.c
@@ -9,6 +9,7 @@
#include <linux/if.h>
#include <linux/module.h>
#include <linux/err.h>
+#include <linux/gcd.h>
#include <linux/slab.h>
#include <linux/list.h>
#include <linux/if_ether.h>
@@ -405,6 +406,7 @@ enum nl80211_multicast_groups {
[NL80211_ATTR_FILS_NONCES] = { .len = 2 * FILS_NONCE_LEN },
[NL80211_ATTR_MULTICAST_TO_UNICAST_ENABLED] = { .type = NLA_FLAG, },
[NL80211_ATTR_BSSID] = { .len = ETH_ALEN },
+ [NL80211_ATTR_GSCAN_PARAMS] = { .type = NLA_NESTED },
};

/* policy for the key attributes */
@@ -11860,6 +11862,322 @@ static int nl80211_set_multicast_to_unicast(struct sk_buff *skb,
return rdev_set_multicast_to_unicast(rdev, dev, enabled);
}

+static const
+struct nla_policy nl80211_gscan_policy[NL80211_GSCAN_ATTR_MAX + 1] = {
+ [NL80211_GSCAN_ATTR_MAX_AP_PER_SCAN] = { .type = NLA_U8 },
+ [NL80211_GSCAN_ATTR_REPORT_PERC] = { .type = NLA_U8 },
+ [NL80211_GSCAN_ATTR_REPORT_SCANS] = { .type = NLA_U8 },
+ [NL80211_GSCAN_ATTR_BUCKETS] = { .type = NLA_NESTED },
+};
+
+static const struct nla_policy
+nl80211_gscan_bucket_policy[NL80211_GSCAN_BUCKET_ATTR_MAX + 1] = {
+ [NL80211_GSCAN_BUCKET_ATTR_ID] = { .type = NLA_U32 },
+ [NL80211_GSCAN_BUCKET_ATTR_BAND] = { .type = NLA_U8 },
+ [NL80211_GSCAN_BUCKET_ATTR_PERIOD] = { .type = NLA_U16 },
+ [NL80211_GSCAN_BUCKET_ATTR_REPORT] = { .type = NLA_U8 },
+ [NL80211_GSCAN_BUCKET_ATTR_MAX_PERIOD] = { .type = NLA_U16 },
+ [NL80211_GSCAN_BUCKET_ATTR_EXPONENT] = { .type = NLA_U8 },
+ [NL80211_GSCAN_BUCKET_ATTR_STEPS] = { .type = NLA_U8 },
+ [NL80211_GSCAN_BUCKET_ATTR_CHANNELS] = { .type = NLA_NESTED },
+};
+
+static const struct nla_policy
+nl80211_gscan_channel_policy[NL80211_GSCAN_CHAN_ATTR_MAX + 1] = {
+ [NL80211_GSCAN_CHAN_ATTR_FREQ] = { .type = NLA_U32 },
+ [NL80211_GSCAN_CHAN_ATTR_DWELL_TIME] = { .type = NLA_U8 },
+ [NL80211_GSCAN_CHAN_ATTR_PASSIVE] = { .type = NLA_FLAG },
+};
+
+static int nl80211_parse_gscan_channel(struct cfg80211_registered_device *rdev,
+ struct nlattr *nattr,
+ struct cfg80211_gscan_channel *chan)
+{
+ struct nlattr *tb[NL80211_GSCAN_CHAN_ATTR_MAX + 1];
+ struct ieee80211_channel *ch;
+ int err;
+
+ err = nla_parse(tb, NL80211_GSCAN_CHAN_ATTR_MAX, nla_data(nattr),
+ nla_len(nattr), nl80211_gscan_channel_policy);
+ if (err)
+ return err;
+
+ if (!tb[NL80211_GSCAN_CHAN_ATTR_FREQ])
+ return -EINVAL;
+
+ ch = ieee80211_get_channel(&rdev->wiphy,
+ nla_get_u32(tb[NL80211_GSCAN_CHAN_ATTR_FREQ]));
+ if (!ch || (ch->flags & IEEE80211_CHAN_DISABLED))
+ return -EINVAL;
+
+ chan->ch = ch;
+
+ if (tb[NL80211_GSCAN_CHAN_ATTR_DWELL_TIME])
+ chan->dwell_time = nla_get_u8(tb[NL80211_GSCAN_CHAN_ATTR_DWELL_TIME]);
+
+ chan->passive = nla_get_flag(tb[NL80211_GSCAN_CHAN_ATTR_PASSIVE]);
+ return 0;
+}
+
+static int nl80211_parse_gscan_bucket(struct cfg80211_registered_device *rdev,
+ struct nlattr *nattr,
+ struct cfg80211_gscan_bucket *bucket,
+ struct cfg80211_gscan_channel *channels)
+{
+ struct nlattr *tb[NL80211_GSCAN_BUCKET_ATTR_MAX + 1];
+ struct nlattr *chan;
+ struct cfg80211_gscan_channel *ch;
+ int err, rem;
+ int num_chans = 0;
+ u32 band_select = 0;
+ u32 dfs_invalid_mask;
+
+ err = nla_parse(tb, NL80211_GSCAN_BUCKET_ATTR_MAX, nla_data(nattr),
+ nla_len(nattr), nl80211_gscan_bucket_policy);
+ if (err)
+ return err;
+
+ if (!tb[NL80211_GSCAN_BUCKET_ATTR_ID] ||
+ !tb[NL80211_GSCAN_BUCKET_ATTR_PERIOD])
+ return -EINVAL;
+
+ bucket->idx = nla_get_u32(tb[NL80211_GSCAN_BUCKET_ATTR_ID]);
+ if (tb[NL80211_GSCAN_BUCKET_ATTR_BAND]) {
+ band_select = nla_get_u8(tb[NL80211_GSCAN_BUCKET_ATTR_BAND]);
+
+ /* only makes sense if a band is selected */
+ if (!(band_select & (NL80211_BUCKET_BAND_2GHZ | NL80211_BUCKET_BAND_5GHZ)))
+ return -EINVAL;
+ } else if (!tb[NL80211_GSCAN_BUCKET_ATTR_CHANNELS]) {
+ return -EINVAL;
+ }
+
+ dfs_invalid_mask = NL80211_BUCKET_BAND_5GHZ | NL80211_BUCKET_BAND_NODFS |
+ NL80211_BUCKET_BAND_DFS_ONLY;
+ if ((band_select & dfs_invalid_mask) == dfs_invalid_mask)
+ return -EINVAL;
+
+ bucket->band = band_select;
+ bucket->period = nla_get_u16(tb[NL80211_GSCAN_BUCKET_ATTR_PERIOD]);
+
+ if (tb[NL80211_GSCAN_BUCKET_ATTR_REPORT])
+ bucket->report_events = nla_get_u8(tb[NL80211_GSCAN_BUCKET_ATTR_REPORT]);
+
+ if (tb[NL80211_GSCAN_BUCKET_ATTR_MAX_PERIOD])
+ bucket->max_period = nla_get_u16(tb[NL80211_GSCAN_BUCKET_ATTR_MAX_PERIOD]);
+
+ if (bucket->max_period) {
+ if (bucket->max_period < bucket->period)
+ return -EINVAL;
+ /* additional attributes required for backoff bucket */
+ if (bucket->max_period > bucket->period) {
+ if (!tb[NL80211_GSCAN_BUCKET_ATTR_EXPONENT] ||
+ !tb[NL80211_GSCAN_BUCKET_ATTR_STEPS])
+ return -EINVAL;
+
+ bucket->exponent = nla_get_u8(tb[NL80211_GSCAN_BUCKET_ATTR_EXPONENT]);
+ bucket->step_count = nla_get_u8(tb[NL80211_GSCAN_BUCKET_ATTR_STEPS]);
+ }
+ }
+
+ /* ignore channels if band is specified */
+ if (band_select)
+ return 0;
+
+ nla_for_each_nested(chan, tb[NL80211_GSCAN_BUCKET_ATTR_CHANNELS], rem) {
+ num_chans++;
+ }
+ if (num_chans > 16)
+ return -EINVAL;
+
+ bucket->n_channels = num_chans;
+ if (!num_chans)
+ return 0;
+
+ bucket->channels = channels;
+ ch = &bucket->channels[0];
+ nla_for_each_nested(chan, tb[NL80211_GSCAN_BUCKET_ATTR_CHANNELS], rem) {
+ err = nl80211_parse_gscan_channel(rdev, chan, ch);
+ if (err) {
+ return err;
+ }
+ ch++;
+ }
+
+ return 0;
+}
+
+static struct cfg80211_gscan_request *
+nl80211_alloc_gscan_request(struct cfg80211_registered_device *rdev,
+ struct nlattr *buckets_attr)
+{
+ struct cfg80211_gscan_request *req;
+ struct cfg80211_gscan_bucket *b;
+ struct cfg80211_gscan_channel *ch;
+ int n_buckets, n_channels;
+ struct nlattr *attr, *bucket, *channel;
+ int rem, rem_b, rem_c;
+ size_t reqsize;
+
+ if (!buckets_attr)
+ return ERR_PTR(-EINVAL);
+
+ n_buckets = 0;
+ n_channels = 0;
+ nla_for_each_nested(bucket, buckets_attr, rem) {
+ n_buckets++;
+ if (n_buckets > rdev->wiphy.gscan->max_scan_buckets)
+ return ERR_PTR(-EINVAL);
+
+ nla_for_each_nested(attr, bucket, rem_b) {
+ if (nla_type(attr) == NL80211_GSCAN_BUCKET_ATTR_CHANNELS) {
+ nla_for_each_nested(channel, attr, rem_c)
+ n_channels++;
+ }
+ }
+ }
+
+ reqsize = sizeof(*req) +
+ sizeof(*b) * n_buckets +
+ sizeof(*ch) * n_channels;
+
+ req = kzalloc(reqsize, GFP_KERNEL);
+ if (!req)
+ return ERR_PTR(-ENOMEM);
+
+ req->n_buckets = n_buckets;
+ return req;
+}
+
+static int nl80211_parse_gscan_params(struct cfg80211_registered_device *rdev,
+ struct nlattr *attrs[],
+ struct cfg80211_gscan_request **request)
+{
+ struct cfg80211_gscan_request *req;
+ struct nlattr *tb[NL80211_GSCAN_ATTR_MAX + 1];
+ struct nlattr *bucket;
+ struct cfg80211_gscan_bucket *b;
+ struct cfg80211_gscan_channel *ch;
+ int err, rem, i;
+ u32 bucket_map;
+
+ if (!attrs[NL80211_ATTR_GSCAN_PARAMS])
+ return -EINVAL;
+
+ err = nla_parse(tb, NL80211_GSCAN_ATTR_MAX,
+ nla_data(attrs[NL80211_ATTR_GSCAN_PARAMS]),
+ nla_len(attrs[NL80211_ATTR_GSCAN_PARAMS]),
+ nl80211_gscan_policy);
+ if (err)
+ return err;
+
+ req = nl80211_alloc_gscan_request(rdev, tb[NL80211_GSCAN_ATTR_BUCKETS]);
+ if (IS_ERR(req))
+ return PTR_ERR(req);
+
+ if (tb[NL80211_GSCAN_ATTR_MAX_AP_PER_SCAN])
+ req->max_ap_per_scan = nla_get_u8(tb[NL80211_GSCAN_ATTR_MAX_AP_PER_SCAN]);
+ if (tb[NL80211_GSCAN_ATTR_REPORT_PERC])
+ req->report_threshold_percent = nla_get_u8(tb[NL80211_GSCAN_ATTR_REPORT_PERC]);
+ if (tb[NL80211_GSCAN_ATTR_REPORT_SCANS])
+ req->report_threshold_num_scans = nla_get_u8(tb[NL80211_GSCAN_ATTR_REPORT_SCANS]);
+
+ b = &req->buckets[0];
+ ch = (struct cfg80211_gscan_channel *)(&req->buckets[req->n_buckets]);
+ nla_for_each_nested(bucket, tb[NL80211_GSCAN_ATTR_BUCKETS], rem) {
+ err = nl80211_parse_gscan_bucket(rdev, bucket, b, ch);
+ if (err)
+ goto free_req;
+ ch += b->n_channels;
+ b++;
+ }
+ bucket_map = 0;
+ for (i = 0; i < req->n_buckets; i++) {
+ if (BIT(req->buckets[i].idx) & bucket_map) {
+ err = -EINVAL;
+ goto free_req;
+ }
+ bucket_map |= BIT(req->buckets[i].idx);
+
+ if (req->base_period)
+ req->base_period = gcd(req->buckets[i].period,
+ req->base_period);
+ else
+ req->base_period = req->buckets[i].period;
+ }
+ *request = req;
+ return 0;
+
+free_req:
+ kfree(req);
+ return err;
+}
+
+static int nl80211_start_gscan(struct sk_buff *skb, struct genl_info *info)
+{
+ struct cfg80211_gscan_request *request;
+ struct cfg80211_registered_device *rdev = info->user_ptr[0];
+ struct net_device *dev = info->user_ptr[1];
+ struct wireless_dev *wdev = dev->ieee80211_ptr;
+ int err;
+
+ if (!rdev->wiphy.gscan ||
+ !rdev->ops->start_gscan)
+ return -EOPNOTSUPP;
+
+ if (rdev->gscan_req)
+ return -EINPROGRESS;
+
+ err = nl80211_parse_gscan_params(rdev, info->attrs, &request);
+ if (err)
+ return err;
+
+ if (info->attrs[NL80211_ATTR_MAC])
+ memcpy(request->mac, nla_data(info->attrs[NL80211_ATTR_MAC]),
+ ETH_ALEN);
+ if (info->attrs[NL80211_ATTR_MAC_MASK])
+ memcpy(request->mac_mask,
+ nla_data(info->attrs[NL80211_ATTR_MAC_MASK]), ETH_ALEN);
+ if (info->attrs[NL80211_ATTR_SCAN_FLAGS]) {
+ request->flags = nla_get_u32(info->attrs[NL80211_ATTR_SCAN_FLAGS]);
+ if (request->flags & NL80211_SCAN_FLAG_RANDOM_ADDR &&
+ (!info->attrs[NL80211_ATTR_MAC] ||
+ !info->attrs[NL80211_ATTR_MAC_MASK])) {
+ kfree(request);
+ return -EINVAL;
+ }
+ }
+
+ wdev_lock(wdev);
+ err = rdev_start_gscan(rdev, dev, request);
+ wdev_unlock(wdev);
+ if (err) {
+ kfree(request);
+ return err;
+ }
+
+ request->scan_start = jiffies;
+ request->dev = dev;
+ if (info->attrs[NL80211_ATTR_SOCKET_OWNER])
+ request->owner_nlportid = info->snd_portid;
+
+ rcu_assign_pointer(rdev->gscan_req, request);
+
+ nl80211_send_scan_event(rdev, dev, NL80211_CMD_START_GSCAN);
+ return 0;
+}
+
+static int nl80211_stop_gscan(struct sk_buff *skb, struct genl_info *info)
+{
+ struct cfg80211_registered_device *rdev = info->user_ptr[0];
+
+ if (!rdev->wiphy.gscan ||
+ !rdev->ops->stop_gscan)
+ return -EOPNOTSUPP;
+
+ return __cfg80211_stop_gscan(rdev, false);
+}
+
#define NL80211_FLAG_NEED_WIPHY 0x01
#define NL80211_FLAG_NEED_NETDEV 0x02
#define NL80211_FLAG_NEED_RTNL 0x04
@@ -12735,6 +13053,22 @@ static void nl80211_post_doit(const struct genl_ops *ops, struct sk_buff *skb,
.internal_flags = NL80211_FLAG_NEED_NETDEV |
NL80211_FLAG_NEED_RTNL,
},
+ {
+ .cmd = NL80211_CMD_START_GSCAN,
+ .doit = nl80211_start_gscan,
+ .policy = nl80211_policy,
+ .flags = GENL_UNS_ADMIN_PERM,
+ .internal_flags = NL80211_FLAG_NEED_NETDEV_UP |
+ NL80211_FLAG_NEED_RTNL,
+ },
+ {
+ .cmd = NL80211_CMD_STOP_GSCAN,
+ .doit = nl80211_stop_gscan,
+ .policy = nl80211_policy,
+ .flags = GENL_UNS_ADMIN_PERM,
+ .internal_flags = NL80211_FLAG_NEED_NETDEV_UP |
+ NL80211_FLAG_NEED_RTNL,
+ },
};

static struct genl_family nl80211_fam __ro_after_init = {
@@ -14540,12 +14874,18 @@ static int nl80211_netlink_notify(struct notifier_block * nb,
list_for_each_entry_rcu(rdev, &cfg80211_rdev_list, list) {
bool schedule_destroy_work = false;
bool schedule_scan_stop = false;
+ bool schedule_gscan_stop = false;
struct cfg80211_sched_scan_request *sched_scan_req =
rcu_dereference(rdev->sched_scan_req);
+ struct cfg80211_gscan_request *gscan_req =
+ rcu_dereference(rdev->gscan_req);

if (sched_scan_req && notify->portid &&
sched_scan_req->owner_nlportid == notify->portid)
schedule_scan_stop = true;
+ if (gscan_req && notify->portid &&
+ gscan_req->owner_nlportid == notify->portid)
+ schedule_gscan_stop = true;

list_for_each_entry_rcu(wdev, &rdev->wiphy.wdev_list, list) {
cfg80211_mlme_unregister_socket(wdev, notify->portid);
@@ -14576,12 +14916,18 @@ static int nl80211_netlink_notify(struct notifier_block * nb,
spin_unlock(&rdev->destroy_list_lock);
schedule_work(&rdev->destroy_work);
}
- } else if (schedule_scan_stop) {
- sched_scan_req->owner_nlportid = 0;
+ } else {
+ if (schedule_scan_stop) {
+ sched_scan_req->owner_nlportid = 0;

- if (rdev->ops->sched_scan_stop &&
- rdev->wiphy.flags & WIPHY_FLAG_SUPPORTS_SCHED_SCAN)
- schedule_work(&rdev->sched_scan_stop_wk);
+ if (rdev->ops->sched_scan_stop &&
+ rdev->wiphy.flags & WIPHY_FLAG_SUPPORTS_SCHED_SCAN)
+ schedule_work(&rdev->sched_scan_stop_wk);
+ }
+ if (schedule_gscan_stop) {
+ gscan_req->owner_nlportid = 0;
+ schedule_work(&rdev->gscan_stop_wk);
+ }
}
}

diff --git a/net/wireless/rdev-ops.h b/net/wireless/rdev-ops.h
index 2f42507..196e6a7 100644
--- a/net/wireless/rdev-ops.h
+++ b/net/wireless/rdev-ops.h
@@ -1153,4 +1153,29 @@ static inline int rdev_set_qos_map(struct cfg80211_registered_device *rdev,
trace_rdev_return_int(&rdev->wiphy, ret);
return ret;
}
+
+static inline int
+rdev_start_gscan(struct cfg80211_registered_device *rdev,
+ struct net_device *dev,
+ struct cfg80211_gscan_request *request)
+{
+ int ret;
+
+ trace_rdev_start_gscan(&rdev->wiphy, dev);
+ ret = rdev->ops->start_gscan(&rdev->wiphy, dev, request);
+ trace_rdev_return_int(&rdev->wiphy, ret);
+ return ret;
+}
+
+static inline int
+rdev_stop_gscan(struct cfg80211_registered_device *rdev,
+ struct net_device *dev)
+{
+ int ret;
+
+ trace_rdev_stop_gscan(&rdev->wiphy, dev);
+ ret = rdev->ops->stop_gscan(&rdev->wiphy, dev);
+ trace_rdev_return_int(&rdev->wiphy, ret);
+ return ret;
+}
#endif /* __CFG80211_RDEV_OPS */
diff --git a/net/wireless/scan.c b/net/wireless/scan.c
index 174076b..8c141c2 100644
--- a/net/wireless/scan.c
+++ b/net/wireless/scan.c
@@ -386,6 +386,34 @@ int __cfg80211_stop_sched_scan(struct cfg80211_registered_device *rdev,
return 0;
}

+int __cfg80211_stop_gscan(struct cfg80211_registered_device *rdev,
+ bool driver_initiated)
+{
+ struct cfg80211_gscan_request *gscan_req;
+ struct net_device *dev;
+
+ ASSERT_RTNL();
+
+ if (!rdev->gscan_req)
+ return -ENOENT;
+
+ gscan_req = rtnl_dereference(rdev->gscan_req);
+ dev = gscan_req->dev;
+
+ if (!driver_initiated) {
+ int err = rdev_stop_gscan(rdev, dev);
+ if (err)
+ return err;
+ }
+
+ nl80211_send_scan_event(rdev, dev, NL80211_CMD_GSCAN_STOPPED);
+
+ RCU_INIT_POINTER(rdev->gscan_req, NULL);
+ kfree_rcu(gscan_req, rcu_head);
+
+ return 0;
+}
+
void cfg80211_bss_age(struct cfg80211_registered_device *rdev,
unsigned long age_secs)
{
diff --git a/net/wireless/trace.h b/net/wireless/trace.h
index ea1b47e..1d0fde9 100644
--- a/net/wireless/trace.h
+++ b/net/wireless/trace.h
@@ -3067,6 +3067,15 @@
WIPHY_PR_ARG, NETDEV_PR_ARG,
BOOL_TO_STR(__entry->enabled))
);
+
+DEFINE_EVENT(wiphy_netdev_evt, rdev_start_gscan,
+ TP_PROTO(struct wiphy *wiphy, struct net_device *netdev),
+ TP_ARGS(wiphy, netdev)
+);
+DEFINE_EVENT(wiphy_netdev_evt, rdev_stop_gscan,
+ TP_PROTO(struct wiphy *wiphy, struct net_device *netdev),
+ TP_ARGS(wiphy, netdev)
+);
#endif /* !__RDEV_OPS_TRACE || TRACE_HEADER_MULTI_READ */

#undef TRACE_INCLUDE_PATH
--
1.9.1

2016-12-16 10:14:00

by Johannes Berg

[permalink] [raw]
Subject: Re: [RFC V3 03/11] nl80211: add support for gscan

On Wed, 2016-12-14 at 10:01 +0100, Arend Van Spriel wrote:

> Had to look for "> 16" ;-)

Sorry.

> Here an instance of the tab vs. space issue you mentioned. Will go
> over the patch and fix that.

There were a few, not really interesting though - git would probably
flag it anyway, or checkpatch :)

> > + if (num_chans > 16)
> > + return -EINVAL;
>
> I suspect this is the restriction you were referring to.

Yes.

> There is no
> reason for this although the android wifi hal has max 16 channels in
> a bucket so I might have picked that up.

I thought I saw something with a u16 bitmap that seemed related, but I
don't see that now so I'm probably just confused.

> So could a driver have a similar limit and should we add such to the
> gscan capabilities? For instance our firmware api has a nasty
> restriction of 64 channels for all buckets together, eg. can do 4
> buckets of 16 channels each.

We do have a limit of the maximum scan buckets, which seems to be 16
right now. We also have a limit on the number of channels per bucket,
which is also 16, but no combined limit afaict (so 16x16 seems fine).

Maybe we do need some advertisement in that area then? Right now,
wifihal seems to be able to read as capabilities the number of buckets
(wifi_gscan_capabilities), but assumes the number of channels:

const unsigned MAX_CHANNELS                = 16;
const unsigned MAX_BUCKETS                 = 16;

I guess we took that and combined it, and you had more negotiation with
Google ;-)

We may then have to actually advertise the limit you have ("64 channels
combined over all buckets"), unless you can get away with just
advertising 4 buckets (and us saying 16 channels per bucket is enough?)

I'm a bit tempted to make this more forward compatible though and not
hard-limit the number of channels per bucket in the code.

johannes

2016-12-13 22:30:02

by Johannes Berg

[permalink] [raw]
Subject: Re: [RFC V3 03/11] nl80211: add support for gscan

On Tue, 2016-12-13 at 21:09 +0100, Arend Van Spriel wrote:

> > There's a bit of a weird hard-coded restriction to 16 channels too,
> > that's due to the bucket map?
>
> Uhm. Is there? I will check, but if you can give me a pointer where
> to look it is appreciated.

Just look for "< 16" or "<= 16" or so in the patch. I do think that's
because the channel map is a u16 though, not sure we'd want to change
that.

johannes

2016-12-12 12:00:20

by Arend Van Spriel

[permalink] [raw]
Subject: [RFC V3 08/11] brcmfmac: report gscan capabilities if firmware supports it

From: Arend van Spriel <[email protected]>

Fill the gscan capability information in the wiphy structure if
it is supported by firmware. The values are hardcoded as it can
not be obtained from firmware.

Reviewed-by: Hante Meuleman <[email protected]>
Reviewed-by: Pieter-Paul Giesberts <[email protected]>
Reviewed-by: Franky Lin <[email protected]>
Signed-off-by: Arend van Spriel <[email protected]>
---
.../wireless/broadcom/brcm80211/brcmfmac/cfg80211.c | 18 +++++++++++++++++-
1 file changed, 17 insertions(+), 1 deletion(-)

diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c
index 7ffc4ab..374b72c 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c
@@ -207,6 +207,16 @@ static bool check_vif_up(struct brcmf_cfg80211_vif *vif)
REG_RULE(5470-10, 5850+10, 80, 6, 20, 0), }
};

+static const struct wiphy_gscan_caps brcmf_gscan_caps = {
+ .max_scan_cache_size = 320,
+ .max_scan_buckets = 8,
+ .max_ap_cache_per_scan = 32,
+ .max_rssi_sample_size = 8,
+ .max_scan_reporting_threshold = 100,
+ .max_hotlist_bssids = 64,
+ .max_significant_wifi_change_aps = 16,
+};
+
/* Note: brcmf_cipher_suites is an array of int defining which cipher suites
* are supported. A pointer to this array and the number of entries is passed
* on to upper layers. AES_CMAC defines whether or not the driver supports MFP.
@@ -6426,7 +6436,12 @@ static int brcmf_setup_wiphy(struct wiphy *wiphy, struct brcmf_if *ifp)
wiphy->max_remain_on_channel_duration = 5000;
if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_PNO))
brcmf_wiphy_pno_params(wiphy);
-
+ if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_GSCAN)) {
+ wiphy->gscan = kmemdup(&brcmf_gscan_caps,
+ sizeof(brcmf_gscan_caps), GFP_KERNEL);
+ if (!wiphy->gscan)
+ return -ENOMEM;
+ }
/* vendor commands/events support */
wiphy->vendor_commands = brcmf_vendor_cmds;
wiphy->n_vendor_commands = BRCMF_VNDR_CMDS_LAST - 1;
@@ -6735,6 +6750,7 @@ static void brcmf_free_wiphy(struct wiphy *wiphy)
if (!wiphy)
return;

+ kfree(wiphy->gscan);
if (wiphy->iface_combinations) {
for (i = 0; i < wiphy->n_iface_combinations; i++)
kfree(wiphy->iface_combinations[i].limits);
--
1.9.1

2016-12-12 12:00:18

by Arend Van Spriel

[permalink] [raw]
Subject: [RFC V3 04/11] nl80211: add driver api for gscan notifications

The driver can indicate gscan results are available or gscan operation
has stopped.

Reviewed-by: Hante Meuleman <[email protected]>
Reviewed-by: Pieter-Paul Giesberts <[email protected]>
Reviewed-by: Franky Lin <[email protected]>
Signed-off-by: Arend van Spriel <[email protected]>
---
include/net/cfg80211.h | 28 +++++++++++++++++++++
include/uapi/linux/nl80211.h | 2 ++
net/wireless/core.c | 2 ++
net/wireless/core.h | 2 ++
net/wireless/nl80211.c | 19 +++++++++++++++
net/wireless/nl80211.h | 2 ++
net/wireless/scan.c | 58 ++++++++++++++++++++++++++++++++++++++++++++
net/wireless/trace.h | 10 ++++++++
8 files changed, 123 insertions(+)

diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h
index 8bc8842..4b02585 100644
--- a/include/net/cfg80211.h
+++ b/include/net/cfg80211.h
@@ -4552,6 +4552,34 @@ void cfg80211_scan_done(struct cfg80211_scan_request *request,
void cfg80211_sched_scan_stopped_rtnl(struct wiphy *wiphy);

/**
+ * cfg80211_gscan_results - notify that new scan results are available
+ *
+ * @wiphy: the wiphy which got GScan results
+ */
+void cfg80211_gscan_results(struct wiphy *wiphy);
+
+/**
+ * cfg80211_gscan_stopped - notify that the GScan has stopped
+ *
+ * @wiphy: the wiphy on which the GScan stopped.
+ *
+ * The driver can call this function to inform cfg80211 that the
+ * GScan had to be stopped, for whatever reason.
+ */
+void cfg80211_gscan_stopped(struct wiphy *wiphy);
+
+/**
+ * cfg80211_gscan_stopped_rtnl - notify that the GScan has stopped
+ *
+ * @wiphy: the wiphy on which the GScan stopped.
+ *
+ * The driver can call this function to inform cfg80211 that the
+ * GScan had to be stopped, for whatever reason.
+ * This function should be called with rtnl locked.
+ */
+void cfg80211_gscan_stopped_rtnl(struct wiphy *wiphy);
+
+/**
* cfg80211_inform_bss_frame_data - inform cfg80211 of a received BSS frame
* @wiphy: the wiphy reporting the BSS
* @data: the BSS metadata
diff --git a/include/uapi/linux/nl80211.h b/include/uapi/linux/nl80211.h
index 5e42383..fd7ccd2 100644
--- a/include/uapi/linux/nl80211.h
+++ b/include/uapi/linux/nl80211.h
@@ -896,6 +896,7 @@
*
* @NL80211_CMD_START_GSCAN: start GScan.
* @NL80211_CMD_STOP_GSCAN: request to stop current GScan.
+ * @NL80211_CMD_GSCAN_RESULTS: indicates that there GScan results available.
* @NL80211_CMD_GSCAN_STOPPED: indicates that the currently running GScan
* has stopped. This event is generated upon @NL80211_CMD_STOP_GSCAN and
* the driver may issue this event at any time when a GScan is running.
@@ -1101,6 +1102,7 @@ enum nl80211_commands {

NL80211_CMD_START_GSCAN,
NL80211_CMD_STOP_GSCAN,
+ NL80211_CMD_GSCAN_RESULTS,
NL80211_CMD_GSCAN_STOPPED,

/* add new commands above here */
diff --git a/net/wireless/core.c b/net/wireless/core.c
index 760a2fb..69eea4c 100644
--- a/net/wireless/core.c
+++ b/net/wireless/core.c
@@ -453,6 +453,7 @@ struct wiphy *wiphy_new_nm(const struct cfg80211_ops *ops, int sizeof_priv,
INIT_LIST_HEAD(&rdev->bss_list);
INIT_WORK(&rdev->scan_done_wk, __cfg80211_scan_done);
INIT_WORK(&rdev->sched_scan_results_wk, __cfg80211_sched_scan_results);
+ INIT_WORK(&rdev->gscan_results_wk, __cfg80211_gscan_results);
INIT_LIST_HEAD(&rdev->mlme_unreg);
spin_lock_init(&rdev->mlme_unreg_lock);
INIT_WORK(&rdev->mlme_unreg_wk, cfg80211_mlme_unreg_wk);
@@ -935,6 +936,7 @@ void wiphy_unregister(struct wiphy *wiphy)
cancel_delayed_work_sync(&rdev->dfs_update_channels_wk);
flush_work(&rdev->destroy_work);
flush_work(&rdev->sched_scan_stop_wk);
+ flush_work(&rdev->gscan_stop_wk);
flush_work(&rdev->mlme_unreg_wk);

#ifdef CONFIG_PM
diff --git a/net/wireless/core.h b/net/wireless/core.h
index ee1d162..89e934f 100644
--- a/net/wireless/core.h
+++ b/net/wireless/core.h
@@ -79,6 +79,7 @@ struct cfg80211_registered_device {
unsigned long suspend_at;
struct work_struct scan_done_wk;
struct work_struct sched_scan_results_wk;
+ struct work_struct gscan_results_wk;

struct genl_info *cur_cmd_info;

@@ -424,6 +425,7 @@ void ___cfg80211_scan_done(struct cfg80211_registered_device *rdev,
void __cfg80211_sched_scan_results(struct work_struct *wk);
int __cfg80211_stop_sched_scan(struct cfg80211_registered_device *rdev,
bool driver_initiated);
+void __cfg80211_gscan_results(struct work_struct *wk);
int __cfg80211_stop_gscan(struct cfg80211_registered_device *rdev,
bool driver_initiated);
void cfg80211_upload_connect_keys(struct wireless_dev *wdev);
diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c
index 4186ece..7699b16 100644
--- a/net/wireless/nl80211.c
+++ b/net/wireless/nl80211.c
@@ -13302,6 +13302,25 @@ void nl80211_send_sched_scan_results(struct cfg80211_registered_device *rdev,
NL80211_MCGRP_SCAN, GFP_KERNEL);
}

+void nl80211_send_gscan_results(struct cfg80211_registered_device *rdev,
+ struct net_device *netdev)
+{
+ struct sk_buff *msg;
+
+ msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
+ if (!msg)
+ return;
+
+ if (nl80211_send_scan_event_msg(msg, rdev, netdev, 0, 0, 0,
+ NL80211_CMD_GSCAN_RESULTS) < 0) {
+ nlmsg_free(msg);
+ return;
+ }
+
+ genlmsg_multicast_netns(&nl80211_fam, wiphy_net(&rdev->wiphy), msg, 0,
+ NL80211_MCGRP_SCAN, GFP_KERNEL);
+}
+
void nl80211_send_scan_event(struct cfg80211_registered_device *rdev,
struct net_device *netdev, u32 cmd)
{
diff --git a/net/wireless/nl80211.h b/net/wireless/nl80211.h
index fb304ce9..4eec856 100644
--- a/net/wireless/nl80211.h
+++ b/net/wireless/nl80211.h
@@ -20,6 +20,8 @@ void nl80211_send_scan_event(struct cfg80211_registered_device *rdev,
struct net_device *netdev, u32 cmd);
void nl80211_send_sched_scan_results(struct cfg80211_registered_device *rdev,
struct net_device *netdev);
+void nl80211_send_gscan_results(struct cfg80211_registered_device *rdev,
+ struct net_device *netdev);
void nl80211_common_reg_change_event(enum nl80211_commands cmd_id,
struct regulatory_request *request);

diff --git a/net/wireless/scan.c b/net/wireless/scan.c
index 8c141c2..cc4f7c7 100644
--- a/net/wireless/scan.c
+++ b/net/wireless/scan.c
@@ -338,6 +338,44 @@ void cfg80211_sched_scan_results(struct wiphy *wiphy)
}
EXPORT_SYMBOL(cfg80211_sched_scan_results);

+void __cfg80211_gscan_results(struct work_struct *wk)
+{
+ struct cfg80211_registered_device *rdev;
+ struct cfg80211_gscan_request *request;
+
+ rdev = container_of(wk, struct cfg80211_registered_device,
+ gscan_results_wk);
+
+ rtnl_lock();
+
+ request = rtnl_dereference(rdev->gscan_req);
+
+ /* we don't have request anymore if the scan is stopping */
+ if (request) {
+ if (request->flags & NL80211_SCAN_FLAG_FLUSH) {
+ /* flush entries from previous scans */
+ spin_lock_bh(&rdev->bss_lock);
+ __cfg80211_bss_expire(rdev, request->scan_start);
+ spin_unlock_bh(&rdev->bss_lock);
+ request->scan_start = jiffies;
+ }
+ nl80211_send_gscan_results(rdev, request->dev);
+ }
+
+ rtnl_unlock();
+}
+
+void cfg80211_gscan_results(struct wiphy *wiphy)
+{
+ trace_cfg80211_gscan_results(wiphy);
+ /* ignore if we're not scanning */
+
+ if (rcu_access_pointer(wiphy_to_rdev(wiphy)->gscan_req))
+ queue_work(cfg80211_wq,
+ &wiphy_to_rdev(wiphy)->gscan_results_wk);
+}
+EXPORT_SYMBOL(cfg80211_gscan_results);
+
void cfg80211_sched_scan_stopped_rtnl(struct wiphy *wiphy)
{
struct cfg80211_registered_device *rdev = wiphy_to_rdev(wiphy);
@@ -358,6 +396,26 @@ void cfg80211_sched_scan_stopped(struct wiphy *wiphy)
}
EXPORT_SYMBOL(cfg80211_sched_scan_stopped);

+void cfg80211_gscan_stopped_rtnl(struct wiphy *wiphy)
+{
+ struct cfg80211_registered_device *rdev = wiphy_to_rdev(wiphy);
+
+ ASSERT_RTNL();
+
+ trace_cfg80211_gscan_stopped(wiphy);
+
+ __cfg80211_stop_gscan(rdev, true);
+}
+EXPORT_SYMBOL(cfg80211_gscan_stopped_rtnl);
+
+void cfg80211_gscan_stopped(struct wiphy *wiphy)
+{
+ rtnl_lock();
+ cfg80211_gscan_stopped_rtnl(wiphy);
+ rtnl_unlock();
+}
+EXPORT_SYMBOL(cfg80211_gscan_stopped);
+
int __cfg80211_stop_sched_scan(struct cfg80211_registered_device *rdev,
bool driver_initiated)
{
diff --git a/net/wireless/trace.h b/net/wireless/trace.h
index 1d0fde9..f3f5d82 100644
--- a/net/wireless/trace.h
+++ b/net/wireless/trace.h
@@ -2799,6 +2799,16 @@
TP_ARGS(wiphy)
);

+DEFINE_EVENT(wiphy_only_evt, cfg80211_gscan_results,
+ TP_PROTO(struct wiphy *wiphy),
+ TP_ARGS(wiphy)
+);
+
+DEFINE_EVENT(wiphy_only_evt, cfg80211_gscan_stopped,
+ TP_PROTO(struct wiphy *wiphy),
+ TP_ARGS(wiphy)
+);
+
TRACE_EVENT(cfg80211_get_bss,
TP_PROTO(struct wiphy *wiphy, struct ieee80211_channel *channel,
const u8 *bssid, const u8 *ssid, size_t ssid_len,
--
1.9.1

2016-12-13 20:09:22

by Arend Van Spriel

[permalink] [raw]
Subject: Re: [RFC V3 01/11] nl80211: add reporting of gscan capabilities



On 13-12-2016 17:15, Johannes Berg wrote:
>
>> + case 14:
>> + if (!rdev->wiphy.gscan) {
>> + /* done */
>> + state->split_start = 0;
>> + break;
>> + }
>>
>
> Nit, but I'm not really happy with this - this assumes that case 14 is
> the last case, if anyone ever adds one we break this code but it would
> still work if the device has gscan. Move the gscan stuff into a new
> function and make that return immediately if gscan is NULL or so?

Agree. When coding this piece I was aware that this would need to change
when 'case 15' would be added, which is probably too easy to overlook. I
will change it.

Regards,
Arend