blob: 9939a0c5a09cb35335fe93b8574e104ab3e3f2ee [file] [log] [blame]
/*-
* Copyright (c) 2002-2005 Sam Leffler, Errno Consulting
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* Alternatively, this software may be distributed under the terms of the
* GNU General Public License ("GPL") version 2 as published by the Free
* Software Foundation.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* $Id: ieee80211_scan_ap.c 1721 2006-09-20 08:45:13Z mentor $
*/
#ifndef EXPORT_SYMTAB
#define EXPORT_SYMTAB
#endif
/*
* IEEE 802.11 ap scanning support.
*/
#ifndef AUTOCONF_INCLUDED
#include <linux/config.h>
#endif
#include <linux/version.h>
#include <linux/module.h>
#include <linux/skbuff.h>
#include <linux/netdevice.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/random.h>
#include "net80211/if_media.h"
#include "net80211/ieee80211_var.h"
#include "net80211/ieee80211_mlme_statistics.h"
static int ap_flush(struct ieee80211_scan_state *);
static void action_tasklet(IEEE80211_TQUEUE_ARG);
static int
lock_ap_list(struct ap_state *as)
{
int bh_disabled = !in_softirq() && !irqs_disabled();
WARN_ON_ONCE(in_irq());
spin_lock(&as->asl_lock);
if (bh_disabled) {
local_bh_disable();
}
return bh_disabled;
}
static void
unlock_ap_list(struct ap_state *as, int bh_disabled)
{
if (bh_disabled) {
local_bh_enable();
}
spin_unlock(&as->asl_lock);
}
static int
ap_lock(struct ieee80211_scan_state *ss)
{
struct ap_state *as = ss->ss_priv;
return lock_ap_list(as);
}
static void ap_unlock(struct ieee80211_scan_state *ss, int bh_disabled)
{
struct ap_state *as = ss->ss_priv;
unlock_ap_list(as, bh_disabled);
}
static void
cleanup_se(struct ap_scan_entry *se)
{
struct ieee80211_scan_entry *ise = &se->base;
if (ise->se_wpa_ie) {
FREE(ise->se_wpa_ie, M_DEVBUF);
ise->se_wpa_ie = NULL;
}
if (ise->se_rsn_ie) {
FREE(ise->se_rsn_ie, M_DEVBUF);
ise->se_rsn_ie = NULL;
}
if (ise->se_wme_ie) {
FREE(ise->se_wme_ie, M_DEVBUF);
ise->se_wme_ie = NULL;
}
if (ise->se_wsc_ie) {
FREE(ise->se_wsc_ie, M_DEVBUF);
ise->se_wsc_ie = NULL;
}
if (ise->se_htcap_ie) {
FREE(ise->se_htcap_ie, M_DEVBUF);
ise->se_htcap_ie = NULL;
}
if (ise->se_htinfo_ie) {
FREE(ise->se_htinfo_ie, M_DEVBUF);
ise->se_htinfo_ie = NULL;
}
if (ise->se_vhtcap_ie) {
FREE(ise->se_vhtcap_ie, M_DEVBUF);
ise->se_vhtcap_ie = NULL;
}
if (ise->se_vhtop_ie) {
FREE(ise->se_vhtop_ie, M_DEVBUF);
ise->se_vhtop_ie = NULL;
}
if (ise->se_ath_ie) {
FREE(ise->se_ath_ie, M_DEVBUF);
ise->se_ath_ie = NULL;
}
if (ise->se_qtn_ie)
{
FREE(ise->se_qtn_ie, M_DEVBUF);
ise->se_qtn_ie = NULL;
}
if (ise->se_ext_bssid_ie) {
FREE(ise->se_ext_bssid_ie, M_DEVBUF);
ise->se_ext_bssid_ie = NULL;
}
}
static void
free_se(struct ap_scan_entry *se)
{
cleanup_se(se);
FREE(se, M_80211_SCAN);
}
static void
free_se_request(struct ap_scan_entry *se)
{
if (se->se_inuse) {
se->se_request_to_free = 1;
} else {
free_se(se);
}
}
static void
free_se_process(struct ap_scan_entry *se)
{
if (!se->se_inuse && se->se_request_to_free) {
free_se(se);
}
}
static void
set_se_inuse(struct ap_scan_entry *se)
{
se->se_inuse = 1;
}
static void
reset_se_inuse(struct ap_scan_entry *se)
{
se->se_inuse = 0;
free_se_process(se);
}
/*
* Attach prior to any scanning work.
*/
static int
ap_attach(struct ieee80211_scan_state *ss)
{
struct ap_state *as;
int i;
_MOD_INC_USE(THIS_MODULE, return 0);
MALLOC(as, struct ap_state *, sizeof(struct ap_state),
M_SCANCACHE, M_NOWAIT | M_ZERO);
if (as == NULL) {
if (printk_ratelimit())
printk("failed to attach before scanning\n");
return 0;
}
as->as_age = AP_PURGE_SCANS;
ss->ss_priv = as;
IEEE80211_INIT_TQUEUE(&as->as_actiontq, action_tasklet, ss);
spin_lock_init(&as->asl_lock);
for (i = 0; i < IEEE80211_CHAN_MAX; i++) {
TAILQ_INIT(&as->as_scan_list[i].asl_head);
}
return 1;
}
static int
ap_flush_asl_table(struct ieee80211_scan_state *ss)
{
struct ap_state *as = ss->ss_priv;
struct ap_scan_entry *se, *next;
int i;
for (i = 0; i < IEEE80211_CHAN_MAX; i++) {
TAILQ_FOREACH_SAFE(se, &as->as_scan_list[i].asl_head, ase_list, next) {
TAILQ_REMOVE(&as->as_scan_list[i].asl_head, se, ase_list);
free_se_request(se);
if (as->as_entry_num > 0)
as->as_entry_num--;
}
}
return 0;
}
/*
* Cleanup any private state.
*/
static int
ap_detach(struct ieee80211_scan_state *ss)
{
struct ap_state *as = ss->ss_priv;
if (as != NULL) {
ap_flush_asl_table(ss);
FREE(as, M_SCANCACHE);
}
_MOD_DEC_USE(THIS_MODULE);
return 1;
}
/*
* Flush all per-scan state.
*/
static int
ap_flush(struct ieee80211_scan_state *ss)
{
struct ap_state *as = ss->ss_priv;
int bh_disabled;
bh_disabled = lock_ap_list(as);
ap_flush_asl_table(ss);
unlock_ap_list(as, bh_disabled);
memset(as->as_maxrssi, 0, sizeof(as->as_maxrssi));
memset(as->as_numpkts, 0, sizeof(as->as_numpkts));
memset(as->as_aci, 0, sizeof(as->as_aci));
memset(as->as_cci, 0, sizeof(as->as_aci));
memset(as->as_numbeacons, 0, sizeof(as->as_numbeacons));
memset(as->as_chanmetric, 0, sizeof(as->as_chanmetric));
memset(as->as_obss_chanlayout, 0, sizeof(as->as_obss_chanlayout));
ss->ss_last = 0; /* ensure no channel will be picked */
return 0;
}
static int
find11gchannel(struct ieee80211com *ic, int i, int freq)
{
const struct ieee80211_channel *c;
int j;
/*
* The normal ordering in the channel list is b channel
* immediately followed by g so optimize the search for
* this. We'll still do a full search just in case.
*/
for (j = i+1; j < ic->ic_nchans; j++) {
c = &ic->ic_channels[j];
if (c->ic_freq == freq && IEEE80211_IS_CHAN_ANYG(c))
return 1;
}
for (j = 0; j < i; j++) {
c = &ic->ic_channels[j];
if (c->ic_freq == freq && IEEE80211_IS_CHAN_ANYG(c))
return 1;
}
return 0;
}
/*
* Start an ap scan by populating the channel list.
*/
static int
ap_start(struct ieee80211_scan_state *ss, struct ieee80211vap *vap)
{
struct ieee80211com *ic = vap->iv_ic;
struct ieee80211_channel *c;
int i;
ss->ss_last = 0;
if (ic->ic_des_mode == IEEE80211_MODE_AUTO) {
for (i = 0; i < ic->ic_nchans; i++) {
c = &ic->ic_channels[i];
if (c == NULL || isclr(ic->ic_chan_active, c->ic_ieee))
continue;
if (IEEE80211_IS_CHAN_TURBO(c)) {
/* XR is not supported on turbo channels */
if (vap->iv_ath_cap & IEEE80211_ATHC_XR)
continue;
/* dynamic channels are scanned in base mode */
if (!IEEE80211_IS_CHAN_ST(c))
continue;
} else {
/*
* Use any 11g channel instead of 11b one.
*/
if (IEEE80211_IS_CHAN_B(c) &&
find11gchannel(ic, i, c->ic_freq))
continue;
}
if (c->ic_flags & IEEE80211_CHAN_RADAR)
continue;
if (ss->ss_last >= IEEE80211_SCAN_MAX)
break;
/* avoid DFS channels if so configured */
if ((ss->ss_flags & IEEE80211_SCAN_NO_DFS) && (c->ic_flags & IEEE80211_CHAN_DFS))
continue;
ss->ss_chans[ss->ss_last++] = c;
}
} else {
u_int modeflags;
modeflags = ieee80211_get_chanflags(ic->ic_des_mode);
if (vap->iv_ath_cap & IEEE80211_ATHC_TURBOP && modeflags != IEEE80211_CHAN_ST) {
if (ic->ic_des_mode == IEEE80211_MODE_11G)
modeflags = IEEE80211_CHAN_108G;
else
modeflags = IEEE80211_CHAN_108A;
}
for (i = 0; i < ic->ic_nchans; i++) {
c = &ic->ic_channels[i];
if (c == NULL || isclr(ic->ic_chan_active, c->ic_ieee))
continue;
if ((c->ic_flags & modeflags) != modeflags)
continue;
/* XR is not supported on turbo channels */
if (IEEE80211_IS_CHAN_TURBO(c) && vap->iv_ath_cap & IEEE80211_ATHC_XR)
continue;
if (ss->ss_last >= IEEE80211_SCAN_MAX)
break;
/*
* do not select static turbo channels if the mode is not
* static turbo .
*/
if (IEEE80211_IS_CHAN_STURBO(c) && ic->ic_des_mode != IEEE80211_MODE_MAX)
continue;
/* No dfs interference detected channels */
if (c->ic_flags & IEEE80211_CHAN_RADAR)
continue;
/* avoid DFS channels if so configured */
if ((ss->ss_flags & IEEE80211_SCAN_NO_DFS) && (c->ic_flags & IEEE80211_CHAN_DFS))
continue;
ss->ss_chans[ss->ss_last++] = c;
}
}
ss->ss_next = 0;
/* XXX tunables */
ss->ss_mindwell = msecs_to_jiffies(ic->ic_mindwell_active);
ss->ss_maxdwell = msecs_to_jiffies(ic->ic_maxdwell_active);
ss->ss_maxdwell_passive = msecs_to_jiffies(ic->ic_maxdwell_passive);
ss->ss_mindwell_passive = msecs_to_jiffies(ic->ic_mindwell_passive);
#ifdef IEEE80211_DEBUG
if (ieee80211_msg_scan(vap)) {
printf("%s: scan set ", vap->iv_dev->name);
ieee80211_scan_dump_channels(ss);
printf(" dwell min %ld max %ld\n",
ss->ss_mindwell, ss->ss_maxdwell);
}
#endif /* IEEE80211_DEBUG */
return 0;
}
/*
* Restart a bg scan.
*/
static int
ap_restart(struct ieee80211_scan_state *ss, struct ieee80211vap *vap)
{
return 0;
}
/*
* Cancel an ongoing scan.
*/
static int
ap_cancel(struct ieee80211_scan_state *ss, struct ieee80211vap *vap)
{
struct ap_state *as = ss->ss_priv;
IEEE80211_CANCEL_TQUEUE(&as->as_actiontq);
return 0;
}
static int
ap_add(struct ieee80211_scan_state *ss, const struct ieee80211_scanparams *sp,
const struct ieee80211_frame *wh, int subtype, int rssi, int rstamp)
{
struct ap_state *as = ss->ss_priv;
struct ieee80211vap *vap = ss->ss_vap;
struct ieee80211com *ic = vap->iv_ic;
struct ap_scan_entry *se;
struct ieee80211_scan_entry *ise;
const u_int8_t *macaddr = wh->i_addr2;
int bh_disabled;
int chan;
int found = 0;
if (is_channel_valid(sp->chan)) {
chan = sp->chan;
} else {
chan = ieee80211_chan2ieee(ic, ic->ic_curchan);
if (!is_channel_valid(chan))
return 1;
}
/* XXX better quantification of channel use? */
/* XXX count bss's? */
/* Now we Only count beacons from different bss for better quantification of channel use */
if (subtype == IEEE80211_FC0_SUBTYPE_BEACON) {
if (rssi > as->as_maxrssi[chan])
as->as_maxrssi[chan] = rssi;
}
as->as_numpkts[chan]++;
bh_disabled = lock_ap_list(as);
TAILQ_FOREACH(se, &as->as_scan_list[chan].asl_head, ase_list) {
if (IEEE80211_ADDR_EQ(se->base.se_macaddr, macaddr)) {
found = 1;
break;
}
}
if (!found) {
if (as->as_entry_num >= ic->ic_scan_tbl_len_max) {
if (printk_ratelimit())
printk("scan found %u scan results but the list is"
" restricted to %u entries\n", as->as_entry_num,
ic->ic_scan_tbl_len_max);
unlock_ap_list(as, bh_disabled);
return 0;
}
MALLOC(se, struct ap_scan_entry *, sizeof(*se), M_80211_SCAN, M_NOWAIT | M_ZERO);
if (se == NULL) {
if (printk_ratelimit())
printk("failed to allocate new scan entry\n");
unlock_ap_list(as, bh_disabled);
return 0;
}
as->as_entry_num++;
IEEE80211_ADDR_COPY(se->base.se_macaddr, macaddr);
TAILQ_INSERT_TAIL(&as->as_scan_list[chan].asl_head, se, ase_list);
if (subtype == IEEE80211_FC0_SUBTYPE_BEACON) {
as->as_numbeacons[chan]++;
}
}
ise = &se->base;
ieee80211_add_scan_entry(ise, sp, wh, subtype, rssi, rstamp);
ieee80211_scan_check_secondary_channel(ss, ise);
if (se->se_lastupdate == 0) { /* first sample */
se->se_avgrssi = RSSI_IN(rssi);
} else { /* avg with previous samples */
RSSI_LPF(se->se_avgrssi, rssi);
}
ise->se_rssi = RSSI_GET(se->se_avgrssi);
unlock_ap_list(as, bh_disabled);
se->se_lastupdate = jiffies; /* update time */
se->se_seen = 1;
se->se_notseen = 0;
return 1;
}
enum chan_sel_algorithm {
CHAN_SEL_CLEAREST = 0, /* Select the clearest channel */
CHAN_SEL_DFS_REENTRY = 1, /* Select the channel based on DFS entry/re-entry requirement */
CHAN_SEL_MAX = 2
};
typedef struct
{
int tx_power_factor; /*Tx power weighting factor*/
int aci_factor; /*ACI weighting factor*/
int cci_factor; /*CCI weighting factor*/
int dfs_factor; /*DFS weighting factor*/
int beacon_factor; /*Beacon number weighting factor */
} decision_metric_factor;
/*
* Weighting factor for TX power is 2, because we have to multiply the CCI factor by 2
* to prevent losing precision when deriving the ACI, as the ACI is 1/2 of the CCI on
* an adjacent channel.
*/
static const decision_metric_factor g_dm_factor[CHAN_SEL_MAX] =
{
{2, -1, -1, 0, -1}, /* CHAN_SEL_CLEAREST */
{2, -1, -1, 8, -1} /* CHAN_SEL_DFS_REENTRY */
};
#define QTN_CHAN_METRIC_BASE 160 /* to make sure the channel metric not to be negative */
#define QTN_METRIC_CCI_LIMIT 16
#define QTN_METRIC_BEACON_LIMIT 4
#define QTN_AS_CCA_INTF_DIVIDER (IEEE80211_SCS_CCA_INTF_SCALE / QTN_METRIC_CCI_LIMIT)
/* Some custom knobs for out ap scan alg */
#define QTN_APSCAN_DFS_BIAS 10 /* Positive bias added for DFS channels */
#define QTN_APSCAN_METSHIFT 16 /* Precision of the metric. 16.16 format*/
#define QTN_APSCAN_TXPOWER_RANDOM_LIMIT 4
enum ieee802111_scan_skipchan_reason {
IEEE80211_SCAN_SKIPCHAN_REASON_INVALID = 1,
IEEE80211_SCAN_SKIPCHAN_REASON_DFS = 2,
IEEE80211_SCAN_SKIPCHAN_REASON_AVAIL = 3,
IEEE80211_SCAN_SKIPCHAN_REASON_NONAVAIL = 4,
IEEE80211_SCAN_SKIPCHAN_REASON_RADAR = 5,
IEEE80211_SCAN_SKIPCHAN_REASON_TURBO = 6,
IEEE80211_SCAN_SKIPCHAN_REASON_PURE20 = 7,
IEEE80211_SCAN_SKIPCHAN_REASON_MISMATCH_NOTDFS = 8,
IEEE80211_SCAN_SKIPCHAN_REASON_MISMATCH_DFS = 9,
IEEE80211_SCAN_SKIPCHAN_REASON_MISMATCH_BW = 10,
IEEE80211_SCAN_SKIPCHAN_REASON_METRIC_BETTER = 11,
IEEE80211_SCAN_SKIPCHAN_REASON_PRI_INACTIVE = 12,
IEEE80211_SCAN_SKIPCHAN_REASON_WEATHER_CHAN = 13,
IEEE80211_SCAN_SKIPCHAN_REASON_NON_DFS = 14,
};
static void local_ap_pick_channel_debug(struct ieee80211_scan_state *ss, int chan, int skip_reason)
{
struct ap_state *as = ss->ss_priv;
if (skip_reason == IEEE80211_SCAN_SKIPCHAN_REASON_INVALID)
return;
IEEE80211_DPRINTF(ss->ss_vap, IEEE80211_MSG_SCAN,
"ap_pick_channel: channel %3u rssi %2d numbss %2d numpkts %2d metric %3d.%-5d (%8d) reason %2d\n",
chan, as->as_maxrssi[chan], as->as_numbeacons[chan], as->as_numpkts[chan],
as->as_chanmetric[chan] >> QTN_APSCAN_METSHIFT,
as->as_chanmetric[chan] & ((1<<QTN_APSCAN_METSHIFT)-1), as->as_chanmetric[chan],
skip_reason);
}
static int local_ap_metric_compare_by_chan(struct ieee80211com *ic, int32_t chan1, int32_t chan2)
{
struct ap_state *as = ic->ic_scan->ss_priv;
if (as->as_chanmetric[chan1] > as->as_chanmetric[chan2])
return 1;
else if (as->as_chanmetric[chan1] == as->as_chanmetric[chan2])
return 0;
return -1;
}
/*
* Pick a quiet channel to use for ap operation.
*
* (i) When ap_pick_channel is being called when channel=0, max_boot_cac=0
* ap_pick_channel picks from set of non-DFS channels only
*
* (ii) When ap_pick_channel is being called when channel=non-DFS, max_boot_cac=0
* DUT becomes operational on channel=non-DFS
*
* (iii) When ap_pick_channel is being called when channel=DFS, max_boot_cac=0
* DUT performs CAC on channel=DFS, start operation after CAC completes
*
* (iv) When ap_pick_channel is being called when channel=0, max_boot_cac=140
* DUT performs ICAC
* clears two channels
* Triggers auto-channel and selects a channel from available cleared channel list
*
* (v) when ap_pick_channel is being called when channel=non-DFS, max_boot_cac=140
* DUT performs ICAC
* clears two channels
* Starts operation on channel=non-DFS
*
* (vi) When ap_pick_channel is being called when channel=DFS, max_boot_cac=140
* DUT performs CAC on channel=DFS
* DUT clears one more DFS channel
* Starts operation on channel=DFS
*/
static struct ieee80211_channel *
ap_pick_channel(struct ieee80211com *ic, struct ieee80211_scan_state *ss, int flags)
{
struct ap_state *as = ss->ss_priv;
decision_metric_factor dm_factor;
int i, chan=0, chan2, bestmetricchan = -1, bestchanix = -1;
struct ieee80211_channel *bestchan = NULL;
struct ieee80211_channel *ic_best_chan = NULL;
struct ieee80211_channel *fs1_bestchan = NULL;
struct ieee80211_channel *fs1_secbestchan = NULL;
struct ieee80211_channel *fs2_bestchan = NULL;
struct ieee80211_channel *fs2_secbestchan = NULL;
char rndbuf[2];
int txpower_random;
int cur_bw;
int pri_inactive;
int skip_reason = 0;
if (IS_IEEE80211_24G_BAND(ic)) {
bestchan = ieee80211_chanset_pick_channel(ss->ss_vap);
goto end;
}
/*
* Convert CCA interference to CCI factor
*/
for (i = 0; i < ss->ss_last; i++) {
chan = ieee80211_chan2ieee(ic, ss->ss_chans[i]);
if (!is_channel_valid(chan))
continue;
if (as->as_cca_intf[chan] <= IEEE80211_SCS_CCA_INTF_SCALE) {
as->as_cci[chan] = 2 * as->as_cca_intf[chan] / QTN_AS_CCA_INTF_DIVIDER;
as->as_cci[chan] = MIN(as->as_cci[chan], QTN_METRIC_CCI_LIMIT);
} else {
as->as_cci[chan] = 0;
}
/* Reset ACI here */
as->as_aci[chan] = 0;
}
/*
* Derive ACI (Adjacent Channel Interference) from CCI.
*/
for (i = 0; i < ss->ss_last; i++) {
chan = ieee80211_chan2ieee(ic, ss->ss_chans[i]);
if (!is_channel_valid(chan))
continue;
/* Adjust adjacent channel metrics to bias against close selection */
if (i != 0) {
chan2 = ieee80211_chan2ieee(ic, ss->ss_chans[i-1]);
if (!is_channel_valid(chan2))
continue;
if (chan2 >= (chan - 4)){
as->as_aci[chan2] += (as->as_cci[chan] >> 1);
}
}
if (i != ss->ss_last - 1) {
chan2 = ieee80211_chan2ieee(ic, ss->ss_chans[i+1]);
if (!is_channel_valid(chan2))
continue;
if (chan2 <= (chan + 4)){
as->as_aci[chan2] += (as->as_cci[chan] >> 1);
}
}
}
/* DFS entry enabled by default */
memcpy(&dm_factor, &g_dm_factor[CHAN_SEL_DFS_REENTRY], sizeof(dm_factor));
if (ic->ic_dm_factor.flags) {
if (ic->ic_dm_factor.flags & DM_FLAG_TXPOWER_FACTOR_PRESENT) {
dm_factor.tx_power_factor = ic->ic_dm_factor.txpower_factor;
}
if (ic->ic_dm_factor.flags & DM_FLAG_ACI_FACTOR_PRESENT) {
dm_factor.aci_factor = ic->ic_dm_factor.aci_factor;
}
if (ic->ic_dm_factor.flags & DM_FLAG_CCI_FACTOR_PRESENT) {
dm_factor.cci_factor = ic->ic_dm_factor.cci_factor;
}
if (ic->ic_dm_factor.flags & DM_FLAG_DFS_FACTOR_PRESENT) {
dm_factor.dfs_factor = ic->ic_dm_factor.dfs_factor;
}
if (ic->ic_dm_factor.flags & DM_FLAG_BEACON_FACTOR_PRESENT) {
dm_factor.beacon_factor = ic->ic_dm_factor.beacon_factor;
}
}
/*
* Compute Channel Metric (Decision Metric) based on Hossein D's formula.
*/
for (i = 0; i < ss->ss_last; i++) {
struct ieee80211_channel *c = ss->ss_chans[i];
chan = ieee80211_chan2ieee(ic, ss->ss_chans[i]);
if (!is_channel_valid(chan))
continue;
/* Add noise to txpower to improve random selection within channels with small txpower difference */
get_random_bytes(rndbuf, 1);
txpower_random = rndbuf[0] / (0xFF / (QTN_APSCAN_TXPOWER_RANDOM_LIMIT + 1));
as->as_chanmetric[chan] = QTN_CHAN_METRIC_BASE
+ dm_factor.tx_power_factor * (c->ic_maxpower + txpower_random)
+ dm_factor.cci_factor * as->as_cci[chan]
+ dm_factor.aci_factor * as->as_aci[chan]
+ dm_factor.dfs_factor * ((c->ic_flags & IEEE80211_CHAN_DFS) ? 1 : 0)
+ dm_factor.beacon_factor * MIN(as->as_numbeacons[chan], QTN_METRIC_BEACON_LIMIT);
/* Add a little noise */
get_random_bytes(rndbuf, sizeof(rndbuf));
as->as_chanmetric[chan] <<= QTN_APSCAN_METSHIFT;
as->as_chanmetric[chan] += (rndbuf[0] << 8) | rndbuf[1];
}
cur_bw = ieee80211_get_bw(ic);
/* NB: use scan list order to preserve channel preference */
for (i = 0; i < ss->ss_last; local_ap_pick_channel_debug(ss, chan, skip_reason), i++) {
ic_best_chan = ss->ss_chans[i];
chan = ieee80211_chan2ieee(ic, ic_best_chan);
if (!is_channel_valid(chan)) {
skip_reason = IEEE80211_SCAN_SKIPCHAN_REASON_INVALID;
continue;
}
if (ic_best_chan == NULL) {
skip_reason = IEEE80211_SCAN_SKIPCHAN_REASON_INVALID;
continue;
}
/* Don't bypass the check of current channel in ic_check_channel */
if (flags == IEEE80211_SCAN_PICK_NOT_AVAILABLE_DFS_ONLY ||
flags == IEEE80211_SCAN_PICK_AVAILABLE_ANY_CHANNEL) {
ic->ic_chan_is_set = 0;
}
if (!ic->ic_check_channel(ic, ic_best_chan, 0, 0)) {
skip_reason = IEEE80211_SCAN_SKIPCHAN_REASON_RADAR;
continue;
}
if ((flags & IEEE80211_SCAN_NO_DFS)
&& (ic_best_chan->ic_flags & IEEE80211_CHAN_DFS)) {
skip_reason = IEEE80211_SCAN_SKIPCHAN_REASON_DFS;
continue;
}
if ((flags == IEEE80211_SCAN_PICK_ANY_DFS) &&
(!(ic_best_chan->ic_flags & IEEE80211_CHAN_DFS))) {
skip_reason = IEEE80211_SCAN_SKIPCHAN_REASON_NON_DFS;
continue;
}
/* IEEE80211_SCAN_PICK_NOT_AVAILABLE_DFS_ONLY is set only during ICAC */
if (flags == IEEE80211_SCAN_PICK_NOT_AVAILABLE_DFS_ONLY) {
if (ic->ic_dfs_chans_available_for_cac(ic, ic_best_chan) == false) {
skip_reason = IEEE80211_SCAN_SKIPCHAN_REASON_AVAIL;
continue;
}
}
/* IEEE80211_SCAN_PICK_AVAILABLE_ANY_CHANNEL is set only after ICAC */
/* When channel=0, max_cac_boot=140
* perform cac on two of not yet available DFS channels
* Mark the DFS channels as available after CAC complettion
* At the end of initial CAC, choose the best available channel
* from initial metric;
*/
if (flags == IEEE80211_SCAN_PICK_AVAILABLE_ANY_CHANNEL) {
if(!ieee80211_is_chan_available(ic_best_chan)) {
skip_reason = IEEE80211_SCAN_SKIPCHAN_REASON_NONAVAIL;
continue;
}
}
/*
* If the channel is unoccupied the max rssi
* should be zero; just take it. Otherwise
* track the channel with the lowest rssi and
* use that when all channels appear occupied.
*
* Check for channel interference, and if found,
* skip the channel. We assume that all channels
* will be checked so atleast one can be found
* suitable and will change. IF this changes,
* then we must know when we "have to" change
* channels for radar and move off.
*/
if (flags & IEEE80211_SCAN_KEEPMODE) {
if (ic->ic_curchan != NULL) {
if ((ic_best_chan->ic_flags & IEEE80211_CHAN_ALLTURBO) !=
(ic->ic_curchan->ic_flags & IEEE80211_CHAN_ALLTURBO)) {
skip_reason = IEEE80211_SCAN_SKIPCHAN_REASON_TURBO;
continue;
}
}
}
if (ic->ic_rf_chipid != CHIPID_DUAL) {
/* hzw: temporary disable these checking for RFIC5 */
/* FIXME: Temporarily dont select any pure 20 channels */
if (!(ic_best_chan->ic_flags & IEEE80211_CHAN_HT40)){
skip_reason = IEEE80211_SCAN_SKIPCHAN_REASON_PURE20;
continue;
}
if (((ss->ss_pick_flags & IEEE80211_PICK_DOMIAN_MASK) == IEEE80211_PICK_DFS) &&
!(ic_best_chan->ic_flags & IEEE80211_CHAN_DFS)) {
skip_reason = IEEE80211_SCAN_SKIPCHAN_REASON_MISMATCH_NOTDFS;
continue;
} else if (((ss->ss_pick_flags & IEEE80211_PICK_DOMIAN_MASK) == IEEE80211_PICK_NONDFS) &&
(ic_best_chan->ic_flags & IEEE80211_CHAN_DFS)) {
skip_reason = IEEE80211_SCAN_SKIPCHAN_REASON_MISMATCH_DFS;
continue;
}
}
pri_inactive = isset(ic->ic_chan_pri_inactive, chan) ? 1 : 0;
if (cur_bw >= BW_HT40) {
if (((cur_bw == BW_HT40) && !(ic_best_chan->ic_flags & IEEE80211_CHAN_HT40)) ||
((cur_bw >= BW_HT80) && !(ic_best_chan->ic_flags & IEEE80211_CHAN_VHT80))) {
skip_reason = IEEE80211_SCAN_SKIPCHAN_REASON_MISMATCH_BW;
continue;
}
/* use the worst chanmetric as the metric of this chan set */
chan2 = ieee80211_find_sec_chan(ic_best_chan);
if (chan2 == 0 || (isclr(ic->ic_chan_pri_inactive, chan2) &&
(as->as_chanmetric[chan] > as->as_chanmetric[chan2]))) {
skip_reason = IEEE80211_SCAN_SKIPCHAN_REASON_METRIC_BETTER;
continue;
}
if (isclr(ic->ic_chan_pri_inactive, chan2)) {
pri_inactive = 0;
}
if (cur_bw >= BW_HT80) {
chan2 = ieee80211_find_sec40u_chan(ic_best_chan);
if (chan2 == 0 || (isclr(ic->ic_chan_pri_inactive, chan2) &&
(as->as_chanmetric[chan] > as->as_chanmetric[chan2]))) {
skip_reason = IEEE80211_SCAN_SKIPCHAN_REASON_METRIC_BETTER;
continue;
}
if (isclr(ic->ic_chan_pri_inactive, chan2)) {
pri_inactive = 0;
}
chan2 = ieee80211_find_sec40l_chan(ic_best_chan);
if (chan2 == 0 || (isclr(ic->ic_chan_pri_inactive, chan2) &&
(as->as_chanmetric[chan] > as->as_chanmetric[chan2]))) {
skip_reason = IEEE80211_SCAN_SKIPCHAN_REASON_METRIC_BETTER;
continue;
}
if (isclr(ic->ic_chan_pri_inactive, chan2)) {
pri_inactive = 0;
}
}
}
if (pri_inactive) {
/* All the sub channel can't be primary channel */
skip_reason = IEEE80211_SCAN_SKIPCHAN_REASON_PRI_INACTIVE;
continue;
}
if (!ic->ic_weachan_cac_allowed &&
ieee80211_is_on_weather_channel(ic, ic_best_chan)) {
/*
* Don't pick weather channel in auto channel mode since it need
* too long CAC time, and it also fix the backward compatibility
* issue with the stations which don't support weather channels
*/
skip_reason = IEEE80211_SCAN_SKIPCHAN_REASON_WEATHER_CHAN;
continue;
}
if (isclr(ic->ic_chan_pri_inactive, chan) && ((bestmetricchan == -1) ||
(as->as_chanmetric[chan] > as->as_chanmetric[bestmetricchan]))) {
bestmetricchan = chan;
bestchanix = i;
}
ieee80211_update_alternate_channels(ic,
ic_best_chan,
&fs2_bestchan,
&fs2_secbestchan,
local_ap_metric_compare_by_chan);
ic_best_chan = ieee80211_scan_switch_pri_chan(ss, ic_best_chan);
if (bestchan == NULL ||
(ic_best_chan &&
as->as_chanmetric[ic_best_chan->ic_ieee] > as->as_chanmetric[bestchan->ic_ieee])) {
bestchan = ic_best_chan;
}
ieee80211_update_alternate_channels(ic,
ic_best_chan,
&fs1_bestchan,
&fs1_secbestchan,
local_ap_metric_compare_by_chan);
skip_reason = 0;
}
/*
* when ap_pick_channel is being called when channel=dfs, max_boot_cac=140
* dut performs cac on channel=dfs
* dut clears one more dfs channel
* starts operation on channel=dfs
*/
/* IEEE80211_SCAN_PICK_NOT_AVAILABLE_DFS_ONLY is set only during ICAC */
if (((flags == IEEE80211_SCAN_PICK_NOT_AVAILABLE_DFS_ONLY) &&
(ic->ic_des_chan_after_init_cac)) && (!ic->ic_ignore_init_scan_icac)) {
ic_best_chan = ieee80211_find_channel_by_ieee(ic, ic->ic_des_chan_after_init_cac);
if (ic_best_chan && (ic->ic_dfs_chans_available_for_cac(ic, ic_best_chan))) {
IEEE80211_DPRINTF(ss->ss_vap, IEEE80211_MSG_SCAN,
"%s: original ic_des_chan_after_init_cac channel %d\n",
__func__, ic_best_chan->ic_ieee);
if (isset(ic->ic_chan_pri_inactive, ic_best_chan->ic_ieee)) {
IEEE80211_DPRINTF(ss->ss_vap, IEEE80211_MSG_SCAN,
"%s: ic_des_chan_after_init_cac channel %d in inactive primary"
" channel list, try to switch another sub-channel\n", __func__,
ic_best_chan->ic_ieee);
ic_best_chan = ieee80211_scan_switch_pri_chan(ss, ic_best_chan);
if (ic_best_chan) {
bestchan = ic_best_chan;
IEEE80211_DPRINTF(ss->ss_vap, IEEE80211_MSG_SCAN,
"%s: new ic_des_chan_after_init_cac channel %d\n",
__func__, ic_best_chan->ic_ieee);
} else {
IEEE80211_DPRINTF(ss->ss_vap, IEEE80211_MSG_SCAN,
"%s: fail to find new ic_des_chan_after_init_cac channel\n",
__func__);
}
} else {
bestchan = ic_best_chan;
}
}
}
if (!bestchan) {
if (bestchanix == -1 && ic->ic_bsschan == IEEE80211_CHAN_ANYC) {
bestchanix = 0;
IEEE80211_DPRINTF(ss->ss_vap, IEEE80211_MSG_SCAN,
"%s: no suitable channel, go to a default one\n", __func__);
}
if (bestchanix >= 0)
bestchan = ss->ss_chans[bestchanix];
}
if (bestchan)
IEEE80211_DPRINTF(ss->ss_vap, IEEE80211_MSG_SCAN, "%s: bestchan %d bestchan rssi %d\n",
__func__, bestchan->ic_ieee, as->as_maxrssi[bestchan->ic_ieee]);
if (ss->ss_flags & IEEE80211_SCAN_NOPICK)
ic_best_chan = ic->ic_bsschan;
else if ((ic->ic_des_chan_after_init_scan) && (!ic->ic_ignore_init_scan_icac))
ic_best_chan = ieee80211_find_channel_by_ieee(ic, ic->ic_des_chan_after_init_scan);
else if ((ic->ic_des_chan_after_init_cac) && (!ic->ic_ignore_init_scan_icac))
ic_best_chan = ieee80211_find_channel_by_ieee(ic, ic->ic_des_chan_after_init_cac);
else
ic_best_chan = bestchan;
ieee80211_ap_pick_alternate_channel(ic,
ic_best_chan,
fs1_bestchan,
fs1_secbestchan,
fs2_bestchan,
fs2_secbestchan);
IEEE80211_DPRINTF(ss->ss_vap, IEEE80211_MSG_SCAN,
"%s: Fast-switch alternate best channel updated to %d\n",
__func__, ic->ic_ieee_best_alt_chan);
end:
IEEE80211_DPRINTF(ss->ss_vap, IEEE80211_MSG_SCAN,
"%s: algorithm %s%s, pick in %s%s%s channels\n", __func__,
((ss->ss_pick_flags & IEEE80211_PICK_ALGORITHM_MASK) == IEEE80211_PICK_REENTRY) ? "dfs_reentry" : "",
((ss->ss_pick_flags & IEEE80211_PICK_ALGORITHM_MASK) == IEEE80211_PICK_CLEAREST) ? "clearest" : "",
((ss->ss_pick_flags & IEEE80211_PICK_DOMIAN_MASK) == IEEE80211_PICK_DFS) ? "dfs" : "",
((ss->ss_pick_flags & IEEE80211_PICK_DOMIAN_MASK) == IEEE80211_PICK_NONDFS) ? "non_dfs" : "",
((ss->ss_pick_flags & IEEE80211_PICK_DOMIAN_MASK) == IEEE80211_PICK_ALL) ? "all" : "");
ss->ss_pick_flags = IEEE80211_PICK_DEFAULT; /* clean the flag */
return bestchan;
}
/*
* Pick a quiet channel to use for ap operation.
*/
static int
ap_end(struct ieee80211_scan_state *ss, struct ieee80211vap *vap,
int (*action)(struct ieee80211vap *, const struct ieee80211_scan_entry *),
u_int32_t flags)
{
struct ieee80211_channel * bestchan = NULL;
struct ap_state *as = ss->ss_priv;
struct ieee80211com *ic = vap->iv_ic;
struct ieee80211_scan_entry se;
int ret;
KASSERT(vap->iv_opmode == IEEE80211_M_HOSTAP,
("wrong opmode %u", vap->iv_opmode));
/* scan end, no action and return */
if (ss->ss_flags & IEEE80211_SCAN_QTN_SEARCH_MBS)
return 1;
/* scan end, do DFS action and return */
if (ss->ss_flags & IEEE80211_SCAN_DFS_ACTION) {
ic->ic_dfs_action_scan_done();
return 1;
}
#ifdef QTN_BG_SCAN
if (ss->ss_flags & IEEE80211_SCAN_QTN_BGSCAN) {
ss->ss_pick_flags = IEEE80211_PICK_DEFAULT; /* clean the flag */
return 1;
}
#endif
memset(&se, 0, sizeof(se));
if (ic->ic_get_init_cac_duration(ic) > 0) {
IEEE80211_DPRINTF(ss->ss_vap, IEEE80211_MSG_SCAN,
"%s: pick dfs channels only: for eu ICAC\n", __func__);
flags = IEEE80211_SCAN_PICK_NOT_AVAILABLE_DFS_ONLY;
}
bestchan = ap_pick_channel(ic, ss, flags);
if (bestchan == NULL) {
IEEE80211_DPRINTF(ss->ss_vap, IEEE80211_MSG_SCAN,
"%s: no suitable channel! Go back!\n", vap->iv_dev->name);
/*
* When max_boot_cac is a very large value, all channels are cleared.
* Return to ICAC completion procedure
*/
if (ic->ic_get_init_cac_duration(ic) > 0) {
return 0;
}
if (ic->ic_bsschan != IEEE80211_CHAN_ANYC) {
se.se_chan = ic->ic_bsschan;
ret = 0;
} else if (!ieee80211_chanset_scan_finished(ic)) {
return 1;
} else {
return 0; /* restart scan */
}
} else {
struct ieee80211_channel *c;
/* XXX notify all vap's? */
/* if this is a dynamic turbo frequency , start with normal mode first */
c = bestchan;
if (IEEE80211_IS_CHAN_TURBO(c) && !IEEE80211_IS_CHAN_STURBO(c)) {
if ((c = ieee80211_find_channel(ic, c->ic_freq,
c->ic_flags & ~IEEE80211_CHAN_TURBO)) == NULL) {
/* should never happen ?? */
return 0;
}
}
/*
* If bss channel is valid and if the
* scan is to not pick any channel then select the
* bss channel, otherwise choose the best channel.
*/
if ((ic->ic_bsschan != IEEE80211_CHAN_ANYC) &&
(ss->ss_flags & IEEE80211_SCAN_NOPICK)) {
se.se_chan = ic->ic_bsschan;
} else {
se.se_chan = c;
}
ret = 1;
}
/*
* ic->ic_des_chan_after_init_scan is valid only during initial bootup scan
* Any Scan after the initial bootup scan, shall choose the best channel
* referring to as_chanmetric;
*/
if ((ic->ic_des_chan_after_init_scan) && (!ic->ic_ignore_init_scan_icac)) {
struct ieee80211_channel *ch = ieee80211_find_channel_by_ieee(ic, ic->ic_des_chan_after_init_scan);
ic->ic_chan_is_set = 0;
if (ic->ic_check_channel(ic, ch, 0, 1) && isclr(ic->ic_chan_pri_inactive, ch->ic_ieee)) {
se.se_chan = ch;
}
ic->ic_des_chan_after_init_scan = 0;
}
ic->ic_des_chan = se.se_chan;
as->as_action = ss->ss_ops->scan_default;
if (action)
as->as_action = action;
as->as_selbss = se;
/*
* Must defer action to avoid possible recursive call through 80211
* state machine, which would result in recursive locking.
*/
IEEE80211_SCHEDULE_TQUEUE(&as->as_actiontq);
return ret;
}
static int
ap_iterate(struct ieee80211_scan_state *ss,
ieee80211_scan_iter_func *f, void *arg)
{
struct ap_state *as = ss->ss_priv;
struct ieee80211vap *vap = ss->ss_vap;
struct ieee80211com *ic = vap->iv_ic;
struct ap_scan_entry *se;
int chan;
int res = 0;
int i;
int bh_disabled;
bh_disabled = lock_ap_list(as);
for (i = 0; i < ss->ss_last; i++) {
chan = ieee80211_chan2ieee(ic, ss->ss_chans[i]);
if (!is_channel_valid(chan))
continue;
TAILQ_FOREACH(se, &as->as_scan_list[chan].asl_head, ase_list) {
set_se_inuse(se);
res = (*f)(arg, &se->base);
reset_se_inuse(se);
if (res) {
unlock_ap_list(as, bh_disabled);
return res;
}
}
}
unlock_ap_list(as, bh_disabled);
return res;
}
static void
local_ap_age(struct ieee80211_scan_state *ss, struct ap_state *as, int age_out)
{
struct ap_scan_entry *se, *next;
int i;
int bh_disabled;
int freed = 0;
bh_disabled = lock_ap_list(as);
for (i = 0; i < IEEE80211_CHAN_MAX; i++) {
TAILQ_FOREACH_SAFE(se, &as->as_scan_list[i].asl_head, ase_list, next) {
if (se->se_notseen > as->as_age) {
TAILQ_REMOVE(&as->as_scan_list[i].asl_head, se, ase_list);
if (age_out && as->as_numbeacons[se->base.se_chan->ic_ieee])
as->as_numbeacons[se->base.se_chan->ic_ieee]--;
free_se_request(se);
freed = 1;
if (as->as_entry_num > 0)
as->as_entry_num--;
} else {
if (se->se_seen) {
se->se_seen = 0;
} else {
se->se_notseen++;
}
}
}
}
if (age_out && freed)
memset(as->as_obss_chanlayout, 0, sizeof(as->as_obss_chanlayout));
unlock_ap_list(as, bh_disabled);
if (age_out && freed)
ap_iterate(ss, (ieee80211_scan_iter_func *)ieee80211_scan_check_secondary_channel, ss);
}
static void
ap_age(struct ieee80211_scan_state *ss)
{
struct ap_state *as;
struct ap_state *as_bak;
as = (struct ap_state *)ss->ss_scs_priv;
as_bak = ss->ss_priv;
ss->ss_priv = as;
local_ap_age(ss, ss->ss_scs_priv, 1);
ss->ss_priv = as_bak;
local_ap_age(ss, ss->ss_priv, 0);
}
static void
ap_assoc_success(struct ieee80211_scan_state *ss,
const u_int8_t macaddr[IEEE80211_ADDR_LEN])
{
/* should not be called */
}
static void
ap_assoc_fail(struct ieee80211_scan_state *ss,
const u_int8_t macaddr[IEEE80211_ADDR_LEN], int reason)
{
/* should not be called */
}
/*
* Default action to execute when a scan entry is found for ap
* mode. Return 1 on success, 0 on failure
*/
static int
ap_default_action(struct ieee80211vap *vap,
const struct ieee80211_scan_entry *se)
{
struct ieee80211com *ic = vap->iv_ic;
if (ic->ic_bsschan != IEEE80211_CHAN_ANYC &&
ic->ic_bsschan != se->se_chan &&
vap->iv_state == IEEE80211_S_RUN) {
ieee80211_enter_csa(ic,
se->se_chan,
NULL,
IEEE80211_CSW_REASON_SCAN,
IEEE80211_DEFAULT_CHANCHANGE_TBTT_COUNT,
IEEE80211_CSA_MUST_STOP_TX,
IEEE80211_CSA_F_BEACON | IEEE80211_CSA_F_ACTION);
} else {
ieee80211_create_bss(vap, se->se_chan);
}
if (IEEE80211_IS_11NG_40(ic))
ieee80211_check_40_bw_allowed(vap);
return 1;
}
static void
action_tasklet(IEEE80211_TQUEUE_ARG data)
{
struct ieee80211_scan_state *ss = (struct ieee80211_scan_state *)data;
struct ap_state *as = (struct ap_state *)ss->ss_priv;
struct ieee80211vap *vap = ss->ss_vap;
(*ss->ss_ops->scan_default)(vap, &as->as_selbss);
}
/*
* Module glue.
*/
MODULE_AUTHOR("Errno Consulting, Sam Leffler");
MODULE_DESCRIPTION("802.11 wireless support: default ap scanner");
#ifdef MODULE_LICENSE
MODULE_LICENSE("Dual BSD/GPL");
#endif
static const struct ieee80211_scanner ap_default = {
.scan_name = "default",
.scan_attach = ap_attach,
.scan_detach = ap_detach,
.scan_start = ap_start,
.scan_restart = ap_restart,
.scan_cancel = ap_cancel,
.scan_end = ap_end,
.scan_flush = ap_flush,
.scan_pickchan = ap_pick_channel,
.scan_add = ap_add,
.scan_age = ap_age,
.scan_iterate = ap_iterate,
.scan_assoc_success = ap_assoc_success,
.scan_assoc_fail = ap_assoc_fail,
.scan_lock = ap_lock,
.scan_unlock = ap_unlock,
.scan_default = ap_default_action,
};
static int __init
init_scanner_ap(void)
{
mlme_stats_init();
ieee80211_scanner_register(IEEE80211_M_HOSTAP, &ap_default);
return 0;
}
module_init(init_scanner_ap);
static void __exit
exit_scanner_ap(void)
{
ieee80211_scanner_unregister_all(&ap_default);
mlme_stats_exit();
}
module_exit(exit_scanner_ap);