| Commit | Line | Data |
|---|---|---|
| 32176cfd RP |
1 | /*- |
| 2 | * Copyright (c) 2007-2008 Sam Leffler, Errno Consulting | |
| 3 | * All rights reserved. | |
| 4 | * | |
| 5 | * Redistribution and use in source and binary forms, with or without | |
| 6 | * modification, are permitted provided that the following conditions | |
| 7 | * are met: | |
| 8 | * 1. Redistributions of source code must retain the above copyright | |
| 9 | * notice, this list of conditions and the following disclaimer. | |
| 10 | * 2. Redistributions in binary form must reproduce the above copyright | |
| 11 | * notice, this list of conditions and the following disclaimer in the | |
| 12 | * documentation and/or other materials provided with the distribution. | |
| 13 | * | |
| 14 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR | |
| 15 | * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES | |
| 16 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. | |
| 17 | * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, | |
| 18 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT | |
| 19 | * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 20 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 21 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 22 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF | |
| 23 | * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 24 | * | |
| 25 | * $FreeBSD: head/sys/net80211/ieee80211_dfs.c 196785 2009-09-03 16:29:02Z sam $ | |
| 26 | * $DragonFly$ | |
| 27 | */ | |
| 28 | ||
| 29 | /* | |
| 30 | * IEEE 802.11 DFS/Radar support. | |
| 31 | */ | |
| 32 | #include "opt_inet.h" | |
| 33 | #include "opt_wlan.h" | |
| 34 | ||
| 35 | #include <sys/param.h> | |
| 36 | #include <sys/systm.h> | |
| 37 | #include <sys/mbuf.h> | |
| 38 | #include <sys/malloc.h> | |
| 39 | #include <sys/kernel.h> | |
| 40 | ||
| 41 | #include <sys/socket.h> | |
| 42 | #include <sys/sockio.h> | |
| 43 | #include <sys/endian.h> | |
| 44 | #include <sys/errno.h> | |
| 45 | #include <sys/proc.h> | |
| 46 | #include <sys/sysctl.h> | |
| 47 | ||
| 48 | #include <net/if.h> | |
| 49 | #include <net/if_media.h> | |
| 50 | #include <net/route.h> | |
| 51 | ||
| 52 | #include <netproto/802_11/ieee80211_var.h> | |
| 53 | ||
| 54 | MALLOC_DEFINE(M_80211_DFS, "80211dfs", "802.11 DFS state"); | |
| 55 | ||
| 56 | static int ieee80211_nol_timeout = 30*60; /* 30 minutes */ | |
| 57 | SYSCTL_INT(_net_wlan, OID_AUTO, nol_timeout, CTLFLAG_RW, | |
| 58 | &ieee80211_nol_timeout, 0, "NOL timeout (secs)"); | |
| 59 | #define NOL_TIMEOUT msecs_to_ticks(ieee80211_nol_timeout*1000) | |
| 60 | ||
| 61 | static int ieee80211_cac_timeout = 60; /* 60 seconds */ | |
| 62 | SYSCTL_INT(_net_wlan, OID_AUTO, cac_timeout, CTLFLAG_RW, | |
| 63 | &ieee80211_cac_timeout, 0, "CAC timeout (secs)"); | |
| 64 | #define CAC_TIMEOUT msecs_to_ticks(ieee80211_cac_timeout*1000) | |
| 65 | ||
| 66 | void | |
| 67 | ieee80211_dfs_attach(struct ieee80211com *ic) | |
| 68 | { | |
| 69 | struct ieee80211_dfs_state *dfs = &ic->ic_dfs; | |
| 70 | ||
| 22603758 RP |
71 | callout_init(&dfs->nol_timer); |
| 72 | callout_init(&dfs->cac_timer); | |
| 32176cfd RP |
73 | } |
| 74 | ||
| 75 | void | |
| 76 | ieee80211_dfs_detach(struct ieee80211com *ic) | |
| 77 | { | |
| 78 | /* NB: we assume no locking is needed */ | |
| 79 | ieee80211_dfs_reset(ic); | |
| 80 | } | |
| 81 | ||
| 82 | void | |
| 83 | ieee80211_dfs_reset(struct ieee80211com *ic) | |
| 84 | { | |
| 85 | struct ieee80211_dfs_state *dfs = &ic->ic_dfs; | |
| 86 | int i; | |
| 87 | ||
| 88 | /* NB: we assume no locking is needed */ | |
| 89 | /* NB: cac_timer should be cleared by the state machine */ | |
| dfcf81fd | 90 | callout_stop(&dfs->nol_timer); |
| 32176cfd RP |
91 | for (i = 0; i < ic->ic_nchans; i++) |
| 92 | ic->ic_channels[i].ic_state = 0; | |
| 93 | dfs->lastchan = NULL; | |
| 94 | } | |
| 95 | ||
| 96 | static void | |
| 97 | cac_timeout(void *arg) | |
| 98 | { | |
| 99 | struct ieee80211vap *vap = arg; | |
| 100 | struct ieee80211com *ic = vap->iv_ic; | |
| 101 | struct ieee80211_dfs_state *dfs = &ic->ic_dfs; | |
| 102 | int i; | |
| 103 | ||
| 32176cfd RP |
104 | if (vap->iv_state != IEEE80211_S_CAC) /* NB: just in case */ |
| 105 | return; | |
| 106 | /* | |
| 107 | * When radar is detected during a CAC we are woken | |
| 108 | * up prematurely to switch to a new channel. | |
| 109 | * Check the channel to decide how to act. | |
| 110 | */ | |
| 111 | if (IEEE80211_IS_CHAN_RADAR(ic->ic_curchan)) { | |
| 112 | ieee80211_notify_cac(ic, ic->ic_curchan, | |
| 113 | IEEE80211_NOTIFY_CAC_RADAR); | |
| 114 | ||
| 115 | if_printf(vap->iv_ifp, | |
| 116 | "CAC timer on channel %u (%u MHz) stopped due to radar\n", | |
| 117 | ic->ic_curchan->ic_ieee, ic->ic_curchan->ic_freq); | |
| 118 | ||
| 119 | /* XXX clobbers any existing desired channel */ | |
| 120 | /* NB: dfs->newchan may be NULL, that's ok */ | |
| 121 | vap->iv_des_chan = dfs->newchan; | |
| 122 | /* XXX recursive lock need ieee80211_new_state_locked */ | |
| 123 | ieee80211_new_state(vap, IEEE80211_S_SCAN, 0); | |
| 124 | } else { | |
| 125 | if_printf(vap->iv_ifp, | |
| 126 | "CAC timer on channel %u (%u MHz) expired; " | |
| 127 | "no radar detected\n", | |
| 128 | ic->ic_curchan->ic_ieee, ic->ic_curchan->ic_freq); | |
| 129 | /* | |
| 130 | * Mark all channels with the current frequency | |
| 131 | * as having completed CAC; this keeps us from | |
| 132 | * doing it again until we change channels. | |
| 133 | */ | |
| 134 | for (i = 0; i < ic->ic_nchans; i++) { | |
| 135 | struct ieee80211_channel *c = &ic->ic_channels[i]; | |
| 136 | if (c->ic_freq == ic->ic_curchan->ic_freq) | |
| 137 | c->ic_state |= IEEE80211_CHANSTATE_CACDONE; | |
| 138 | } | |
| 139 | ieee80211_notify_cac(ic, ic->ic_curchan, | |
| 140 | IEEE80211_NOTIFY_CAC_EXPIRE); | |
| 141 | ieee80211_cac_completeswitch(vap); | |
| 142 | } | |
| 143 | } | |
| 144 | ||
| 145 | /* | |
| 146 | * Initiate the CAC timer. The driver is responsible | |
| 147 | * for setting up the hardware to scan for radar on the | |
| 148 | * channnel, we just handle timing things out. | |
| 149 | */ | |
| 150 | void | |
| 151 | ieee80211_dfs_cac_start(struct ieee80211vap *vap) | |
| 152 | { | |
| 153 | struct ieee80211com *ic = vap->iv_ic; | |
| 154 | struct ieee80211_dfs_state *dfs = &ic->ic_dfs; | |
| 155 | ||
| 32176cfd RP |
156 | callout_reset(&dfs->cac_timer, CAC_TIMEOUT, cac_timeout, vap); |
| 157 | if_printf(vap->iv_ifp, "start %d second CAC timer on channel %u (%u MHz)\n", | |
| 158 | ticks_to_secs(CAC_TIMEOUT), | |
| 159 | ic->ic_curchan->ic_ieee, ic->ic_curchan->ic_freq); | |
| 160 | ieee80211_notify_cac(ic, ic->ic_curchan, IEEE80211_NOTIFY_CAC_START); | |
| 161 | } | |
| 162 | ||
| 163 | /* | |
| 164 | * Clear the CAC timer. | |
| 165 | */ | |
| 166 | void | |
| 167 | ieee80211_dfs_cac_stop(struct ieee80211vap *vap) | |
| 168 | { | |
| 169 | struct ieee80211com *ic = vap->iv_ic; | |
| 170 | struct ieee80211_dfs_state *dfs = &ic->ic_dfs; | |
| 171 | ||
| 32176cfd RP |
172 | /* NB: racey but not important */ |
| 173 | if (callout_pending(&dfs->cac_timer)) { | |
| 174 | if_printf(vap->iv_ifp, "stop CAC timer on channel %u (%u MHz)\n", | |
| 175 | ic->ic_curchan->ic_ieee, ic->ic_curchan->ic_freq); | |
| 176 | ieee80211_notify_cac(ic, ic->ic_curchan, | |
| 177 | IEEE80211_NOTIFY_CAC_STOP); | |
| 178 | } | |
| 179 | callout_stop(&dfs->cac_timer); | |
| 180 | } | |
| 181 | ||
| 182 | void | |
| 183 | ieee80211_dfs_cac_clear(struct ieee80211com *ic, | |
| 184 | const struct ieee80211_channel *chan) | |
| 185 | { | |
| 186 | int i; | |
| 187 | ||
| 188 | for (i = 0; i < ic->ic_nchans; i++) { | |
| 189 | struct ieee80211_channel *c = &ic->ic_channels[i]; | |
| 190 | if (c->ic_freq == chan->ic_freq) | |
| 191 | c->ic_state &= ~IEEE80211_CHANSTATE_CACDONE; | |
| 192 | } | |
| 193 | } | |
| 194 | ||
| 195 | static void | |
| 196 | dfs_timeout(void *arg) | |
| 197 | { | |
| 198 | struct ieee80211com *ic = arg; | |
| 199 | struct ieee80211_dfs_state *dfs = &ic->ic_dfs; | |
| 200 | struct ieee80211_channel *c; | |
| 201 | int i, oldest, now; | |
| 202 | ||
| 32176cfd RP |
203 | now = oldest = ticks; |
| 204 | for (i = 0; i < ic->ic_nchans; i++) { | |
| 205 | c = &ic->ic_channels[i]; | |
| 206 | if (IEEE80211_IS_CHAN_RADAR(c)) { | |
| 207 | if (time_after_eq(now, dfs->nol_event[i]+NOL_TIMEOUT)) { | |
| 208 | c->ic_state &= ~IEEE80211_CHANSTATE_RADAR; | |
| 209 | if (c->ic_state & IEEE80211_CHANSTATE_NORADAR) { | |
| 210 | /* | |
| 211 | * NB: do this here so we get only one | |
| 212 | * msg instead of one for every channel | |
| 213 | * table entry. | |
| 214 | */ | |
| 215 | if_printf(ic->ic_ifp, "radar on channel" | |
| 216 | " %u (%u MHz) cleared after timeout\n", | |
| 217 | c->ic_ieee, c->ic_freq); | |
| 218 | /* notify user space */ | |
| 219 | c->ic_state &= | |
| 220 | ~IEEE80211_CHANSTATE_NORADAR; | |
| 221 | ieee80211_notify_radar(ic, c); | |
| 222 | } | |
| 223 | } else if (dfs->nol_event[i] < oldest) | |
| 224 | oldest = dfs->nol_event[i]; | |
| 225 | } | |
| 226 | } | |
| 227 | if (oldest != now) { | |
| 228 | /* arrange to process next channel up for a status change */ | |
| 22603758 RP |
229 | callout_reset(&dfs->nol_timer, oldest + NOL_TIMEOUT - now, |
| 230 | dfs_timeout, ic); | |
| 32176cfd RP |
231 | } |
| 232 | } | |
| 233 | ||
| 234 | static void | |
| 235 | announce_radar(struct ifnet *ifp, const struct ieee80211_channel *curchan, | |
| 236 | const struct ieee80211_channel *newchan) | |
| 237 | { | |
| 238 | if (newchan == NULL) | |
| 239 | if_printf(ifp, "radar detected on channel %u (%u MHz)\n", | |
| 240 | curchan->ic_ieee, curchan->ic_freq); | |
| 241 | else | |
| 242 | if_printf(ifp, "radar detected on channel %u (%u MHz), " | |
| 243 | "moving to channel %u (%u MHz)\n", | |
| 244 | curchan->ic_ieee, curchan->ic_freq, | |
| 245 | newchan->ic_ieee, newchan->ic_freq); | |
| 246 | } | |
| 247 | ||
| 248 | /* | |
| 249 | * Handle a radar detection event on a channel. The channel is | |
| 250 | * added to the NOL list and we record the time of the event. | |
| 251 | * Entries are aged out after NOL_TIMEOUT. If radar was | |
| 252 | * detected while doing CAC we force a state/channel change. | |
| 253 | * Otherwise radar triggers a channel switch using the CSA | |
| 254 | * mechanism (when the channel is the bss channel). | |
| 255 | */ | |
| 256 | void | |
| 257 | ieee80211_dfs_notify_radar(struct ieee80211com *ic, struct ieee80211_channel *chan) | |
| 258 | { | |
| 259 | struct ieee80211_dfs_state *dfs = &ic->ic_dfs; | |
| 260 | int i, now; | |
| 261 | ||
| 32176cfd RP |
262 | /* |
| 263 | * Mark all entries with this frequency. Notify user | |
| 264 | * space and arrange for notification when the radar | |
| 265 | * indication is cleared. Then kick the NOL processing | |
| 266 | * thread if not already running. | |
| 267 | */ | |
| 268 | now = ticks; | |
| 269 | for (i = 0; i < ic->ic_nchans; i++) { | |
| 270 | struct ieee80211_channel *c = &ic->ic_channels[i]; | |
| 271 | if (c->ic_freq == chan->ic_freq) { | |
| 272 | c->ic_state &= ~IEEE80211_CHANSTATE_CACDONE; | |
| 273 | c->ic_state |= IEEE80211_CHANSTATE_RADAR; | |
| 274 | dfs->nol_event[i] = now; | |
| 275 | } | |
| 276 | } | |
| 277 | ieee80211_notify_radar(ic, chan); | |
| 278 | chan->ic_state |= IEEE80211_CHANSTATE_NORADAR; | |
| 279 | if (!callout_pending(&dfs->nol_timer)) | |
| 280 | callout_reset(&dfs->nol_timer, NOL_TIMEOUT, dfs_timeout, ic); | |
| 281 | ||
| 282 | /* | |
| 283 | * If radar is detected on the bss channel while | |
| 284 | * doing CAC; force a state change by scheduling the | |
| 285 | * callout to be dispatched asap. Otherwise, if this | |
| 286 | * event is for the bss channel then we must quiet | |
| 287 | * traffic and schedule a channel switch. | |
| 288 | * | |
| 289 | * Note this allows us to receive notification about | |
| 290 | * channels other than the bss channel; not sure | |
| 291 | * that can/will happen but it's simple to support. | |
| 292 | */ | |
| 293 | if (chan == ic->ic_bsschan) { | |
| 294 | /* XXX need a way to defer to user app */ | |
| 295 | dfs->newchan = ieee80211_dfs_pickchannel(ic); | |
| 296 | ||
| 297 | announce_radar(ic->ic_ifp, chan, dfs->newchan); | |
| 298 | ||
| 22603758 | 299 | #ifdef notyet |
| 32176cfd | 300 | if (callout_pending(&dfs->cac_timer)) |
| 22603758 | 301 | callout_reset(&dfs->cac_timer, 0, cac_timeout, vap); |
| 32176cfd RP |
302 | else if (dfs->newchan != NULL) { |
| 303 | /* XXX mode 1, switch count 2 */ | |
| 304 | /* XXX calculate switch count based on max | |
| 305 | switch time and beacon interval? */ | |
| 306 | ieee80211_csa_startswitch(ic, dfs->newchan, 1, 2); | |
| 307 | } else { | |
| 308 | /* | |
| 309 | * Spec says to stop all transmissions and | |
| 310 | * wait on the current channel for an entry | |
| 311 | * on the NOL to expire. | |
| 312 | */ | |
| 313 | /*XXX*/ | |
| 314 | } | |
| 22603758 | 315 | #endif |
| 32176cfd RP |
316 | } else { |
| 317 | /* | |
| 318 | * Issue rate-limited console msgs. | |
| 319 | */ | |
| 320 | if (dfs->lastchan != chan) { | |
| 321 | dfs->lastchan = chan; | |
| 322 | dfs->cureps = 0; | |
| 323 | announce_radar(ic->ic_ifp, chan, NULL); | |
| 324 | } else if (ppsratecheck(&dfs->lastevent, &dfs->cureps, 1)) { | |
| 325 | announce_radar(ic->ic_ifp, chan, NULL); | |
| 326 | } | |
| 327 | } | |
| 328 | } | |
| 329 | ||
| 330 | struct ieee80211_channel * | |
| 331 | ieee80211_dfs_pickchannel(struct ieee80211com *ic) | |
| 332 | { | |
| 333 | struct ieee80211_channel *c; | |
| 334 | int i, flags; | |
| 335 | uint16_t v; | |
| 336 | ||
| 337 | /* | |
| 338 | * Consult the scan cache first. | |
| 339 | */ | |
| 340 | flags = ic->ic_curchan->ic_flags & IEEE80211_CHAN_ALL; | |
| 341 | /* | |
| 342 | * XXX if curchan is HT this will never find a channel | |
| 343 | * XXX 'cuz we scan only legacy channels | |
| 344 | */ | |
| 345 | c = ieee80211_scan_pickchannel(ic, flags); | |
| 346 | if (c != NULL) | |
| 347 | return c; | |
| 348 | /* | |
| 349 | * No channel found in scan cache; select a compatible | |
| 350 | * one at random (skipping channels where radar has | |
| 351 | * been detected). | |
| 352 | */ | |
| 353 | get_random_bytes(&v, sizeof(v)); | |
| 354 | v %= ic->ic_nchans; | |
| 355 | for (i = v; i < ic->ic_nchans; i++) { | |
| 356 | c = &ic->ic_channels[i]; | |
| 357 | if (!IEEE80211_IS_CHAN_RADAR(c) && | |
| 358 | (c->ic_flags & flags) == flags) | |
| 359 | return c; | |
| 360 | } | |
| 361 | for (i = 0; i < v; i++) { | |
| 362 | c = &ic->ic_channels[i]; | |
| 363 | if (!IEEE80211_IS_CHAN_RADAR(c) && | |
| 364 | (c->ic_flags & flags) == flags) | |
| 365 | return c; | |
| 366 | } | |
| 367 | if_printf(ic->ic_ifp, "HELP, no channel located to switch to!\n"); | |
| 368 | return NULL; | |
| 369 | } |