ieee80211_dfs.c revision 227293
1178354Ssam/*- 2178354Ssam * Copyright (c) 2007-2008 Sam Leffler, Errno Consulting 3178354Ssam * All rights reserved. 4178354Ssam * 5178354Ssam * Redistribution and use in source and binary forms, with or without 6178354Ssam * modification, are permitted provided that the following conditions 7178354Ssam * are met: 8178354Ssam * 1. Redistributions of source code must retain the above copyright 9178354Ssam * notice, this list of conditions and the following disclaimer. 10178354Ssam * 2. Redistributions in binary form must reproduce the above copyright 11178354Ssam * notice, this list of conditions and the following disclaimer in the 12178354Ssam * documentation and/or other materials provided with the distribution. 13178354Ssam * 14178354Ssam * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 15178354Ssam * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 16178354Ssam * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 17178354Ssam * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 18178354Ssam * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 19178354Ssam * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 20178354Ssam * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 21178354Ssam * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22178354Ssam * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 23178354Ssam * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24178354Ssam */ 25178354Ssam 26178354Ssam#include <sys/cdefs.h> 27178354Ssam#ifdef __FreeBSD__ 28178354Ssam__FBSDID("$FreeBSD: head/sys/net80211/ieee80211_dfs.c 227293 2011-11-07 06:44:47Z ed $"); 29178354Ssam#endif 30178354Ssam 31178354Ssam/* 32178354Ssam * IEEE 802.11 DFS/Radar support. 33178354Ssam */ 34178354Ssam#include "opt_inet.h" 35178354Ssam#include "opt_wlan.h" 36178354Ssam 37178354Ssam#include <sys/param.h> 38178354Ssam#include <sys/systm.h> 39178354Ssam#include <sys/mbuf.h> 40178354Ssam#include <sys/malloc.h> 41178354Ssam#include <sys/kernel.h> 42178354Ssam 43178354Ssam#include <sys/socket.h> 44178354Ssam#include <sys/sockio.h> 45178354Ssam#include <sys/endian.h> 46178354Ssam#include <sys/errno.h> 47178354Ssam#include <sys/proc.h> 48178354Ssam#include <sys/sysctl.h> 49178354Ssam 50178354Ssam#include <net/if.h> 51178354Ssam#include <net/if_media.h> 52178354Ssam 53178354Ssam#include <net80211/ieee80211_var.h> 54178354Ssam 55227293Sedstatic MALLOC_DEFINE(M_80211_DFS, "80211dfs", "802.11 DFS state"); 56178354Ssam 57193115Ssamstatic int ieee80211_nol_timeout = 30*60; /* 30 minutes */ 58193115SsamSYSCTL_INT(_net_wlan, OID_AUTO, nol_timeout, CTLFLAG_RW, 59193115Ssam &ieee80211_nol_timeout, 0, "NOL timeout (secs)"); 60178354Ssam#define NOL_TIMEOUT msecs_to_ticks(ieee80211_nol_timeout*1000) 61193115Ssam 62193115Ssamstatic int ieee80211_cac_timeout = 60; /* 60 seconds */ 63193115SsamSYSCTL_INT(_net_wlan, OID_AUTO, cac_timeout, CTLFLAG_RW, 64193115Ssam &ieee80211_cac_timeout, 0, "CAC timeout (secs)"); 65178354Ssam#define CAC_TIMEOUT msecs_to_ticks(ieee80211_cac_timeout*1000) 66178354Ssam 67178354Ssamvoid 68178354Ssamieee80211_dfs_attach(struct ieee80211com *ic) 69178354Ssam{ 70178354Ssam struct ieee80211_dfs_state *dfs = &ic->ic_dfs; 71178354Ssam 72193844Ssam callout_init_mtx(&dfs->nol_timer, IEEE80211_LOCK_OBJ(ic), 0); 73193844Ssam callout_init_mtx(&dfs->cac_timer, IEEE80211_LOCK_OBJ(ic), 0); 74178354Ssam} 75178354Ssam 76178354Ssamvoid 77178354Ssamieee80211_dfs_detach(struct ieee80211com *ic) 78178354Ssam{ 79178354Ssam /* NB: we assume no locking is needed */ 80178354Ssam ieee80211_dfs_reset(ic); 81178354Ssam} 82178354Ssam 83178354Ssamvoid 84178354Ssamieee80211_dfs_reset(struct ieee80211com *ic) 85178354Ssam{ 86178354Ssam struct ieee80211_dfs_state *dfs = &ic->ic_dfs; 87178354Ssam int i; 88178354Ssam 89178354Ssam /* NB: we assume no locking is needed */ 90178354Ssam /* NB: cac_timer should be cleared by the state machine */ 91178354Ssam callout_drain(&dfs->nol_timer); 92178354Ssam for (i = 0; i < ic->ic_nchans; i++) 93178354Ssam ic->ic_channels[i].ic_state = 0; 94178354Ssam dfs->lastchan = NULL; 95178354Ssam} 96178354Ssam 97178354Ssamstatic void 98178354Ssamcac_timeout(void *arg) 99178354Ssam{ 100178354Ssam struct ieee80211vap *vap = arg; 101178354Ssam struct ieee80211com *ic = vap->iv_ic; 102178354Ssam struct ieee80211_dfs_state *dfs = &ic->ic_dfs; 103178354Ssam int i; 104178354Ssam 105193844Ssam IEEE80211_LOCK_ASSERT(ic); 106193844Ssam 107178354Ssam if (vap->iv_state != IEEE80211_S_CAC) /* NB: just in case */ 108178354Ssam return; 109178354Ssam /* 110178354Ssam * When radar is detected during a CAC we are woken 111178354Ssam * up prematurely to switch to a new channel. 112178354Ssam * Check the channel to decide how to act. 113178354Ssam */ 114178354Ssam if (IEEE80211_IS_CHAN_RADAR(ic->ic_curchan)) { 115178354Ssam ieee80211_notify_cac(ic, ic->ic_curchan, 116178354Ssam IEEE80211_NOTIFY_CAC_RADAR); 117178354Ssam 118178354Ssam if_printf(vap->iv_ifp, 119178354Ssam "CAC timer on channel %u (%u MHz) stopped due to radar\n", 120178354Ssam ic->ic_curchan->ic_ieee, ic->ic_curchan->ic_freq); 121178354Ssam 122178354Ssam /* XXX clobbers any existing desired channel */ 123178354Ssam /* NB: dfs->newchan may be NULL, that's ok */ 124178354Ssam vap->iv_des_chan = dfs->newchan; 125193844Ssam /* XXX recursive lock need ieee80211_new_state_locked */ 126178354Ssam ieee80211_new_state(vap, IEEE80211_S_SCAN, 0); 127178354Ssam } else { 128178354Ssam if_printf(vap->iv_ifp, 129178354Ssam "CAC timer on channel %u (%u MHz) expired; " 130178354Ssam "no radar detected\n", 131178354Ssam ic->ic_curchan->ic_ieee, ic->ic_curchan->ic_freq); 132178354Ssam /* 133178354Ssam * Mark all channels with the current frequency 134178354Ssam * as having completed CAC; this keeps us from 135178354Ssam * doing it again until we change channels. 136178354Ssam */ 137178354Ssam for (i = 0; i < ic->ic_nchans; i++) { 138178354Ssam struct ieee80211_channel *c = &ic->ic_channels[i]; 139178354Ssam if (c->ic_freq == ic->ic_curchan->ic_freq) 140178354Ssam c->ic_state |= IEEE80211_CHANSTATE_CACDONE; 141178354Ssam } 142178354Ssam ieee80211_notify_cac(ic, ic->ic_curchan, 143178354Ssam IEEE80211_NOTIFY_CAC_EXPIRE); 144178354Ssam ieee80211_cac_completeswitch(vap); 145178354Ssam } 146178354Ssam} 147178354Ssam 148178354Ssam/* 149178354Ssam * Initiate the CAC timer. The driver is responsible 150178354Ssam * for setting up the hardware to scan for radar on the 151178354Ssam * channnel, we just handle timing things out. 152178354Ssam */ 153178354Ssamvoid 154178354Ssamieee80211_dfs_cac_start(struct ieee80211vap *vap) 155178354Ssam{ 156178354Ssam struct ieee80211com *ic = vap->iv_ic; 157178354Ssam struct ieee80211_dfs_state *dfs = &ic->ic_dfs; 158178354Ssam 159178354Ssam IEEE80211_LOCK_ASSERT(ic); 160178354Ssam 161178354Ssam callout_reset(&dfs->cac_timer, CAC_TIMEOUT, cac_timeout, vap); 162178354Ssam if_printf(vap->iv_ifp, "start %d second CAC timer on channel %u (%u MHz)\n", 163178354Ssam ticks_to_secs(CAC_TIMEOUT), 164178354Ssam ic->ic_curchan->ic_ieee, ic->ic_curchan->ic_freq); 165178354Ssam ieee80211_notify_cac(ic, ic->ic_curchan, IEEE80211_NOTIFY_CAC_START); 166178354Ssam} 167178354Ssam 168178354Ssam/* 169178354Ssam * Clear the CAC timer. 170178354Ssam */ 171178354Ssamvoid 172178354Ssamieee80211_dfs_cac_stop(struct ieee80211vap *vap) 173178354Ssam{ 174178354Ssam struct ieee80211com *ic = vap->iv_ic; 175178354Ssam struct ieee80211_dfs_state *dfs = &ic->ic_dfs; 176178354Ssam 177178354Ssam IEEE80211_LOCK_ASSERT(ic); 178178354Ssam 179178354Ssam /* NB: racey but not important */ 180178354Ssam if (callout_pending(&dfs->cac_timer)) { 181178354Ssam if_printf(vap->iv_ifp, "stop CAC timer on channel %u (%u MHz)\n", 182178354Ssam ic->ic_curchan->ic_ieee, ic->ic_curchan->ic_freq); 183178354Ssam ieee80211_notify_cac(ic, ic->ic_curchan, 184178354Ssam IEEE80211_NOTIFY_CAC_STOP); 185178354Ssam } 186178354Ssam callout_stop(&dfs->cac_timer); 187178354Ssam} 188178354Ssam 189178354Ssamvoid 190178354Ssamieee80211_dfs_cac_clear(struct ieee80211com *ic, 191178354Ssam const struct ieee80211_channel *chan) 192178354Ssam{ 193178354Ssam int i; 194178354Ssam 195178354Ssam for (i = 0; i < ic->ic_nchans; i++) { 196178354Ssam struct ieee80211_channel *c = &ic->ic_channels[i]; 197178354Ssam if (c->ic_freq == chan->ic_freq) 198178354Ssam c->ic_state &= ~IEEE80211_CHANSTATE_CACDONE; 199178354Ssam } 200178354Ssam} 201178354Ssam 202178354Ssamstatic void 203178354Ssamdfs_timeout(void *arg) 204178354Ssam{ 205178354Ssam struct ieee80211com *ic = arg; 206178354Ssam struct ieee80211_dfs_state *dfs = &ic->ic_dfs; 207178354Ssam struct ieee80211_channel *c; 208178354Ssam int i, oldest, now; 209178354Ssam 210193844Ssam IEEE80211_LOCK_ASSERT(ic); 211193844Ssam 212178354Ssam now = oldest = ticks; 213178354Ssam for (i = 0; i < ic->ic_nchans; i++) { 214178354Ssam c = &ic->ic_channels[i]; 215178354Ssam if (IEEE80211_IS_CHAN_RADAR(c)) { 216178354Ssam if (time_after_eq(now, dfs->nol_event[i]+NOL_TIMEOUT)) { 217178354Ssam c->ic_state &= ~IEEE80211_CHANSTATE_RADAR; 218178354Ssam if (c->ic_state & IEEE80211_CHANSTATE_NORADAR) { 219178354Ssam /* 220178354Ssam * NB: do this here so we get only one 221178354Ssam * msg instead of one for every channel 222178354Ssam * table entry. 223178354Ssam */ 224178354Ssam if_printf(ic->ic_ifp, "radar on channel" 225178354Ssam " %u (%u MHz) cleared after timeout\n", 226178354Ssam c->ic_ieee, c->ic_freq); 227178354Ssam /* notify user space */ 228178354Ssam c->ic_state &= 229178354Ssam ~IEEE80211_CHANSTATE_NORADAR; 230178354Ssam ieee80211_notify_radar(ic, c); 231178354Ssam } 232178354Ssam } else if (dfs->nol_event[i] < oldest) 233178354Ssam oldest = dfs->nol_event[i]; 234178354Ssam } 235178354Ssam } 236178354Ssam if (oldest != now) { 237178354Ssam /* arrange to process next channel up for a status change */ 238196785Ssam callout_schedule(&dfs->nol_timer, oldest + NOL_TIMEOUT - now); 239178354Ssam } 240178354Ssam} 241178354Ssam 242178354Ssamstatic void 243178354Ssamannounce_radar(struct ifnet *ifp, const struct ieee80211_channel *curchan, 244178354Ssam const struct ieee80211_channel *newchan) 245178354Ssam{ 246178354Ssam if (newchan == NULL) 247178354Ssam if_printf(ifp, "radar detected on channel %u (%u MHz)\n", 248178354Ssam curchan->ic_ieee, curchan->ic_freq); 249178354Ssam else 250178354Ssam if_printf(ifp, "radar detected on channel %u (%u MHz), " 251178354Ssam "moving to channel %u (%u MHz)\n", 252178354Ssam curchan->ic_ieee, curchan->ic_freq, 253178354Ssam newchan->ic_ieee, newchan->ic_freq); 254178354Ssam} 255178354Ssam 256178354Ssam/* 257178354Ssam * Handle a radar detection event on a channel. The channel is 258178354Ssam * added to the NOL list and we record the time of the event. 259178354Ssam * Entries are aged out after NOL_TIMEOUT. If radar was 260178354Ssam * detected while doing CAC we force a state/channel change. 261178354Ssam * Otherwise radar triggers a channel switch using the CSA 262178354Ssam * mechanism (when the channel is the bss channel). 263178354Ssam */ 264178354Ssamvoid 265178354Ssamieee80211_dfs_notify_radar(struct ieee80211com *ic, struct ieee80211_channel *chan) 266178354Ssam{ 267178354Ssam struct ieee80211_dfs_state *dfs = &ic->ic_dfs; 268178354Ssam int i, now; 269178354Ssam 270178354Ssam IEEE80211_LOCK_ASSERT(ic); 271178354Ssam 272178354Ssam /* 273178354Ssam * Mark all entries with this frequency. Notify user 274178354Ssam * space and arrange for notification when the radar 275178354Ssam * indication is cleared. Then kick the NOL processing 276178354Ssam * thread if not already running. 277178354Ssam */ 278178354Ssam now = ticks; 279178354Ssam for (i = 0; i < ic->ic_nchans; i++) { 280178354Ssam struct ieee80211_channel *c = &ic->ic_channels[i]; 281178354Ssam if (c->ic_freq == chan->ic_freq) { 282178354Ssam c->ic_state &= ~IEEE80211_CHANSTATE_CACDONE; 283178354Ssam c->ic_state |= IEEE80211_CHANSTATE_RADAR; 284178354Ssam dfs->nol_event[i] = now; 285178354Ssam } 286178354Ssam } 287178354Ssam ieee80211_notify_radar(ic, chan); 288178354Ssam chan->ic_state |= IEEE80211_CHANSTATE_NORADAR; 289178354Ssam if (!callout_pending(&dfs->nol_timer)) 290178354Ssam callout_reset(&dfs->nol_timer, NOL_TIMEOUT, dfs_timeout, ic); 291178354Ssam 292178354Ssam /* 293178354Ssam * If radar is detected on the bss channel while 294178354Ssam * doing CAC; force a state change by scheduling the 295178354Ssam * callout to be dispatched asap. Otherwise, if this 296178354Ssam * event is for the bss channel then we must quiet 297178354Ssam * traffic and schedule a channel switch. 298178354Ssam * 299178354Ssam * Note this allows us to receive notification about 300178354Ssam * channels other than the bss channel; not sure 301178354Ssam * that can/will happen but it's simple to support. 302178354Ssam */ 303178354Ssam if (chan == ic->ic_bsschan) { 304178354Ssam /* XXX need a way to defer to user app */ 305178354Ssam dfs->newchan = ieee80211_dfs_pickchannel(ic); 306178354Ssam 307178354Ssam announce_radar(ic->ic_ifp, chan, dfs->newchan); 308178354Ssam 309178354Ssam if (callout_pending(&dfs->cac_timer)) 310181193Ssam callout_schedule(&dfs->cac_timer, 0); 311178354Ssam else if (dfs->newchan != NULL) { 312178354Ssam /* XXX mode 1, switch count 2 */ 313178354Ssam /* XXX calculate switch count based on max 314178354Ssam switch time and beacon interval? */ 315178354Ssam ieee80211_csa_startswitch(ic, dfs->newchan, 1, 2); 316178354Ssam } else { 317178354Ssam /* 318178354Ssam * Spec says to stop all transmissions and 319178354Ssam * wait on the current channel for an entry 320178354Ssam * on the NOL to expire. 321178354Ssam */ 322178354Ssam /*XXX*/ 323223583Sadrian if_printf(ic->ic_ifp, "%s: No free channels; waiting for entry " 324223583Sadrian "on NOL to expire\n", __func__); 325178354Ssam } 326178354Ssam } else { 327178354Ssam /* 328178354Ssam * Issue rate-limited console msgs. 329178354Ssam */ 330178354Ssam if (dfs->lastchan != chan) { 331178354Ssam dfs->lastchan = chan; 332178354Ssam dfs->cureps = 0; 333178354Ssam announce_radar(ic->ic_ifp, chan, NULL); 334178354Ssam } else if (ppsratecheck(&dfs->lastevent, &dfs->cureps, 1)) { 335178354Ssam announce_radar(ic->ic_ifp, chan, NULL); 336178354Ssam } 337178354Ssam } 338178354Ssam} 339178354Ssam 340178354Ssamstruct ieee80211_channel * 341178354Ssamieee80211_dfs_pickchannel(struct ieee80211com *ic) 342178354Ssam{ 343178354Ssam struct ieee80211_channel *c; 344178354Ssam int i, flags; 345178354Ssam uint16_t v; 346178354Ssam 347178354Ssam /* 348178354Ssam * Consult the scan cache first. 349178354Ssam */ 350178354Ssam flags = ic->ic_curchan->ic_flags & IEEE80211_CHAN_ALL; 351178354Ssam /* 352178354Ssam * XXX if curchan is HT this will never find a channel 353178354Ssam * XXX 'cuz we scan only legacy channels 354178354Ssam */ 355178354Ssam c = ieee80211_scan_pickchannel(ic, flags); 356178354Ssam if (c != NULL) 357178354Ssam return c; 358178354Ssam /* 359178354Ssam * No channel found in scan cache; select a compatible 360178354Ssam * one at random (skipping channels where radar has 361178354Ssam * been detected). 362178354Ssam */ 363178354Ssam get_random_bytes(&v, sizeof(v)); 364178354Ssam v %= ic->ic_nchans; 365178354Ssam for (i = v; i < ic->ic_nchans; i++) { 366178354Ssam c = &ic->ic_channels[i]; 367178354Ssam if (!IEEE80211_IS_CHAN_RADAR(c) && 368178354Ssam (c->ic_flags & flags) == flags) 369178354Ssam return c; 370178354Ssam } 371178354Ssam for (i = 0; i < v; i++) { 372178354Ssam c = &ic->ic_channels[i]; 373178354Ssam if (!IEEE80211_IS_CHAN_RADAR(c) && 374178354Ssam (c->ic_flags & flags) == flags) 375178354Ssam return c; 376178354Ssam } 377178354Ssam if_printf(ic->ic_ifp, "HELP, no channel located to switch to!\n"); 378178354Ssam return NULL; 379178354Ssam} 380