| Commit | Line | Data |
|---|---|---|
| 32176cfd RP |
1 | /*- |
| 2 | * Copyright (c) 2002-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_scan.c 195618 2009-07-11 15:02:45Z rpaulo $ | |
| 26 | * $DragonFly$ | |
| 27 | */ | |
| 28 | ||
| 29 | /* | |
| 30 | * IEEE 802.11 scanning support. | |
| 31 | */ | |
| 32 | #include "opt_wlan.h" | |
| 33 | ||
| 34 | #include <sys/param.h> | |
| 35 | #include <sys/systm.h> | |
| 36 | #include <sys/proc.h> | |
| 37 | #include <sys/kernel.h> | |
| 32176cfd | 38 | |
| ccb4138a | 39 | #include <sys/condvar.h> |
| 32176cfd RP |
40 | #include <sys/socket.h> |
| 41 | ||
| 42 | #include <net/if.h> | |
| 43 | #include <net/if_media.h> | |
| 44 | #include <net/ethernet.h> | |
| 45 | #include <net/route.h> | |
| 46 | ||
| 47 | #include <netproto/802_11/ieee80211_var.h> | |
| 48 | ||
| 49 | #include <net/bpf.h> | |
| 50 | ||
| 51 | struct scan_state { | |
| 52 | struct ieee80211_scan_state base; /* public state */ | |
| 53 | ||
| 54 | u_int ss_iflags; /* flags used internally */ | |
| 55 | #define ISCAN_MINDWELL 0x0001 /* min dwell time reached */ | |
| 56 | #define ISCAN_DISCARD 0x0002 /* discard rx'd frames */ | |
| 57 | #define ISCAN_CANCEL 0x0004 /* cancel current scan */ | |
| 58 | #define ISCAN_ABORT 0x0008 /* end the scan immediately */ | |
| 59 | unsigned long ss_chanmindwell; /* min dwell on curchan */ | |
| 60 | unsigned long ss_scanend; /* time scan must stop */ | |
| 61 | u_int ss_duration; /* duration for next scan */ | |
| 62 | struct task ss_scan_task; /* scan execution */ | |
| 63 | struct cv ss_scan_cv; /* scan signal */ | |
| 64 | struct callout ss_scan_timer; /* scan timer */ | |
| 65 | }; | |
| 66 | #define SCAN_PRIVATE(ss) ((struct scan_state *) ss) | |
| 67 | ||
| 68 | /* | |
| 69 | * Amount of time to go off-channel during a background | |
| 70 | * scan. This value should be large enough to catch most | |
| 71 | * ap's but short enough that we can return on-channel | |
| 72 | * before our listen interval expires. | |
| 73 | * | |
| 74 | * XXX tunable | |
| 75 | * XXX check against configured listen interval | |
| 76 | */ | |
| 77 | #define IEEE80211_SCAN_OFFCHANNEL msecs_to_ticks(150) | |
| 78 | ||
| 79 | /* | |
| 80 | * Roaming-related defaults. RSSI thresholds are as returned by the | |
| 81 | * driver (.5dBm). Transmit rate thresholds are IEEE rate codes (i.e | |
| 82 | * .5M units) or MCS. | |
| 83 | */ | |
| 84 | /* rssi thresholds */ | |
| 85 | #define ROAM_RSSI_11A_DEFAULT 14 /* 11a bss */ | |
| 86 | #define ROAM_RSSI_11B_DEFAULT 14 /* 11b bss */ | |
| 87 | #define ROAM_RSSI_11BONLY_DEFAULT 14 /* 11b-only bss */ | |
| 88 | /* transmit rate thresholds */ | |
| 89 | #define ROAM_RATE_11A_DEFAULT 2*12 /* 11a bss */ | |
| 90 | #define ROAM_RATE_11B_DEFAULT 2*5 /* 11b bss */ | |
| 91 | #define ROAM_RATE_11BONLY_DEFAULT 2*1 /* 11b-only bss */ | |
| 92 | #define ROAM_RATE_HALF_DEFAULT 2*6 /* half-width 11a/g bss */ | |
| 93 | #define ROAM_RATE_QUARTER_DEFAULT 2*3 /* quarter-width 11a/g bss */ | |
| 94 | #define ROAM_MCS_11N_DEFAULT (1 | IEEE80211_RATE_MCS) /* 11n bss */ | |
| 95 | ||
| 96 | static void scan_curchan(struct ieee80211_scan_state *, unsigned long); | |
| 97 | static void scan_mindwell(struct ieee80211_scan_state *); | |
| 98 | static void scan_signal(void *); | |
| 99 | static void scan_task(void *, int); | |
| 100 | ||
| 101 | MALLOC_DEFINE(M_80211_SCAN, "80211scan", "802.11 scan state"); | |
| 102 | ||
| 103 | void | |
| 104 | ieee80211_scan_attach(struct ieee80211com *ic) | |
| 105 | { | |
| 106 | struct scan_state *ss; | |
| 107 | ||
| 108 | ss = (struct scan_state *) kmalloc(sizeof(struct scan_state), | |
| fcaa651d | 109 | M_80211_SCAN, M_INTWAIT | M_ZERO); |
| 32176cfd RP |
110 | if (ss == NULL) { |
| 111 | ic->ic_scan = NULL; | |
| 112 | return; | |
| 113 | } | |
| 26c6f223 | 114 | wlan_cv_init(&ss->ss_scan_cv, "scan"); |
| 22603758 | 115 | callout_init(&ss->ss_scan_timer); |
| 32176cfd RP |
116 | TASK_INIT(&ss->ss_scan_task, 0, scan_task, ss); |
| 117 | ic->ic_scan = &ss->base; | |
| 118 | ss->base.ss_ic = ic; | |
| 119 | ||
| 120 | ic->ic_scan_curchan = scan_curchan; | |
| 121 | ic->ic_scan_mindwell = scan_mindwell; | |
| 122 | } | |
| 123 | ||
| 124 | void | |
| 125 | ieee80211_scan_detach(struct ieee80211com *ic) | |
| 126 | { | |
| 127 | struct ieee80211_scan_state *ss = ic->ic_scan; | |
| 128 | ||
| 129 | if (ss != NULL) { | |
| 32176cfd RP |
130 | SCAN_PRIVATE(ss)->ss_iflags |= ISCAN_ABORT; |
| 131 | scan_signal(ss); | |
| 32176cfd | 132 | ieee80211_draintask(ic, &SCAN_PRIVATE(ss)->ss_scan_task); |
| dfcf81fd | 133 | callout_stop(&SCAN_PRIVATE(ss)->ss_scan_timer); |
| 32176cfd RP |
134 | KASSERT((ic->ic_flags & IEEE80211_F_SCAN) == 0, |
| 135 | ("scan still running")); | |
| 136 | if (ss->ss_ops != NULL) { | |
| 137 | ss->ss_ops->scan_detach(ss); | |
| 138 | ss->ss_ops = NULL; | |
| 139 | } | |
| 140 | ic->ic_scan = NULL; | |
| 141 | kfree(SCAN_PRIVATE(ss), M_80211_SCAN); | |
| 142 | } | |
| 143 | } | |
| 144 | ||
| 145 | static const struct ieee80211_roamparam defroam[IEEE80211_MODE_MAX] = { | |
| 146 | [IEEE80211_MODE_11A] = { .rssi = ROAM_RSSI_11A_DEFAULT, | |
| 147 | .rate = ROAM_RATE_11A_DEFAULT }, | |
| 148 | [IEEE80211_MODE_11G] = { .rssi = ROAM_RSSI_11B_DEFAULT, | |
| 149 | .rate = ROAM_RATE_11B_DEFAULT }, | |
| 150 | [IEEE80211_MODE_11B] = { .rssi = ROAM_RSSI_11BONLY_DEFAULT, | |
| 151 | .rate = ROAM_RATE_11BONLY_DEFAULT }, | |
| 152 | [IEEE80211_MODE_TURBO_A]= { .rssi = ROAM_RSSI_11A_DEFAULT, | |
| 153 | .rate = ROAM_RATE_11A_DEFAULT }, | |
| 154 | [IEEE80211_MODE_TURBO_G]= { .rssi = ROAM_RSSI_11A_DEFAULT, | |
| 155 | .rate = ROAM_RATE_11A_DEFAULT }, | |
| 156 | [IEEE80211_MODE_STURBO_A]={ .rssi = ROAM_RSSI_11A_DEFAULT, | |
| 157 | .rate = ROAM_RATE_11A_DEFAULT }, | |
| 158 | [IEEE80211_MODE_HALF] = { .rssi = ROAM_RSSI_11A_DEFAULT, | |
| 159 | .rate = ROAM_RATE_HALF_DEFAULT }, | |
| 160 | [IEEE80211_MODE_QUARTER]= { .rssi = ROAM_RSSI_11A_DEFAULT, | |
| 161 | .rate = ROAM_RATE_QUARTER_DEFAULT }, | |
| 162 | [IEEE80211_MODE_11NA] = { .rssi = ROAM_RSSI_11A_DEFAULT, | |
| 163 | .rate = ROAM_MCS_11N_DEFAULT }, | |
| 164 | [IEEE80211_MODE_11NG] = { .rssi = ROAM_RSSI_11B_DEFAULT, | |
| 165 | .rate = ROAM_MCS_11N_DEFAULT }, | |
| 166 | }; | |
| 167 | ||
| 168 | void | |
| 169 | ieee80211_scan_vattach(struct ieee80211vap *vap) | |
| 170 | { | |
| 171 | vap->iv_bgscanidle = (IEEE80211_BGSCAN_IDLE_DEFAULT*1000)/hz; | |
| 172 | vap->iv_bgscanintvl = IEEE80211_BGSCAN_INTVAL_DEFAULT*hz; | |
| 173 | vap->iv_scanvalid = IEEE80211_SCAN_VALID_DEFAULT*hz; | |
| 174 | ||
| 175 | vap->iv_roaming = IEEE80211_ROAMING_AUTO; | |
| 176 | memcpy(vap->iv_roamparms, defroam, sizeof(defroam)); | |
| 177 | } | |
| 178 | ||
| 179 | void | |
| 180 | ieee80211_scan_vdetach(struct ieee80211vap *vap) | |
| 181 | { | |
| 182 | struct ieee80211com *ic = vap->iv_ic; | |
| 183 | struct ieee80211_scan_state *ss; | |
| 184 | ||
| 32176cfd RP |
185 | ss = ic->ic_scan; |
| 186 | if (ss != NULL && ss->ss_vap == vap) { | |
| 187 | if (ic->ic_flags & IEEE80211_F_SCAN) { | |
| 188 | SCAN_PRIVATE(ss)->ss_iflags |= ISCAN_ABORT; | |
| 189 | scan_signal(ss); | |
| 190 | } | |
| 191 | if (ss->ss_ops != NULL) { | |
| 192 | ss->ss_ops->scan_detach(ss); | |
| 193 | ss->ss_ops = NULL; | |
| 194 | } | |
| 195 | ss->ss_vap = NULL; | |
| 196 | } | |
| 32176cfd RP |
197 | } |
| 198 | ||
| 199 | /* | |
| 200 | * Simple-minded scanner module support. | |
| 201 | */ | |
| 202 | static const char *scan_modnames[IEEE80211_OPMODE_MAX] = { | |
| 203 | "wlan_scan_sta", /* IEEE80211_M_IBSS */ | |
| 204 | "wlan_scan_sta", /* IEEE80211_M_STA */ | |
| 205 | "wlan_scan_wds", /* IEEE80211_M_WDS */ | |
| 206 | "wlan_scan_sta", /* IEEE80211_M_AHDEMO */ | |
| 207 | "wlan_scan_ap", /* IEEE80211_M_HOSTAP */ | |
| 208 | "wlan_scan_monitor", /* IEEE80211_M_MONITOR */ | |
| 209 | "wlan_scan_sta", /* IEEE80211_M_MBSS */ | |
| 210 | }; | |
| 211 | static const struct ieee80211_scanner *scanners[IEEE80211_OPMODE_MAX]; | |
| 212 | ||
| 213 | const struct ieee80211_scanner * | |
| 214 | ieee80211_scanner_get(enum ieee80211_opmode mode) | |
| 215 | { | |
| 216 | if (mode >= IEEE80211_OPMODE_MAX) | |
| 217 | return NULL; | |
| 218 | if (scanners[mode] == NULL) | |
| 219 | ieee80211_load_module(scan_modnames[mode]); | |
| 220 | return scanners[mode]; | |
| 221 | } | |
| 222 | ||
| 223 | void | |
| 224 | ieee80211_scanner_register(enum ieee80211_opmode mode, | |
| 225 | const struct ieee80211_scanner *scan) | |
| 226 | { | |
| 227 | if (mode >= IEEE80211_OPMODE_MAX) | |
| 228 | return; | |
| 229 | scanners[mode] = scan; | |
| 230 | } | |
| 231 | ||
| 232 | void | |
| 233 | ieee80211_scanner_unregister(enum ieee80211_opmode mode, | |
| 234 | const struct ieee80211_scanner *scan) | |
| 235 | { | |
| 236 | if (mode >= IEEE80211_OPMODE_MAX) | |
| 237 | return; | |
| 238 | if (scanners[mode] == scan) | |
| 239 | scanners[mode] = NULL; | |
| 240 | } | |
| 241 | ||
| 242 | void | |
| 243 | ieee80211_scanner_unregister_all(const struct ieee80211_scanner *scan) | |
| 244 | { | |
| 245 | int m; | |
| 246 | ||
| 247 | for (m = 0; m < IEEE80211_OPMODE_MAX; m++) | |
| 248 | if (scanners[m] == scan) | |
| 249 | scanners[m] = NULL; | |
| 250 | } | |
| 251 | ||
| 252 | /* | |
| 253 | * Update common scanner state to reflect the current | |
| 254 | * operating mode. This is called when the state machine | |
| 255 | * is transitioned to RUN state w/o scanning--e.g. when | |
| 256 | * operating in monitor mode. The purpose of this is to | |
| 257 | * ensure later callbacks find ss_ops set to properly | |
| 258 | * reflect current operating mode. | |
| 259 | */ | |
| 260 | static void | |
| 261 | scan_update_locked(struct ieee80211vap *vap, | |
| 262 | const struct ieee80211_scanner *scan) | |
| 263 | { | |
| 264 | struct ieee80211com *ic = vap->iv_ic; | |
| 265 | struct ieee80211_scan_state *ss = ic->ic_scan; | |
| 266 | ||
| 32176cfd RP |
267 | #ifdef IEEE80211_DEBUG |
| 268 | if (ss->ss_vap != vap || ss->ss_ops != scan) { | |
| 269 | IEEE80211_DPRINTF(vap, IEEE80211_MSG_SCAN, | |
| 270 | "%s: current scanner is <%s:%s>, switch to <%s:%s>\n", | |
| 271 | __func__, | |
| 272 | ss->ss_vap != NULL ? | |
| 273 | ss->ss_vap->iv_ifp->if_xname : "none", | |
| 274 | ss->ss_vap != NULL ? | |
| 275 | ieee80211_opmode_name[ss->ss_vap->iv_opmode] : "none", | |
| 276 | vap->iv_ifp->if_xname, | |
| 277 | ieee80211_opmode_name[vap->iv_opmode]); | |
| 278 | } | |
| 279 | #endif | |
| 280 | ss->ss_vap = vap; | |
| 281 | if (ss->ss_ops != scan) { | |
| 282 | /* | |
| 283 | * Switch scanners; detach old, attach new. Special | |
| 284 | * case where a single scan module implements multiple | |
| 285 | * policies by using different scan ops but a common | |
| 286 | * core. We assume if the old and new attach methods | |
| 287 | * are identical then it's ok to just change ss_ops | |
| 288 | * and not flush the internal state of the module. | |
| 289 | */ | |
| 290 | if (scan == NULL || ss->ss_ops == NULL || | |
| 291 | ss->ss_ops->scan_attach != scan->scan_attach) { | |
| 292 | if (ss->ss_ops != NULL) | |
| 293 | ss->ss_ops->scan_detach(ss); | |
| 294 | if (scan != NULL && !scan->scan_attach(ss)) { | |
| 295 | /* XXX attach failure */ | |
| 296 | /* XXX stat+msg */ | |
| 297 | scan = NULL; | |
| 298 | } | |
| 299 | } | |
| 300 | ss->ss_ops = scan; | |
| 301 | } | |
| 302 | } | |
| 303 | ||
| 304 | static char | |
| 305 | channel_type(const struct ieee80211_channel *c) | |
| 306 | { | |
| 307 | if (IEEE80211_IS_CHAN_ST(c)) | |
| 308 | return 'S'; | |
| 309 | if (IEEE80211_IS_CHAN_108A(c)) | |
| 310 | return 'T'; | |
| 311 | if (IEEE80211_IS_CHAN_108G(c)) | |
| 312 | return 'G'; | |
| 313 | if (IEEE80211_IS_CHAN_HT(c)) | |
| 314 | return 'n'; | |
| 315 | if (IEEE80211_IS_CHAN_A(c)) | |
| 316 | return 'a'; | |
| 317 | if (IEEE80211_IS_CHAN_ANYG(c)) | |
| 318 | return 'g'; | |
| 319 | if (IEEE80211_IS_CHAN_B(c)) | |
| 320 | return 'b'; | |
| 321 | return 'f'; | |
| 322 | } | |
| 323 | ||
| 324 | void | |
| 325 | ieee80211_scan_dump_channels(const struct ieee80211_scan_state *ss) | |
| 326 | { | |
| 327 | struct ieee80211com *ic = ss->ss_ic; | |
| 328 | const char *sep; | |
| 329 | int i; | |
| 330 | ||
| 331 | sep = ""; | |
| 332 | for (i = ss->ss_next; i < ss->ss_last; i++) { | |
| 333 | const struct ieee80211_channel *c = ss->ss_chans[i]; | |
| 334 | ||
| 335 | kprintf("%s%u%c", sep, ieee80211_chan2ieee(ic, c), | |
| 336 | channel_type(c)); | |
| 337 | sep = ", "; | |
| 338 | } | |
| 339 | } | |
| 340 | ||
| 341 | #ifdef IEEE80211_DEBUG | |
| 342 | static void | |
| 343 | scan_dump(struct ieee80211_scan_state *ss) | |
| 344 | { | |
| 345 | struct ieee80211vap *vap = ss->ss_vap; | |
| 346 | ||
| 347 | if_printf(vap->iv_ifp, "scan set "); | |
| 348 | ieee80211_scan_dump_channels(ss); | |
| 349 | kprintf(" dwell min %lums max %lums\n", | |
| 350 | ticks_to_msecs(ss->ss_mindwell), ticks_to_msecs(ss->ss_maxdwell)); | |
| 351 | } | |
| 352 | #endif /* IEEE80211_DEBUG */ | |
| 353 | ||
| 354 | static void | |
| 355 | copy_ssid(struct ieee80211vap *vap, struct ieee80211_scan_state *ss, | |
| 356 | int nssid, const struct ieee80211_scan_ssid ssids[]) | |
| 357 | { | |
| 358 | if (nssid > IEEE80211_SCAN_MAX_SSID) { | |
| 359 | /* XXX printf */ | |
| 360 | IEEE80211_DPRINTF(vap, IEEE80211_MSG_SCAN, | |
| 361 | "%s: too many ssid %d, ignoring all of them\n", | |
| 362 | __func__, nssid); | |
| 363 | return; | |
| 364 | } | |
| 365 | memcpy(ss->ss_ssid, ssids, nssid * sizeof(ssids[0])); | |
| 366 | ss->ss_nssid = nssid; | |
| 367 | } | |
| 368 | ||
| 369 | /* | |
| 370 | * Start a scan unless one is already going. | |
| 371 | */ | |
| 372 | static int | |
| 373 | start_scan_locked(const struct ieee80211_scanner *scan, | |
| 374 | struct ieee80211vap *vap, int flags, u_int duration, | |
| 375 | u_int mindwell, u_int maxdwell, | |
| 376 | u_int nssid, const struct ieee80211_scan_ssid ssids[]) | |
| 377 | { | |
| 378 | struct ieee80211com *ic = vap->iv_ic; | |
| 379 | struct ieee80211_scan_state *ss = ic->ic_scan; | |
| 380 | ||
| 32176cfd RP |
381 | if (ic->ic_flags & IEEE80211_F_CSAPENDING) { |
| 382 | IEEE80211_DPRINTF(vap, IEEE80211_MSG_SCAN, | |
| 383 | "%s: scan inhibited by pending channel change\n", __func__); | |
| 384 | } else if ((ic->ic_flags & IEEE80211_F_SCAN) == 0) { | |
| 385 | IEEE80211_DPRINTF(vap, IEEE80211_MSG_SCAN, | |
| 386 | "%s: %s scan, duration %u mindwell %u maxdwell %u, desired mode %s, %s%s%s%s%s%s\n" | |
| 387 | , __func__ | |
| 388 | , flags & IEEE80211_SCAN_ACTIVE ? "active" : "passive" | |
| 389 | , duration, mindwell, maxdwell | |
| 390 | , ieee80211_phymode_name[vap->iv_des_mode] | |
| 391 | , flags & IEEE80211_SCAN_FLUSH ? "flush" : "append" | |
| 392 | , flags & IEEE80211_SCAN_NOPICK ? ", nopick" : "" | |
| 393 | , flags & IEEE80211_SCAN_NOJOIN ? ", nojoin" : "" | |
| 394 | , flags & IEEE80211_SCAN_NOBCAST ? ", nobcast" : "" | |
| 395 | , flags & IEEE80211_SCAN_PICK1ST ? ", pick1st" : "" | |
| 396 | , flags & IEEE80211_SCAN_ONCE ? ", once" : "" | |
| 397 | ); | |
| 398 | ||
| 399 | scan_update_locked(vap, scan); | |
| 400 | if (ss->ss_ops != NULL) { | |
| 401 | if ((flags & IEEE80211_SCAN_NOSSID) == 0) | |
| 402 | copy_ssid(vap, ss, nssid, ssids); | |
| 403 | ||
| 404 | /* NB: top 4 bits for internal use */ | |
| 405 | ss->ss_flags = flags & 0xfff; | |
| 406 | if (ss->ss_flags & IEEE80211_SCAN_ACTIVE) | |
| 407 | vap->iv_stats.is_scan_active++; | |
| 408 | else | |
| 409 | vap->iv_stats.is_scan_passive++; | |
| 410 | if (flags & IEEE80211_SCAN_FLUSH) | |
| 411 | ss->ss_ops->scan_flush(ss); | |
| 412 | ||
| 413 | /* NB: flush frames rx'd before 1st channel change */ | |
| 414 | SCAN_PRIVATE(ss)->ss_iflags |= ISCAN_DISCARD; | |
| 415 | SCAN_PRIVATE(ss)->ss_duration = duration; | |
| 416 | ss->ss_next = 0; | |
| 417 | ss->ss_mindwell = mindwell; | |
| 418 | ss->ss_maxdwell = maxdwell; | |
| 419 | /* NB: scan_start must be before the scan runtask */ | |
| 420 | ss->ss_ops->scan_start(ss, vap); | |
| 421 | #ifdef IEEE80211_DEBUG | |
| 422 | if (ieee80211_msg_scan(vap)) | |
| 423 | scan_dump(ss); | |
| 424 | #endif /* IEEE80211_DEBUG */ | |
| 425 | ic->ic_flags |= IEEE80211_F_SCAN; | |
| 426 | ieee80211_runtask(ic, &SCAN_PRIVATE(ss)->ss_scan_task); | |
| 427 | } | |
| 428 | } else { | |
| 429 | IEEE80211_DPRINTF(vap, IEEE80211_MSG_SCAN, | |
| 430 | "%s: %s scan already in progress\n", __func__, | |
| 431 | ss->ss_flags & IEEE80211_SCAN_ACTIVE ? "active" : "passive"); | |
| 432 | } | |
| 433 | return (ic->ic_flags & IEEE80211_F_SCAN); | |
| 434 | } | |
| 435 | ||
| 436 | /* | |
| 437 | * Start a scan unless one is already going. | |
| 438 | */ | |
| 439 | int | |
| 440 | ieee80211_start_scan(struct ieee80211vap *vap, int flags, | |
| 441 | u_int duration, u_int mindwell, u_int maxdwell, | |
| 442 | u_int nssid, const struct ieee80211_scan_ssid ssids[]) | |
| 443 | { | |
| 444 | struct ieee80211com *ic = vap->iv_ic; | |
| 445 | const struct ieee80211_scanner *scan; | |
| 446 | int result; | |
| 447 | ||
| e8361ca0 | 448 | ic = vap->iv_ic; |
| 32176cfd RP |
449 | scan = ieee80211_scanner_get(vap->iv_opmode); |
| 450 | if (scan == NULL) { | |
| 451 | IEEE80211_DPRINTF(vap, IEEE80211_MSG_SCAN, | |
| 452 | "%s: no scanner support for %s mode\n", | |
| 453 | __func__, ieee80211_opmode_name[vap->iv_opmode]); | |
| 454 | /* XXX stat */ | |
| 455 | return 0; | |
| 456 | } | |
| 457 | ||
| 32176cfd RP |
458 | result = start_scan_locked(scan, vap, flags, duration, |
| 459 | mindwell, maxdwell, nssid, ssids); | |
| 32176cfd RP |
460 | |
| 461 | return result; | |
| 462 | } | |
| 463 | ||
| 464 | /* | |
| 465 | * Check the scan cache for an ap/channel to use; if that | |
| 466 | * fails then kick off a new scan. | |
| 467 | */ | |
| 468 | int | |
| 469 | ieee80211_check_scan(struct ieee80211vap *vap, int flags, | |
| 470 | u_int duration, u_int mindwell, u_int maxdwell, | |
| 471 | u_int nssid, const struct ieee80211_scan_ssid ssids[]) | |
| 472 | { | |
| 473 | struct ieee80211com *ic = vap->iv_ic; | |
| 474 | struct ieee80211_scan_state *ss = ic->ic_scan; | |
| 475 | const struct ieee80211_scanner *scan; | |
| 476 | int result; | |
| 477 | ||
| 478 | scan = ieee80211_scanner_get(vap->iv_opmode); | |
| 479 | if (scan == NULL) { | |
| 480 | IEEE80211_DPRINTF(vap, IEEE80211_MSG_SCAN, | |
| 481 | "%s: no scanner support for %s mode\n", | |
| 482 | __func__, vap->iv_opmode); | |
| 483 | /* XXX stat */ | |
| 484 | return 0; | |
| 485 | } | |
| 486 | ||
| 487 | /* | |
| 488 | * Check if there's a list of scan candidates already. | |
| 489 | * XXX want more than the ap we're currently associated with | |
| 490 | */ | |
| 491 | ||
| 32176cfd RP |
492 | IEEE80211_DPRINTF(vap, IEEE80211_MSG_SCAN, |
| 493 | "%s: %s scan, %s%s%s%s%s\n" | |
| 494 | , __func__ | |
| 495 | , flags & IEEE80211_SCAN_ACTIVE ? "active" : "passive" | |
| 496 | , flags & IEEE80211_SCAN_FLUSH ? "flush" : "append" | |
| 497 | , flags & IEEE80211_SCAN_NOPICK ? ", nopick" : "" | |
| 498 | , flags & IEEE80211_SCAN_NOJOIN ? ", nojoin" : "" | |
| 499 | , flags & IEEE80211_SCAN_PICK1ST ? ", pick1st" : "" | |
| 500 | , flags & IEEE80211_SCAN_ONCE ? ", once" : "" | |
| 501 | ); | |
| 502 | ||
| 503 | if (ss->ss_ops != scan) { | |
| 504 | /* XXX re-use cache contents? e.g. adhoc<->sta */ | |
| 505 | flags |= IEEE80211_SCAN_FLUSH; | |
| 506 | } | |
| 507 | scan_update_locked(vap, scan); | |
| 508 | if (ss->ss_ops != NULL) { | |
| 509 | /* XXX verify ss_ops matches vap->iv_opmode */ | |
| 510 | if ((flags & IEEE80211_SCAN_NOSSID) == 0) { | |
| 511 | /* | |
| 512 | * Update the ssid list and mark flags so if | |
| 513 | * we call start_scan it doesn't duplicate work. | |
| 514 | */ | |
| 515 | copy_ssid(vap, ss, nssid, ssids); | |
| 516 | flags |= IEEE80211_SCAN_NOSSID; | |
| 517 | } | |
| 518 | if ((ic->ic_flags & IEEE80211_F_SCAN) == 0 && | |
| 519 | (flags & IEEE80211_SCAN_FLUSH) == 0 && | |
| 520 | time_before(ticks, ic->ic_lastscan + vap->iv_scanvalid)) { | |
| 521 | /* | |
| 522 | * We're not currently scanning and the cache is | |
| 523 | * deemed hot enough to consult. Lock out others | |
| 524 | * by marking IEEE80211_F_SCAN while we decide if | |
| 525 | * something is already in the scan cache we can | |
| 526 | * use. Also discard any frames that might come | |
| 527 | * in while temporarily marked as scanning. | |
| 528 | */ | |
| 529 | SCAN_PRIVATE(ss)->ss_iflags |= ISCAN_DISCARD; | |
| 530 | ic->ic_flags |= IEEE80211_F_SCAN; | |
| 531 | ||
| 532 | /* NB: need to use supplied flags in check */ | |
| 533 | ss->ss_flags = flags & 0xff; | |
| 534 | result = ss->ss_ops->scan_end(ss, vap); | |
| 535 | ||
| 536 | ic->ic_flags &= ~IEEE80211_F_SCAN; | |
| 537 | SCAN_PRIVATE(ss)->ss_iflags &= ~ISCAN_DISCARD; | |
| 538 | if (result) { | |
| 539 | ieee80211_notify_scan_done(vap); | |
| 32176cfd RP |
540 | return 1; |
| 541 | } | |
| 542 | } | |
| 543 | } | |
| 544 | result = start_scan_locked(scan, vap, flags, duration, | |
| 545 | mindwell, maxdwell, nssid, ssids); | |
| 32176cfd RP |
546 | |
| 547 | return result; | |
| 548 | } | |
| 549 | ||
| 550 | /* | |
| 551 | * Check the scan cache for an ap/channel to use; if that fails | |
| 552 | * then kick off a scan using the current settings. | |
| 553 | */ | |
| 554 | int | |
| 555 | ieee80211_check_scan_current(struct ieee80211vap *vap) | |
| 556 | { | |
| 557 | return ieee80211_check_scan(vap, | |
| 558 | IEEE80211_SCAN_ACTIVE, | |
| 559 | IEEE80211_SCAN_FOREVER, 0, 0, | |
| 560 | vap->iv_des_nssid, vap->iv_des_ssid); | |
| 561 | } | |
| 562 | ||
| 563 | /* | |
| 564 | * Restart a previous scan. If the previous scan completed | |
| 565 | * then we start again using the existing channel list. | |
| 566 | */ | |
| 567 | int | |
| 568 | ieee80211_bg_scan(struct ieee80211vap *vap, int flags) | |
| 569 | { | |
| 570 | struct ieee80211com *ic = vap->iv_ic; | |
| 571 | struct ieee80211_scan_state *ss = ic->ic_scan; | |
| 572 | const struct ieee80211_scanner *scan; | |
| 573 | ||
| 574 | scan = ieee80211_scanner_get(vap->iv_opmode); | |
| 575 | if (scan == NULL) { | |
| 576 | IEEE80211_DPRINTF(vap, IEEE80211_MSG_SCAN, | |
| 577 | "%s: no scanner support for %s mode\n", | |
| 578 | __func__, vap->iv_opmode); | |
| 579 | /* XXX stat */ | |
| 580 | return 0; | |
| 581 | } | |
| 582 | ||
| 32176cfd RP |
583 | if ((ic->ic_flags & IEEE80211_F_SCAN) == 0) { |
| 584 | u_int duration; | |
| 585 | /* | |
| 586 | * Go off-channel for a fixed interval that is large | |
| 587 | * enough to catch most ap's but short enough that | |
| 588 | * we can return on-channel before our listen interval | |
| 589 | * expires. | |
| 590 | */ | |
| 591 | duration = IEEE80211_SCAN_OFFCHANNEL; | |
| 592 | ||
| 593 | IEEE80211_DPRINTF(vap, IEEE80211_MSG_SCAN, | |
| 594 | "%s: %s scan, ticks %u duration %lu\n", __func__, | |
| 595 | ss->ss_flags & IEEE80211_SCAN_ACTIVE ? "active" : "passive", | |
| 596 | ticks, duration); | |
| 597 | ||
| 598 | scan_update_locked(vap, scan); | |
| 599 | if (ss->ss_ops != NULL) { | |
| 600 | ss->ss_vap = vap; | |
| 601 | /* | |
| 602 | * A background scan does not select a new sta; it | |
| 603 | * just refreshes the scan cache. Also, indicate | |
| 604 | * the scan logic should follow the beacon schedule: | |
| 605 | * we go off-channel and scan for a while, then | |
| 606 | * return to the bss channel to receive a beacon, | |
| 607 | * then go off-channel again. All during this time | |
| 608 | * we notify the ap we're in power save mode. When | |
| 609 | * the scan is complete we leave power save mode. | |
| 610 | * If any beacon indicates there are frames pending | |
| 611 | * for us then we drop out of power save mode | |
| 612 | * (and background scan) automatically by way of the | |
| 613 | * usual sta power save logic. | |
| 614 | */ | |
| 615 | ss->ss_flags |= IEEE80211_SCAN_NOPICK | |
| 616 | | IEEE80211_SCAN_BGSCAN | |
| 617 | | flags | |
| 618 | ; | |
| 619 | /* if previous scan completed, restart */ | |
| 620 | if (ss->ss_next >= ss->ss_last) { | |
| 621 | if (ss->ss_flags & IEEE80211_SCAN_ACTIVE) | |
| 622 | vap->iv_stats.is_scan_active++; | |
| 623 | else | |
| 624 | vap->iv_stats.is_scan_passive++; | |
| 625 | /* | |
| 626 | * NB: beware of the scan cache being flushed; | |
| 627 | * if the channel list is empty use the | |
| 628 | * scan_start method to populate it. | |
| 629 | */ | |
| 630 | ss->ss_next = 0; | |
| 631 | if (ss->ss_last != 0) | |
| 632 | ss->ss_ops->scan_restart(ss, vap); | |
| 633 | else { | |
| 634 | ss->ss_ops->scan_start(ss, vap); | |
| 635 | #ifdef IEEE80211_DEBUG | |
| 636 | if (ieee80211_msg_scan(vap)) | |
| 637 | scan_dump(ss); | |
| 638 | #endif /* IEEE80211_DEBUG */ | |
| 639 | } | |
| 640 | } | |
| 641 | /* NB: flush frames rx'd before 1st channel change */ | |
| 642 | SCAN_PRIVATE(ss)->ss_iflags |= ISCAN_DISCARD; | |
| 643 | SCAN_PRIVATE(ss)->ss_duration = duration; | |
| 644 | ss->ss_maxdwell = duration; | |
| 645 | ic->ic_flags |= IEEE80211_F_SCAN; | |
| 646 | ic->ic_flags_ext |= IEEE80211_FEXT_BGSCAN; | |
| 647 | ieee80211_runtask(ic, &SCAN_PRIVATE(ss)->ss_scan_task); | |
| 648 | } else { | |
| 649 | /* XXX msg+stat */ | |
| 650 | } | |
| 651 | } else { | |
| 652 | IEEE80211_DPRINTF(vap, IEEE80211_MSG_SCAN, | |
| 653 | "%s: %s scan already in progress\n", __func__, | |
| 654 | ss->ss_flags & IEEE80211_SCAN_ACTIVE ? "active" : "passive"); | |
| 655 | } | |
| 32176cfd RP |
656 | |
| 657 | /* NB: racey, does it matter? */ | |
| 658 | return (ic->ic_flags & IEEE80211_F_SCAN); | |
| 659 | } | |
| 660 | ||
| 661 | /* | |
| 662 | * Cancel any scan currently going on for the specified vap. | |
| 663 | */ | |
| 664 | void | |
| 665 | ieee80211_cancel_scan(struct ieee80211vap *vap) | |
| 666 | { | |
| 667 | struct ieee80211com *ic = vap->iv_ic; | |
| 668 | struct ieee80211_scan_state *ss = ic->ic_scan; | |
| 669 | ||
| 32176cfd RP |
670 | if ((ic->ic_flags & IEEE80211_F_SCAN) && |
| 671 | ss->ss_vap == vap && | |
| 672 | (SCAN_PRIVATE(ss)->ss_iflags & ISCAN_CANCEL) == 0) { | |
| 673 | IEEE80211_DPRINTF(vap, IEEE80211_MSG_SCAN, | |
| 674 | "%s: cancel %s scan\n", __func__, | |
| 675 | ss->ss_flags & IEEE80211_SCAN_ACTIVE ? | |
| 676 | "active" : "passive"); | |
| 677 | ||
| 678 | /* clear bg scan NOPICK and mark cancel request */ | |
| 679 | ss->ss_flags &= ~IEEE80211_SCAN_NOPICK; | |
| 680 | SCAN_PRIVATE(ss)->ss_iflags |= ISCAN_CANCEL; | |
| 681 | /* wake up the scan task */ | |
| 682 | scan_signal(ss); | |
| 683 | } | |
| 32176cfd RP |
684 | } |
| 685 | ||
| 686 | /* | |
| 687 | * Cancel any scan currently going on. | |
| 688 | */ | |
| 689 | void | |
| 690 | ieee80211_cancel_anyscan(struct ieee80211vap *vap) | |
| 691 | { | |
| 692 | struct ieee80211com *ic = vap->iv_ic; | |
| 693 | struct ieee80211_scan_state *ss = ic->ic_scan; | |
| 694 | ||
| 32176cfd RP |
695 | if ((ic->ic_flags & IEEE80211_F_SCAN) && |
| 696 | (SCAN_PRIVATE(ss)->ss_iflags & ISCAN_CANCEL) == 0) { | |
| 697 | IEEE80211_DPRINTF(vap, IEEE80211_MSG_SCAN, | |
| 698 | "%s: cancel %s scan\n", __func__, | |
| 699 | ss->ss_flags & IEEE80211_SCAN_ACTIVE ? | |
| 700 | "active" : "passive"); | |
| 701 | ||
| 702 | /* clear bg scan NOPICK and mark cancel request */ | |
| 703 | ss->ss_flags &= ~IEEE80211_SCAN_NOPICK; | |
| 704 | SCAN_PRIVATE(ss)->ss_iflags |= ISCAN_CANCEL; | |
| 705 | /* wake up the scan task */ | |
| 706 | scan_signal(ss); | |
| 707 | } | |
| 32176cfd RP |
708 | } |
| 709 | ||
| 710 | /* | |
| 711 | * Public access to scan_next for drivers that manage | |
| 712 | * scanning themselves (e.g. for firmware-based devices). | |
| 713 | */ | |
| 714 | void | |
| 715 | ieee80211_scan_next(struct ieee80211vap *vap) | |
| 716 | { | |
| 717 | struct ieee80211com *ic = vap->iv_ic; | |
| 718 | struct ieee80211_scan_state *ss = ic->ic_scan; | |
| 719 | ||
| 720 | /* wake up the scan task */ | |
| 32176cfd | 721 | scan_signal(ss); |
| 32176cfd RP |
722 | } |
| 723 | ||
| 724 | /* | |
| 725 | * Public access to scan_next for drivers that are not able to scan single | |
| 726 | * channels (e.g. for firmware-based devices). | |
| 727 | */ | |
| 728 | void | |
| 729 | ieee80211_scan_done(struct ieee80211vap *vap) | |
| 730 | { | |
| 731 | struct ieee80211com *ic = vap->iv_ic; | |
| 732 | struct ieee80211_scan_state *ss; | |
| 733 | ||
| 32176cfd RP |
734 | ss = ic->ic_scan; |
| 735 | ss->ss_next = ss->ss_last; /* all channels are complete */ | |
| 736 | scan_signal(ss); | |
| 32176cfd RP |
737 | } |
| 738 | ||
| 739 | /* | |
| 740 | * Probe the curent channel, if allowed, while scanning. | |
| 741 | * If the channel is not marked passive-only then send | |
| 742 | * a probe request immediately. Otherwise mark state and | |
| 743 | * listen for beacons on the channel; if we receive something | |
| 744 | * then we'll transmit a probe request. | |
| 745 | */ | |
| 746 | void | |
| 747 | ieee80211_probe_curchan(struct ieee80211vap *vap, int force) | |
| 748 | { | |
| 749 | struct ieee80211com *ic = vap->iv_ic; | |
| 750 | struct ieee80211_scan_state *ss = ic->ic_scan; | |
| 751 | struct ifnet *ifp = vap->iv_ifp; | |
| 752 | int i; | |
| 753 | ||
| 754 | if ((ic->ic_curchan->ic_flags & IEEE80211_CHAN_PASSIVE) && !force) { | |
| 755 | ic->ic_flags_ext |= IEEE80211_FEXT_PROBECHAN; | |
| 756 | return; | |
| 757 | } | |
| 758 | /* | |
| 759 | * Send directed probe requests followed by any | |
| 760 | * broadcast probe request. | |
| 761 | * XXX remove dependence on ic/vap->iv_bss | |
| 762 | */ | |
| 763 | for (i = 0; i < ss->ss_nssid; i++) | |
| 764 | ieee80211_send_probereq(vap->iv_bss, | |
| 765 | vap->iv_myaddr, ifp->if_broadcastaddr, | |
| 766 | ifp->if_broadcastaddr, | |
| 767 | ss->ss_ssid[i].ssid, ss->ss_ssid[i].len); | |
| 768 | if ((ss->ss_flags & IEEE80211_SCAN_NOBCAST) == 0) | |
| 769 | ieee80211_send_probereq(vap->iv_bss, | |
| 770 | vap->iv_myaddr, ifp->if_broadcastaddr, | |
| 771 | ifp->if_broadcastaddr, | |
| 772 | "", 0); | |
| 773 | } | |
| 774 | ||
| 775 | /* | |
| 776 | * Scan curchan. If this is an active scan and the channel | |
| 777 | * is not marked passive then send probe request frame(s). | |
| 778 | * Arrange for the channel change after maxdwell ticks. | |
| 779 | */ | |
| 780 | static void | |
| 781 | scan_curchan(struct ieee80211_scan_state *ss, unsigned long maxdwell) | |
| 782 | { | |
| 783 | struct ieee80211vap *vap = ss->ss_vap; | |
| 784 | ||
| 32176cfd RP |
785 | if (ss->ss_flags & IEEE80211_SCAN_ACTIVE) |
| 786 | ieee80211_probe_curchan(vap, 0); | |
| 787 | callout_reset(&SCAN_PRIVATE(ss)->ss_scan_timer, | |
| 788 | maxdwell, scan_signal, ss); | |
| 32176cfd RP |
789 | } |
| 790 | ||
| 791 | static void | |
| 792 | scan_signal(void *arg) | |
| 793 | { | |
| 794 | struct ieee80211_scan_state *ss = (struct ieee80211_scan_state *) arg; | |
| 795 | ||
| 26c6f223 | 796 | wlan_cv_signal(&SCAN_PRIVATE(ss)->ss_scan_cv, 0); |
| 32176cfd RP |
797 | } |
| 798 | ||
| 799 | /* | |
| 800 | * Handle mindwell requirements completed; initiate a channel | |
| 801 | * change to the next channel asap. | |
| 802 | */ | |
| 803 | static void | |
| 804 | scan_mindwell(struct ieee80211_scan_state *ss) | |
| 805 | { | |
| 806 | struct ieee80211com *ic = ss->ss_ic; | |
| 807 | ||
| e8361ca0 | 808 | ic = ss->ss_ic; |
| 32176cfd | 809 | scan_signal(ss); |
| 32176cfd RP |
810 | } |
| 811 | ||
| 812 | static void | |
| 813 | scan_task(void *arg, int pending) | |
| 814 | { | |
| 815 | #define ISCAN_REP (ISCAN_MINDWELL | ISCAN_DISCARD) | |
| 816 | struct ieee80211_scan_state *ss = (struct ieee80211_scan_state *) arg; | |
| 817 | struct ieee80211vap *vap = ss->ss_vap; | |
| 818 | struct ieee80211com *ic = ss->ss_ic; | |
| 819 | struct ieee80211_channel *chan; | |
| 820 | unsigned long maxdwell, scanend; | |
| 821 | int scandone = 0; | |
| 822 | ||
| 32176cfd RP |
823 | if (vap == NULL || (ic->ic_flags & IEEE80211_F_SCAN) == 0 || |
| 824 | (SCAN_PRIVATE(ss)->ss_iflags & ISCAN_ABORT)) { | |
| 825 | /* Cancelled before we started */ | |
| 826 | goto done; | |
| 827 | } | |
| 828 | ||
| 829 | if (ss->ss_next == ss->ss_last) { | |
| 830 | IEEE80211_DPRINTF(vap, IEEE80211_MSG_SCAN, | |
| 831 | "%s: no channels to scan\n", __func__); | |
| 832 | goto done; | |
| 833 | } | |
| 834 | ||
| 835 | if (vap->iv_opmode == IEEE80211_M_STA && | |
| 836 | vap->iv_state == IEEE80211_S_RUN) { | |
| 837 | if ((vap->iv_bss->ni_flags & IEEE80211_NODE_PWR_MGT) == 0) { | |
| 838 | /* Enable station power save mode */ | |
| 839 | ieee80211_sta_pwrsave(vap, 1); | |
| 840 | /* | |
| 841 | * Use an 1ms delay so the null data frame has a chance | |
| 26c6f223 | 842 | * to go out. Minimum one tick. |
| 32176cfd RP |
843 | * XXX Should use M_TXCB mechanism to eliminate this. |
| 844 | */ | |
| 26c6f223 MD |
845 | wlan_cv_timedwait(&SCAN_PRIVATE(ss)->ss_scan_cv, |
| 846 | hz / 1000 + 1); | |
| 32176cfd RP |
847 | if (SCAN_PRIVATE(ss)->ss_iflags & ISCAN_ABORT) |
| 848 | goto done; | |
| 849 | } | |
| 850 | } | |
| 851 | ||
| 852 | scanend = ticks + SCAN_PRIVATE(ss)->ss_duration; | |
| 32176cfd | 853 | ic->ic_scan_start(ic); /* notify driver */ |
| 32176cfd RP |
854 | |
| 855 | for (;;) { | |
| 856 | scandone = (ss->ss_next >= ss->ss_last) || | |
| 857 | (SCAN_PRIVATE(ss)->ss_iflags & ISCAN_CANCEL) != 0; | |
| 858 | if (scandone || (ss->ss_flags & IEEE80211_SCAN_GOTPICK) || | |
| 859 | (SCAN_PRIVATE(ss)->ss_iflags & ISCAN_ABORT) || | |
| 860 | time_after(ticks + ss->ss_mindwell, scanend)) | |
| 861 | break; | |
| 862 | ||
| 863 | chan = ss->ss_chans[ss->ss_next++]; | |
| 864 | ||
| 865 | /* | |
| 866 | * Watch for truncation due to the scan end time. | |
| 867 | */ | |
| 868 | if (time_after(ticks + ss->ss_maxdwell, scanend)) | |
| 869 | maxdwell = scanend - ticks; | |
| 870 | else | |
| 871 | maxdwell = ss->ss_maxdwell; | |
| 872 | ||
| 873 | IEEE80211_DPRINTF(vap, IEEE80211_MSG_SCAN, | |
| 874 | "%s: chan %3d%c -> %3d%c [%s, dwell min %lums max %lums]\n", | |
| 875 | __func__, | |
| 876 | ieee80211_chan2ieee(ic, ic->ic_curchan), | |
| 877 | channel_type(ic->ic_curchan), | |
| 878 | ieee80211_chan2ieee(ic, chan), channel_type(chan), | |
| 879 | (ss->ss_flags & IEEE80211_SCAN_ACTIVE) && | |
| 880 | (chan->ic_flags & IEEE80211_CHAN_PASSIVE) == 0 ? | |
| 881 | "active" : "passive", | |
| 882 | ticks_to_msecs(ss->ss_mindwell), ticks_to_msecs(maxdwell)); | |
| 883 | ||
| 884 | /* | |
| 885 | * Potentially change channel and phy mode. | |
| 886 | */ | |
| 887 | ic->ic_curchan = chan; | |
| 888 | ic->ic_rt = ieee80211_get_ratetable(chan); | |
| 32176cfd RP |
889 | /* |
| 890 | * Perform the channel change and scan unlocked so the driver | |
| 891 | * may sleep. Once set_channel returns the hardware has | |
| 892 | * completed the channel change. | |
| 893 | */ | |
| 894 | ic->ic_set_channel(ic); | |
| 895 | ieee80211_radiotap_chan_change(ic); | |
| 896 | ||
| 897 | /* | |
| 898 | * Scan curchan. Drivers for "intelligent hardware" | |
| 899 | * override ic_scan_curchan to tell the device to do | |
| 900 | * the work. Otherwise we manage the work outselves; | |
| 901 | * sending a probe request (as needed), and arming the | |
| 902 | * timeout to switch channels after maxdwell ticks. | |
| 903 | * | |
| 904 | * scan_curchan should only pause for the time required to | |
| 905 | * prepare/initiate the hardware for the scan (if at all), the | |
| 906 | * below condvar is used to sleep for the channels dwell time | |
| 907 | * and allows it to be signalled for abort. | |
| 908 | */ | |
| 909 | ic->ic_scan_curchan(ss, maxdwell); | |
| 32176cfd RP |
910 | |
| 911 | SCAN_PRIVATE(ss)->ss_chanmindwell = ticks + ss->ss_mindwell; | |
| 912 | /* clear mindwell lock and initial channel change flush */ | |
| 913 | SCAN_PRIVATE(ss)->ss_iflags &= ~ISCAN_REP; | |
| 914 | ||
| 915 | if ((SCAN_PRIVATE(ss)->ss_iflags & (ISCAN_CANCEL|ISCAN_ABORT))) | |
| 916 | continue; | |
| 917 | ||
| 918 | /* Wait to be signalled to scan the next channel */ | |
| 26c6f223 | 919 | wlan_cv_wait(&SCAN_PRIVATE(ss)->ss_scan_cv); |
| 32176cfd RP |
920 | } |
| 921 | if (SCAN_PRIVATE(ss)->ss_iflags & ISCAN_ABORT) | |
| 922 | goto done; | |
| 923 | ||
| 32176cfd | 924 | ic->ic_scan_end(ic); /* notify driver */ |
| 32176cfd RP |
925 | |
| 926 | /* | |
| 927 | * Record scan complete time. Note that we also do | |
| 928 | * this when canceled so any background scan will | |
| 929 | * not be restarted for a while. | |
| 930 | */ | |
| 931 | if (scandone) | |
| 932 | ic->ic_lastscan = ticks; | |
| 933 | /* return to the bss channel */ | |
| 934 | if (ic->ic_bsschan != IEEE80211_CHAN_ANYC && | |
| 935 | ic->ic_curchan != ic->ic_bsschan) { | |
| 936 | ieee80211_setupcurchan(ic, ic->ic_bsschan); | |
| 32176cfd RP |
937 | ic->ic_set_channel(ic); |
| 938 | ieee80211_radiotap_chan_change(ic); | |
| 32176cfd RP |
939 | } |
| 940 | /* clear internal flags and any indication of a pick */ | |
| 941 | SCAN_PRIVATE(ss)->ss_iflags &= ~ISCAN_REP; | |
| 942 | ss->ss_flags &= ~IEEE80211_SCAN_GOTPICK; | |
| 943 | ||
| 944 | /* | |
| 945 | * If not canceled and scan completed, do post-processing. | |
| 946 | * If the callback function returns 0, then it wants to | |
| 947 | * continue/restart scanning. Unfortunately we needed to | |
| 948 | * notify the driver to end the scan above to avoid having | |
| 949 | * rx frames alter the scan candidate list. | |
| 950 | */ | |
| 951 | if ((SCAN_PRIVATE(ss)->ss_iflags & ISCAN_CANCEL) == 0 && | |
| 952 | !ss->ss_ops->scan_end(ss, vap) && | |
| 953 | (ss->ss_flags & IEEE80211_SCAN_ONCE) == 0 && | |
| 954 | time_before(ticks + ss->ss_mindwell, scanend)) { | |
| 955 | IEEE80211_DPRINTF(vap, IEEE80211_MSG_SCAN, | |
| 956 | "%s: done, restart " | |
| 957 | "[ticks %u, dwell min %lu scanend %lu]\n", | |
| 958 | __func__, | |
| 959 | ticks, ss->ss_mindwell, scanend); | |
| 960 | ss->ss_next = 0; /* reset to begining */ | |
| 961 | if (ss->ss_flags & IEEE80211_SCAN_ACTIVE) | |
| 962 | vap->iv_stats.is_scan_active++; | |
| 963 | else | |
| 964 | vap->iv_stats.is_scan_passive++; | |
| 965 | ||
| 966 | ss->ss_ops->scan_restart(ss, vap); /* XXX? */ | |
| 967 | ieee80211_runtask(ic, &SCAN_PRIVATE(ss)->ss_scan_task); | |
| 32176cfd RP |
968 | return; |
| 969 | } | |
| 970 | ||
| 971 | /* past here, scandone is ``true'' if not in bg mode */ | |
| 972 | if ((ss->ss_flags & IEEE80211_SCAN_BGSCAN) == 0) | |
| 973 | scandone = 1; | |
| 974 | ||
| 975 | IEEE80211_DPRINTF(vap, IEEE80211_MSG_SCAN, | |
| 976 | "%s: %s, [ticks %u, dwell min %lu scanend %lu]\n", | |
| 977 | __func__, scandone ? "done" : "stopped", | |
| 978 | ticks, ss->ss_mindwell, scanend); | |
| 979 | ||
| 980 | /* | |
| 981 | * Clear the SCAN bit first in case frames are | |
| 982 | * pending on the station power save queue. If | |
| 983 | * we defer this then the dispatch of the frames | |
| 984 | * may generate a request to cancel scanning. | |
| 985 | */ | |
| 986 | done: | |
| 987 | ic->ic_flags &= ~IEEE80211_F_SCAN; | |
| 988 | /* | |
| 989 | * Drop out of power save mode when a scan has | |
| 990 | * completed. If this scan was prematurely terminated | |
| 991 | * because it is a background scan then don't notify | |
| 992 | * the ap; we'll either return to scanning after we | |
| 993 | * receive the beacon frame or we'll drop out of power | |
| 994 | * save mode because the beacon indicates we have frames | |
| 995 | * waiting for us. | |
| 996 | */ | |
| 997 | if (scandone) { | |
| 998 | ieee80211_sta_pwrsave(vap, 0); | |
| 999 | if (ss->ss_next >= ss->ss_last) { | |
| 1000 | ieee80211_notify_scan_done(vap); | |
| 1001 | ic->ic_flags_ext &= ~IEEE80211_FEXT_BGSCAN; | |
| 1002 | } | |
| 1003 | } | |
| 1004 | SCAN_PRIVATE(ss)->ss_iflags &= ~(ISCAN_CANCEL|ISCAN_ABORT); | |
| 1005 | ss->ss_flags &= ~(IEEE80211_SCAN_ONCE | IEEE80211_SCAN_PICK1ST); | |
| 32176cfd RP |
1006 | #undef ISCAN_REP |
| 1007 | } | |
| 1008 | ||
| 1009 | #ifdef IEEE80211_DEBUG | |
| 1010 | static void | |
| 1011 | dump_country(const uint8_t *ie) | |
| 1012 | { | |
| 1013 | const struct ieee80211_country_ie *cie = | |
| 1014 | (const struct ieee80211_country_ie *) ie; | |
| 1015 | int i, nbands, schan, nchan; | |
| 1016 | ||
| 1017 | if (cie->len < 3) { | |
| 1018 | kprintf(" <bogus country ie, len %d>", cie->len); | |
| 1019 | return; | |
| 1020 | } | |
| 1021 | kprintf(" country [%c%c%c", cie->cc[0], cie->cc[1], cie->cc[2]); | |
| 1022 | nbands = (cie->len - 3) / sizeof(cie->band[0]); | |
| 1023 | for (i = 0; i < nbands; i++) { | |
| 1024 | schan = cie->band[i].schan; | |
| 1025 | nchan = cie->band[i].nchan; | |
| 1026 | if (nchan != 1) | |
| 1027 | kprintf(" %u-%u,%u", schan, schan + nchan-1, | |
| 1028 | cie->band[i].maxtxpwr); | |
| 1029 | else | |
| 1030 | kprintf(" %u,%u", schan, cie->band[i].maxtxpwr); | |
| 1031 | } | |
| 1032 | kprintf("]"); | |
| 1033 | } | |
| 1034 | ||
| 1035 | static void | |
| 1036 | dump_probe_beacon(uint8_t subtype, int isnew, | |
| 1037 | const uint8_t mac[IEEE80211_ADDR_LEN], | |
| 1038 | const struct ieee80211_scanparams *sp, int rssi) | |
| 1039 | { | |
| 1040 | ||
| 6168f72e RP |
1041 | kprintf("[%6D] %s%s on chan %u (bss chan %u) ", |
| 1042 | mac, ":", isnew ? "new " : "", | |
| 32176cfd RP |
1043 | ieee80211_mgt_subtype_name[subtype >> IEEE80211_FC0_SUBTYPE_SHIFT], |
| 1044 | sp->chan, sp->bchan); | |
| 1045 | ieee80211_print_essid(sp->ssid + 2, sp->ssid[1]); | |
| 1046 | kprintf(" rssi %d\n", rssi); | |
| 1047 | ||
| 1048 | if (isnew) { | |
| 6168f72e RP |
1049 | kprintf("[%6D] caps 0x%x bintval %u erp 0x%x", |
| 1050 | mac, ":", sp->capinfo, sp->bintval, sp->erp); | |
| 32176cfd RP |
1051 | if (sp->country != NULL) |
| 1052 | dump_country(sp->country); | |
| 1053 | kprintf("\n"); | |
| 1054 | } | |
| 1055 | } | |
| 1056 | #endif /* IEEE80211_DEBUG */ | |
| 1057 | ||
| 1058 | /* | |
| 1059 | * Process a beacon or probe response frame. | |
| 1060 | */ | |
| 1061 | void | |
| 1062 | ieee80211_add_scan(struct ieee80211vap *vap, | |
| 1063 | const struct ieee80211_scanparams *sp, | |
| 1064 | const struct ieee80211_frame *wh, | |
| 1065 | int subtype, int rssi, int noise) | |
| 1066 | { | |
| 1067 | struct ieee80211com *ic = vap->iv_ic; | |
| 1068 | struct ieee80211_scan_state *ss = ic->ic_scan; | |
| 1069 | ||
| 1070 | /* XXX locking */ | |
| 1071 | /* | |
| 1072 | * Frames received during startup are discarded to avoid | |
| 1073 | * using scan state setup on the initial entry to the timer | |
| 1074 | * callback. This can occur because the device may enable | |
| 1075 | * rx prior to our doing the initial channel change in the | |
| 1076 | * timer routine. | |
| 1077 | */ | |
| 1078 | if (SCAN_PRIVATE(ss)->ss_iflags & ISCAN_DISCARD) | |
| 1079 | return; | |
| 1080 | #ifdef IEEE80211_DEBUG | |
| 1081 | if (ieee80211_msg_scan(vap) && (ic->ic_flags & IEEE80211_F_SCAN)) | |
| 1082 | dump_probe_beacon(subtype, 1, wh->i_addr2, sp, rssi); | |
| 1083 | #endif | |
| 1084 | if (ss->ss_ops != NULL && | |
| 1085 | ss->ss_ops->scan_add(ss, sp, wh, subtype, rssi, noise)) { | |
| 1086 | /* | |
| 1087 | * If we've reached the min dwell time terminate | |
| 1088 | * the timer so we'll switch to the next channel. | |
| 1089 | */ | |
| 1090 | if ((SCAN_PRIVATE(ss)->ss_iflags & ISCAN_MINDWELL) == 0 && | |
| 1091 | time_after_eq(ticks, SCAN_PRIVATE(ss)->ss_chanmindwell)) { | |
| 1092 | IEEE80211_DPRINTF(vap, IEEE80211_MSG_SCAN, | |
| 1093 | "%s: chan %3d%c min dwell met (%u > %lu)\n", | |
| 1094 | __func__, | |
| 1095 | ieee80211_chan2ieee(ic, ic->ic_curchan), | |
| 1096 | channel_type(ic->ic_curchan), | |
| 1097 | ticks, SCAN_PRIVATE(ss)->ss_chanmindwell); | |
| 1098 | SCAN_PRIVATE(ss)->ss_iflags |= ISCAN_MINDWELL; | |
| 1099 | /* | |
| 1100 | * NB: trigger at next clock tick or wait for the | |
| 1101 | * hardware. | |
| 1102 | */ | |
| 1103 | ic->ic_scan_mindwell(ss); | |
| 1104 | } | |
| 1105 | } | |
| 1106 | } | |
| 1107 | ||
| 1108 | /* | |
| 1109 | * Timeout/age scan cache entries; called from sta timeout | |
| 1110 | * timer (XXX should be self-contained). | |
| 1111 | */ | |
| 1112 | void | |
| 1113 | ieee80211_scan_timeout(struct ieee80211com *ic) | |
| 1114 | { | |
| 1115 | struct ieee80211_scan_state *ss = ic->ic_scan; | |
| 1116 | ||
| 1117 | if (ss->ss_ops != NULL) | |
| 1118 | ss->ss_ops->scan_age(ss); | |
| 1119 | } | |
| 1120 | ||
| 1121 | /* | |
| 1122 | * Mark a scan cache entry after a successful associate. | |
| 1123 | */ | |
| 1124 | void | |
| 1125 | ieee80211_scan_assoc_success(struct ieee80211vap *vap, const uint8_t mac[]) | |
| 1126 | { | |
| 1127 | struct ieee80211_scan_state *ss = vap->iv_ic->ic_scan; | |
| 1128 | ||
| 1129 | if (ss->ss_ops != NULL) { | |
| 1130 | IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_SCAN, | |
| 1131 | mac, "%s", __func__); | |
| 1132 | ss->ss_ops->scan_assoc_success(ss, mac); | |
| 1133 | } | |
| 1134 | } | |
| 1135 | ||
| 1136 | /* | |
| 1137 | * Demerit a scan cache entry after failing to associate. | |
| 1138 | */ | |
| 1139 | void | |
| 1140 | ieee80211_scan_assoc_fail(struct ieee80211vap *vap, | |
| 1141 | const uint8_t mac[], int reason) | |
| 1142 | { | |
| 1143 | struct ieee80211_scan_state *ss = vap->iv_ic->ic_scan; | |
| 1144 | ||
| 1145 | if (ss->ss_ops != NULL) { | |
| 1146 | IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_SCAN, mac, | |
| 1147 | "%s: reason %u", __func__, reason); | |
| 1148 | ss->ss_ops->scan_assoc_fail(ss, mac, reason); | |
| 1149 | } | |
| 1150 | } | |
| 1151 | ||
| 1152 | /* | |
| 1153 | * Iterate over the contents of the scan cache. | |
| 1154 | */ | |
| 1155 | void | |
| 1156 | ieee80211_scan_iterate(struct ieee80211vap *vap, | |
| 1157 | ieee80211_scan_iter_func *f, void *arg) | |
| 1158 | { | |
| 1159 | struct ieee80211_scan_state *ss = vap->iv_ic->ic_scan; | |
| 1160 | ||
| 1161 | if (ss->ss_ops != NULL) | |
| 1162 | ss->ss_ops->scan_iterate(ss, f, arg); | |
| 1163 | } | |
| 1164 | ||
| 1165 | /* | |
| 1166 | * Flush the contents of the scan cache. | |
| 1167 | */ | |
| 1168 | void | |
| 1169 | ieee80211_scan_flush(struct ieee80211vap *vap) | |
| 1170 | { | |
| 1171 | struct ieee80211_scan_state *ss = vap->iv_ic->ic_scan; | |
| 1172 | ||
| 1173 | if (ss->ss_ops != NULL && ss->ss_vap == vap) { | |
| 1174 | IEEE80211_DPRINTF(vap, IEEE80211_MSG_SCAN, "%s\n", __func__); | |
| 1175 | ss->ss_ops->scan_flush(ss); | |
| 1176 | } | |
| 1177 | } | |
| 1178 | ||
| 1179 | /* | |
| 1180 | * Check the scan cache for an ap/channel to use; if that | |
| 1181 | * fails then kick off a new scan. | |
| 1182 | */ | |
| 1183 | struct ieee80211_channel * | |
| 1184 | ieee80211_scan_pickchannel(struct ieee80211com *ic, int flags) | |
| 1185 | { | |
| 1186 | struct ieee80211_scan_state *ss = ic->ic_scan; | |
| 1187 | ||
| 32176cfd RP |
1188 | if (ss == NULL || ss->ss_ops == NULL || ss->ss_vap == NULL) { |
| 1189 | /* XXX printf? */ | |
| 1190 | return NULL; | |
| 1191 | } | |
| 1192 | if (ss->ss_ops->scan_pickchan == NULL) { | |
| 1193 | IEEE80211_DPRINTF(ss->ss_vap, IEEE80211_MSG_SCAN, | |
| 1194 | "%s: scan module does not support picking a channel, " | |
| 1195 | "opmode %s\n", __func__, ss->ss_vap->iv_opmode); | |
| 1196 | return NULL; | |
| 1197 | } | |
| 1198 | return ss->ss_ops->scan_pickchan(ss, flags); | |
| 1199 | } |