Path: blob/main/sys/contrib/dev/broadcom/brcm80211/brcmfmac/pno.c
178665 views
// SPDX-License-Identifier: ISC1/*2* Copyright (c) 2016 Broadcom3*/4#include <linux/netdevice.h>5#include <linux/gcd.h>6#include <net/cfg80211.h>78#include "core.h"9#include "debug.h"10#include "fwil.h"11#include "fwil_types.h"12#include "cfg80211.h"13#include "pno.h"1415#define BRCMF_PNO_VERSION 216#define BRCMF_PNO_REPEAT 417#define BRCMF_PNO_FREQ_EXPO_MAX 318#define BRCMF_PNO_IMMEDIATE_SCAN_BIT 319#define BRCMF_PNO_ENABLE_BD_SCAN_BIT 520#define BRCMF_PNO_ENABLE_ADAPTSCAN_BIT 621#define BRCMF_PNO_REPORT_SEPARATELY_BIT 1122#define BRCMF_PNO_SCAN_INCOMPLETE 023#define BRCMF_PNO_WPA_AUTH_ANY 0xFFFFFFFF24#define BRCMF_PNO_HIDDEN_BIT 225#define BRCMF_PNO_SCHED_SCAN_PERIOD 302627#define BRCMF_PNO_MAX_BUCKETS 1628#define GSCAN_BATCH_NO_THR_SET 10129#define GSCAN_RETRY_THRESHOLD 33031struct brcmf_pno_info {32int n_reqs;33struct cfg80211_sched_scan_request *reqs[BRCMF_PNO_MAX_BUCKETS];34struct mutex req_lock;35};3637#define ifp_to_pno(_ifp) ((_ifp)->drvr->config->pno)3839static int brcmf_pno_store_request(struct brcmf_pno_info *pi,40struct cfg80211_sched_scan_request *req)41{42if (WARN(pi->n_reqs == BRCMF_PNO_MAX_BUCKETS,43"pno request storage full\n"))44return -ENOSPC;4546#if defined(__linux__)47brcmf_dbg(SCAN, "reqid=%llu\n", req->reqid);48#elif defined(__FreeBSD__)49brcmf_dbg(SCAN, "reqid=%ju\n", (uintmax_t)req->reqid);50#endif51mutex_lock(&pi->req_lock);52pi->reqs[pi->n_reqs++] = req;53mutex_unlock(&pi->req_lock);54return 0;55}5657static int brcmf_pno_remove_request(struct brcmf_pno_info *pi, u64 reqid)58{59int i, err = 0;6061mutex_lock(&pi->req_lock);6263/* Nothing to do if we have no requests */64if (pi->n_reqs == 0)65goto done;6667/* find request */68for (i = 0; i < pi->n_reqs; i++) {69if (pi->reqs[i]->reqid == reqid)70break;71}72/* request not found */73if (WARN(i == pi->n_reqs, "reqid not found\n")) {74err = -ENOENT;75goto done;76}7778#if defined(__linux__)79brcmf_dbg(SCAN, "reqid=%llu\n", reqid);80#elif defined(__FreeBSD__)81brcmf_dbg(SCAN, "reqid=%ju\n", (uintmax_t)reqid);82#endif83pi->n_reqs--;8485/* if last we are done */86if (!pi->n_reqs || i == pi->n_reqs)87goto done;8889/* fill the gap with remaining requests */90while (i <= pi->n_reqs - 1) {91pi->reqs[i] = pi->reqs[i + 1];92i++;93}9495done:96mutex_unlock(&pi->req_lock);97return err;98}99100static int brcmf_pno_channel_config(struct brcmf_if *ifp,101struct brcmf_pno_config_le *cfg)102{103cfg->reporttype = 0;104cfg->flags = 0;105106return brcmf_fil_iovar_data_set(ifp, "pfn_cfg", cfg, sizeof(*cfg));107}108109static int brcmf_pno_config(struct brcmf_if *ifp, u32 scan_freq,110u32 mscan, u32 bestn)111{112struct brcmf_pub *drvr = ifp->drvr;113struct brcmf_pno_param_le pfn_param;114u16 flags;115u32 pfnmem;116s32 err;117118memset(&pfn_param, 0, sizeof(pfn_param));119pfn_param.version = cpu_to_le32(BRCMF_PNO_VERSION);120121/* set extra pno params */122flags = BIT(BRCMF_PNO_IMMEDIATE_SCAN_BIT) |123BIT(BRCMF_PNO_ENABLE_ADAPTSCAN_BIT);124pfn_param.repeat = BRCMF_PNO_REPEAT;125pfn_param.exp = BRCMF_PNO_FREQ_EXPO_MAX;126127/* set up pno scan fr */128pfn_param.scan_freq = cpu_to_le32(scan_freq);129130if (mscan) {131pfnmem = bestn;132133/* set bestn in firmware */134err = brcmf_fil_iovar_int_set(ifp, "pfnmem", pfnmem);135if (err < 0) {136bphy_err(drvr, "failed to set pfnmem\n");137goto exit;138}139/* get max mscan which the firmware supports */140err = brcmf_fil_iovar_int_get(ifp, "pfnmem", &pfnmem);141if (err < 0) {142bphy_err(drvr, "failed to get pfnmem\n");143goto exit;144}145mscan = min_t(u32, mscan, pfnmem);146pfn_param.mscan = mscan;147pfn_param.bestn = bestn;148flags |= BIT(BRCMF_PNO_ENABLE_BD_SCAN_BIT);149brcmf_dbg(INFO, "mscan=%d, bestn=%d\n", mscan, bestn);150}151152pfn_param.flags = cpu_to_le16(flags);153err = brcmf_fil_iovar_data_set(ifp, "pfn_set", &pfn_param,154sizeof(pfn_param));155if (err)156bphy_err(drvr, "pfn_set failed, err=%d\n", err);157158exit:159return err;160}161162static int brcmf_pno_set_random(struct brcmf_if *ifp, struct brcmf_pno_info *pi)163{164struct brcmf_pub *drvr = ifp->drvr;165struct brcmf_pno_macaddr_le pfn_mac;166u8 *mac_addr = NULL;167u8 *mac_mask = NULL;168int err, i, ri;169170for (ri = 0; ri < pi->n_reqs; ri++)171if (pi->reqs[ri]->flags & NL80211_SCAN_FLAG_RANDOM_ADDR) {172mac_addr = pi->reqs[ri]->mac_addr;173mac_mask = pi->reqs[ri]->mac_addr_mask;174break;175}176177/* no random mac requested */178if (!mac_addr)179return 0;180181pfn_mac.version = BRCMF_PFN_MACADDR_CFG_VER;182pfn_mac.flags = BRCMF_PFN_MAC_OUI_ONLY | BRCMF_PFN_SET_MAC_UNASSOC;183184memcpy(pfn_mac.mac, mac_addr, ETH_ALEN);185for (i = 0; i < ETH_ALEN; i++) {186pfn_mac.mac[i] &= mac_mask[i];187pfn_mac.mac[i] |= get_random_u8() & ~(mac_mask[i]);188}189/* Clear multi bit */190pfn_mac.mac[0] &= 0xFE;191/* Set locally administered */192pfn_mac.mac[0] |= 0x02;193194#if defined(__linux__)195brcmf_dbg(SCAN, "enabling random mac: reqid=%llu mac=%pM\n",196pi->reqs[ri]->reqid, pfn_mac.mac);197#elif defined(__FreeBSD__)198brcmf_dbg(SCAN, "enabling random mac: reqid=%ju mac=%6D\n",199(uintmax_t)pi->reqs[ri]->reqid, pfn_mac.mac, ":");200#endif201err = brcmf_fil_iovar_data_set(ifp, "pfn_macaddr", &pfn_mac,202sizeof(pfn_mac));203if (err)204bphy_err(drvr, "pfn_macaddr failed, err=%d\n", err);205206return err;207}208209static int brcmf_pno_add_ssid(struct brcmf_if *ifp, struct cfg80211_ssid *ssid,210bool active)211{212struct brcmf_pub *drvr = ifp->drvr;213struct brcmf_pno_net_param_le pfn;214int err;215216pfn.auth = cpu_to_le32(WLAN_AUTH_OPEN);217pfn.wpa_auth = cpu_to_le32(BRCMF_PNO_WPA_AUTH_ANY);218pfn.wsec = cpu_to_le32(0);219pfn.infra = cpu_to_le32(1);220pfn.flags = 0;221if (active)222pfn.flags = cpu_to_le32(1 << BRCMF_PNO_HIDDEN_BIT);223pfn.ssid.SSID_len = cpu_to_le32(ssid->ssid_len);224memcpy(pfn.ssid.SSID, ssid->ssid, ssid->ssid_len);225226brcmf_dbg(SCAN, "adding ssid=%.32s (active=%d)\n", ssid->ssid, active);227err = brcmf_fil_iovar_data_set(ifp, "pfn_add", &pfn, sizeof(pfn));228if (err < 0)229bphy_err(drvr, "adding failed: err=%d\n", err);230return err;231}232233static int brcmf_pno_add_bssid(struct brcmf_if *ifp, const u8 *bssid)234{235struct brcmf_pub *drvr = ifp->drvr;236struct brcmf_pno_bssid_le bssid_cfg;237int err;238239memcpy(bssid_cfg.bssid, bssid, ETH_ALEN);240bssid_cfg.flags = 0;241242brcmf_dbg(SCAN, "adding bssid=%pM\n", bssid);243err = brcmf_fil_iovar_data_set(ifp, "pfn_add_bssid", &bssid_cfg,244sizeof(bssid_cfg));245if (err < 0)246bphy_err(drvr, "adding failed: err=%d\n", err);247return err;248}249250static bool brcmf_is_ssid_active(struct cfg80211_ssid *ssid,251struct cfg80211_sched_scan_request *req)252{253int i;254255if (!ssid || !req->ssids || !req->n_ssids)256return false;257258for (i = 0; i < req->n_ssids; i++) {259if (ssid->ssid_len == req->ssids[i].ssid_len) {260if (!strncmp(ssid->ssid, req->ssids[i].ssid,261ssid->ssid_len))262return true;263}264}265return false;266}267268static int brcmf_pno_clean(struct brcmf_if *ifp)269{270struct brcmf_pub *drvr = ifp->drvr;271int ret;272273/* Disable pfn */274ret = brcmf_fil_iovar_int_set(ifp, "pfn", 0);275if (ret == 0) {276/* clear pfn */277ret = brcmf_fil_iovar_data_set(ifp, "pfnclear", NULL, 0);278}279if (ret < 0)280bphy_err(drvr, "failed code %d\n", ret);281282return ret;283}284285static int brcmf_pno_get_bucket_channels(struct cfg80211_sched_scan_request *r,286struct brcmf_pno_config_le *pno_cfg)287{288u32 n_chan = le32_to_cpu(pno_cfg->channel_num);289u16 chan;290int i, err = 0;291292for (i = 0; i < r->n_channels; i++) {293if (n_chan >= BRCMF_NUMCHANNELS) {294err = -ENOSPC;295goto done;296}297chan = r->channels[i]->hw_value;298brcmf_dbg(SCAN, "[%d] Chan : %u\n", n_chan, chan);299pno_cfg->channel_list[n_chan++] = cpu_to_le16(chan);300}301/* return number of channels */302err = n_chan;303done:304pno_cfg->channel_num = cpu_to_le32(n_chan);305return err;306}307308static int brcmf_pno_prep_fwconfig(struct brcmf_pno_info *pi,309struct brcmf_pno_config_le *pno_cfg,310struct brcmf_gscan_bucket_config **buckets,311u32 *scan_freq)312{313struct cfg80211_sched_scan_request *sr;314struct brcmf_gscan_bucket_config *fw_buckets;315int i, err, chidx;316317brcmf_dbg(SCAN, "n_reqs=%d\n", pi->n_reqs);318if (WARN_ON(!pi->n_reqs))319return -ENODATA;320321/*322* actual scan period is determined using gcd() for each323* scheduled scan period.324*/325*scan_freq = pi->reqs[0]->scan_plans[0].interval;326for (i = 1; i < pi->n_reqs; i++) {327sr = pi->reqs[i];328*scan_freq = gcd(sr->scan_plans[0].interval, *scan_freq);329}330if (*scan_freq < BRCMF_PNO_SCHED_SCAN_MIN_PERIOD) {331brcmf_dbg(SCAN, "scan period too small, using minimum\n");332*scan_freq = BRCMF_PNO_SCHED_SCAN_MIN_PERIOD;333}334335*buckets = NULL;336fw_buckets = kcalloc(pi->n_reqs, sizeof(*fw_buckets), GFP_KERNEL);337if (!fw_buckets)338return -ENOMEM;339340memset(pno_cfg, 0, sizeof(*pno_cfg));341for (i = 0; i < pi->n_reqs; i++) {342sr = pi->reqs[i];343chidx = brcmf_pno_get_bucket_channels(sr, pno_cfg);344if (chidx < 0) {345err = chidx;346goto fail;347}348fw_buckets[i].bucket_end_index = chidx - 1;349fw_buckets[i].bucket_freq_multiple =350sr->scan_plans[0].interval / *scan_freq;351/* assure period is non-zero */352if (!fw_buckets[i].bucket_freq_multiple)353fw_buckets[i].bucket_freq_multiple = 1;354fw_buckets[i].flag = BRCMF_PNO_REPORT_NO_BATCH;355}356357if (BRCMF_SCAN_ON()) {358brcmf_err("base period=%u\n", *scan_freq);359for (i = 0; i < pi->n_reqs; i++) {360brcmf_err("[%d] period %u max %u repeat %u flag %x idx %u\n",361i, fw_buckets[i].bucket_freq_multiple,362le16_to_cpu(fw_buckets[i].max_freq_multiple),363fw_buckets[i].repeat, fw_buckets[i].flag,364fw_buckets[i].bucket_end_index);365}366}367*buckets = fw_buckets;368return pi->n_reqs;369370fail:371kfree(fw_buckets);372return err;373}374375static int brcmf_pno_config_networks(struct brcmf_if *ifp,376struct brcmf_pno_info *pi)377{378struct cfg80211_sched_scan_request *r;379struct cfg80211_match_set *ms;380bool active;381int i, j, err = 0;382383for (i = 0; i < pi->n_reqs; i++) {384r = pi->reqs[i];385386for (j = 0; j < r->n_match_sets; j++) {387ms = &r->match_sets[j];388if (ms->ssid.ssid_len) {389active = brcmf_is_ssid_active(&ms->ssid, r);390err = brcmf_pno_add_ssid(ifp, &ms->ssid,391active);392}393if (!err && is_valid_ether_addr(ms->bssid))394err = brcmf_pno_add_bssid(ifp, ms->bssid);395396if (err < 0)397return err;398}399}400return 0;401}402403static int brcmf_pno_config_sched_scans(struct brcmf_if *ifp)404{405struct brcmf_pub *drvr = ifp->drvr;406struct brcmf_pno_info *pi;407struct brcmf_gscan_config *gscan_cfg;408struct brcmf_gscan_bucket_config *buckets;409struct brcmf_pno_config_le pno_cfg;410size_t gsz;411u32 scan_freq;412int err, n_buckets;413414pi = ifp_to_pno(ifp);415n_buckets = brcmf_pno_prep_fwconfig(pi, &pno_cfg, &buckets,416&scan_freq);417if (n_buckets < 0)418return n_buckets;419420gsz = struct_size(gscan_cfg, bucket, n_buckets);421gscan_cfg = kzalloc(gsz, GFP_KERNEL);422if (!gscan_cfg) {423err = -ENOMEM;424goto free_buckets;425}426427/* clean up everything */428err = brcmf_pno_clean(ifp);429if (err < 0) {430bphy_err(drvr, "failed error=%d\n", err);431goto free_gscan;432}433434/* configure pno */435err = brcmf_pno_config(ifp, scan_freq, 0, 0);436if (err < 0)437goto free_gscan;438439err = brcmf_pno_channel_config(ifp, &pno_cfg);440if (err < 0)441goto clean;442443gscan_cfg->version = cpu_to_le16(BRCMF_GSCAN_CFG_VERSION);444gscan_cfg->retry_threshold = GSCAN_RETRY_THRESHOLD;445gscan_cfg->buffer_threshold = GSCAN_BATCH_NO_THR_SET;446gscan_cfg->flags = BRCMF_GSCAN_CFG_ALL_BUCKETS_IN_1ST_SCAN;447448gscan_cfg->count_of_channel_buckets = n_buckets;449memcpy(gscan_cfg->bucket, buckets,450array_size(n_buckets, sizeof(*buckets)));451452err = brcmf_fil_iovar_data_set(ifp, "pfn_gscan_cfg", gscan_cfg, gsz);453454if (err < 0)455goto clean;456457/* configure random mac */458err = brcmf_pno_set_random(ifp, pi);459if (err < 0)460goto clean;461462err = brcmf_pno_config_networks(ifp, pi);463if (err < 0)464goto clean;465466/* Enable the PNO */467err = brcmf_fil_iovar_int_set(ifp, "pfn", 1);468469clean:470if (err < 0)471brcmf_pno_clean(ifp);472free_gscan:473kfree(gscan_cfg);474free_buckets:475kfree(buckets);476return err;477}478479int brcmf_pno_start_sched_scan(struct brcmf_if *ifp,480struct cfg80211_sched_scan_request *req)481{482struct brcmf_pno_info *pi;483int ret;484485#if defined(__linux__)486brcmf_dbg(TRACE, "reqid=%llu\n", req->reqid);487#elif defined(__FreeBSD__)488brcmf_dbg(TRACE, "reqid=%ju\n", (uintmax_t)req->reqid);489#endif490491pi = ifp_to_pno(ifp);492ret = brcmf_pno_store_request(pi, req);493if (ret < 0)494return ret;495496ret = brcmf_pno_config_sched_scans(ifp);497if (ret < 0) {498brcmf_pno_remove_request(pi, req->reqid);499if (pi->n_reqs)500(void)brcmf_pno_config_sched_scans(ifp);501return ret;502}503return 0;504}505506int brcmf_pno_stop_sched_scan(struct brcmf_if *ifp, u64 reqid)507{508struct brcmf_pno_info *pi;509int err;510511#if defined(__linux__)512brcmf_dbg(TRACE, "reqid=%llu\n", reqid);513#elif defined(__FreeBSD__)514brcmf_dbg(TRACE, "reqid=%ju\n", (uintmax_t)reqid);515#endif516517pi = ifp_to_pno(ifp);518519/* No PNO request */520if (!pi->n_reqs)521return 0;522523err = brcmf_pno_remove_request(pi, reqid);524if (err)525return err;526527brcmf_pno_clean(ifp);528529if (pi->n_reqs)530(void)brcmf_pno_config_sched_scans(ifp);531532return 0;533}534535int brcmf_pno_attach(struct brcmf_cfg80211_info *cfg)536{537struct brcmf_pno_info *pi;538539brcmf_dbg(TRACE, "enter\n");540pi = kzalloc(sizeof(*pi), GFP_KERNEL);541if (!pi)542return -ENOMEM;543544cfg->pno = pi;545mutex_init(&pi->req_lock);546return 0;547}548549void brcmf_pno_detach(struct brcmf_cfg80211_info *cfg)550{551struct brcmf_pno_info *pi;552553brcmf_dbg(TRACE, "enter\n");554pi = cfg->pno;555cfg->pno = NULL;556557WARN_ON(pi->n_reqs);558mutex_destroy(&pi->req_lock);559kfree(pi);560}561562void brcmf_pno_wiphy_params(struct wiphy *wiphy, bool gscan)563{564/* scheduled scan settings */565wiphy->max_sched_scan_reqs = gscan ? BRCMF_PNO_MAX_BUCKETS : 1;566wiphy->max_sched_scan_ssids = BRCMF_PNO_MAX_PFN_COUNT;567wiphy->max_match_sets = BRCMF_PNO_MAX_PFN_COUNT;568wiphy->max_sched_scan_ie_len = BRCMF_SCAN_IE_LEN_MAX;569wiphy->max_sched_scan_plan_interval = BRCMF_PNO_SCHED_SCAN_MAX_PERIOD;570}571572u64 brcmf_pno_find_reqid_by_bucket(struct brcmf_pno_info *pi, u32 bucket)573{574u64 reqid = 0;575576mutex_lock(&pi->req_lock);577578if (bucket < pi->n_reqs)579reqid = pi->reqs[bucket]->reqid;580581mutex_unlock(&pi->req_lock);582return reqid;583}584585u32 brcmf_pno_get_bucket_map(struct brcmf_pno_info *pi,586struct brcmf_pno_net_info_le *ni)587{588struct cfg80211_sched_scan_request *req;589struct cfg80211_match_set *ms;590u32 bucket_map = 0;591int i, j;592593mutex_lock(&pi->req_lock);594for (i = 0; i < pi->n_reqs; i++) {595req = pi->reqs[i];596597if (!req->n_match_sets)598continue;599for (j = 0; j < req->n_match_sets; j++) {600ms = &req->match_sets[j];601if (ms->ssid.ssid_len == ni->SSID_len &&602!memcmp(ms->ssid.ssid, ni->SSID, ni->SSID_len)) {603bucket_map |= BIT(i);604break;605}606if (is_valid_ether_addr(ms->bssid) &&607!memcmp(ms->bssid, ni->bssid, ETH_ALEN)) {608bucket_map |= BIT(i);609break;610}611}612}613mutex_unlock(&pi->req_lock);614return bucket_map;615}616617618