/*- * Copyright (c) 2002-2007 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. * * 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. */ #include __FBSDID("$FreeBSD$"); /* * IEEE 802.11 ap scanning support. */ #include #include #include #include #include #include #include #include #include #include struct ap_state { int as_maxrssi[IEEE80211_CHAN_MAX]; }; static int ap_flush(struct ieee80211_scan_state *); /* number of references from net80211 layer */ static int nrefs = 0; /* * Attach prior to any scanning work. */ static int ap_attach(struct ieee80211_scan_state *ss) { struct ap_state *as; MALLOC(as, struct ap_state *, sizeof(struct ap_state), M_80211_SCAN, M_NOWAIT); ss->ss_priv = as; ap_flush(ss); nrefs++; /* NB: we assume caller locking */ return 1; } /* * Cleanup any private state. */ static int ap_detach(struct ieee80211_scan_state *ss) { struct ap_state *as = ss->ss_priv; if (as != NULL) { KASSERT(nrefs > 0, ("imbalanced attach/detach")); nrefs--; /* NB: we assume caller locking */ FREE(as, M_80211_SCAN); } return 1; } /* * Flush all per-scan state. */ static int ap_flush(struct ieee80211_scan_state *ss) { struct ap_state *as = ss->ss_priv; memset(as->as_maxrssi, 0, sizeof(as->as_maxrssi)); ss->ss_last = 0; /* insure 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 ieee80211com *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 (IEEE80211_IS_CHAN_TURBO(c)) { #ifdef IEEE80211_F_XR /* XR is not supported on turbo channels */ if (ic->ic_flags & IEEE80211_F_XR) continue; #endif /* dynamic channels are scanned in base mode */ if (!IEEE80211_IS_CHAN_ST(c)) continue; } else if (IEEE80211_IS_CHAN_HT(c)) { /* HT channels are scanned in legacy */ continue; } else { /* * Use any 11g channel instead of 11b one. */ if (IEEE80211_IS_CHAN_B(c) && find11gchannel(ic, i, c->ic_freq)) continue; } if (ss->ss_last >= IEEE80211_SCAN_MAX) break; ss->ss_chans[ss->ss_last++] = c; } } else { static const u_int chanflags[IEEE80211_MODE_MAX] = { 0, /* IEEE80211_MODE_AUTO */ IEEE80211_CHAN_A, /* IEEE80211_MODE_11A */ IEEE80211_CHAN_B, /* IEEE80211_MODE_11B */ IEEE80211_CHAN_G, /* IEEE80211_MODE_11G */ IEEE80211_CHAN_FHSS, /* IEEE80211_MODE_FH */ IEEE80211_CHAN_108A, /* IEEE80211_MODE_TURBO_A */ IEEE80211_CHAN_108G, /* IEEE80211_MODE_TURBO_G */ IEEE80211_CHAN_ST, /* IEEE80211_MODE_STURBO_A */ IEEE80211_CHAN_A, /* IEEE80211_MODE_11NA */ IEEE80211_CHAN_G, /* IEEE80211_MODE_11NG */ }; u_int modeflags; modeflags = chanflags[ic->ic_des_mode]; if ((ic->ic_flags & IEEE80211_F_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->ic_flags & modeflags) != modeflags) continue; #ifdef IEEE80211_F_XR /* XR is not supported on turbo channels */ if (IEEE80211_IS_CHAN_TURBO(c) && (ic->ic_flags & IEEE80211_F_XR)) continue; #endif 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_STURBO_A) continue; ss->ss_chans[ss->ss_last++] = c; } } ss->ss_next = 0; /* XXX tunables */ ss->ss_mindwell = msecs_to_ticks(200); /* 200ms */ ss->ss_maxdwell = msecs_to_ticks(300); /* 300ms */ #ifdef IEEE80211_DEBUG if (ieee80211_msg_scan(ic)) { if_printf(ic->ic_ifp, "scan set "); 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 ieee80211com *ic) { return 0; } /* * Cancel an ongoing scan. */ static int ap_cancel(struct ieee80211_scan_state *ss, struct ieee80211com *ic) { return 0; } /* * Record max rssi on channel. */ static int ap_add(struct ieee80211_scan_state *ss, const struct ieee80211_scanparams *sp, const struct ieee80211_frame *wh, int subtype, int rssi, int noise, int rstamp) { struct ap_state *as = ss->ss_priv; struct ieee80211com *ic = ss->ss_ic; int chan; chan = ieee80211_chan2ieee(ic, ic->ic_curchan); /* XXX better quantification of channel use? */ /* XXX count bss's? */ if (rssi > as->as_maxrssi[chan]) as->as_maxrssi[chan] = rssi; /* XXX interference, turbo requirements */ return 1; } /* * Pick a quiet channel to use for ap operation. */ static int ap_end(struct ieee80211_scan_state *ss, struct ieee80211com *ic) { struct ap_state *as = ss->ss_priv; int i, chan, bestchan, bestchanix; KASSERT(ic->ic_opmode == IEEE80211_M_HOSTAP, ("wrong opmode %u", ic->ic_opmode)); /* XXX select channel more intelligently, e.g. channel spread, power */ bestchan = -1; bestchanix = 0; /* NB: silence compiler */ /* NB: use scan list order to preserve channel preference */ for (i = 0; i < ss->ss_last; i++) { /* * 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. */ /* XXX channel have interference? */ chan = ieee80211_chan2ieee(ic, ss->ss_chans[i]); IEEE80211_DPRINTF(ic, IEEE80211_MSG_SCAN, "%s: channel %u rssi %d bestchan %d bestchan rssi %d\n", __func__, chan, as->as_maxrssi[chan], bestchan, bestchan != -1 ? as->as_maxrssi[bestchan] : 0); if (as->as_maxrssi[chan] == 0) { bestchan = chan; bestchanix = i; /* XXX use other considerations */ break; } if (bestchan == -1 || as->as_maxrssi[chan] < as->as_maxrssi[bestchan]) bestchan = chan; } if (bestchan == -1) { /* no suitable channel, should not happen */ IEEE80211_DPRINTF(ic, IEEE80211_MSG_SCAN, "%s: no suitable channel! (should not happen)\n", __func__); /* XXX print something? */ 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 = ss->ss_chans[bestchanix]; if (IEEE80211_IS_CHAN_TURBO(c) && !IEEE80211_IS_CHAN_STURBO(c)) { c = ieee80211_find_channel(ic, c->ic_freq, c->ic_flags & ~IEEE80211_CHAN_TURBO); if (c == NULL) { /* should never happen ?? */ return 0; } } ieee80211_create_ibss(ic, ieee80211_ht_adjust_channel(ic, c, ic->ic_flags_ext)); return 1; } } static void ap_age(struct ieee80211_scan_state *ss) { /* XXX is there anything meaningful to do? */ } static void ap_iterate(struct ieee80211_scan_state *ss, ieee80211_scan_iter_func *f, void *arg) { /* NB: nothing meaningful we can do */ } static void ap_assoc_success(struct ieee80211_scan_state *ss, const uint8_t macaddr[IEEE80211_ADDR_LEN]) { /* should not be called */ } static void ap_assoc_fail(struct ieee80211_scan_state *ss, const uint8_t macaddr[IEEE80211_ADDR_LEN], int reason) { /* should not be called */ } 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_add = ap_add, .scan_age = ap_age, .scan_iterate = ap_iterate, .scan_assoc_success = ap_assoc_success, .scan_assoc_fail = ap_assoc_fail, }; /* * Module glue. */ static int wlan_modevent(module_t mod, int type, void *unused) { switch (type) { case MOD_LOAD: ieee80211_scanner_register(IEEE80211_M_HOSTAP, &ap_default); return 0; case MOD_UNLOAD: case MOD_QUIESCE: if (nrefs) { printf("wlan_scan_ap: still in use (%u dynamic refs)\n", nrefs); return EBUSY; } if (type == MOD_UNLOAD) ieee80211_scanner_unregister_all(&ap_default); return 0; } return EINVAL; } static moduledata_t wlan_mod = { "wlan_scan_ap", wlan_modevent, 0 }; DECLARE_MODULE(wlan_scan_ap, wlan_mod, SI_SUB_DRIVERS, SI_ORDER_FIRST); MODULE_VERSION(wlan_scan_ap, 1); MODULE_DEPEND(wlan_scan_ap, wlan, 1, 1, 1);