1 /* 2 * WPA Supplicant - background scan and roaming module: simple 3 * Copyright (c) 2009-2010, Jouni Malinen <j (at) w1.fi> 4 * 5 * This program is free software; you can redistribute it and/or modify 6 * it under the terms of the GNU General Public License version 2 as 7 * published by the Free Software Foundation. 8 * 9 * Alternatively, this software may be distributed under the terms of BSD 10 * license. 11 * 12 * See README and COPYING for more details. 13 */ 14 15 #include "includes.h" 16 17 #include "common.h" 18 #include "eloop.h" 19 #include "drivers/driver.h" 20 #include "config_ssid.h" 21 #include "wpa_supplicant_i.h" 22 #include "driver_i.h" 23 #include "scan.h" 24 #include "bgscan.h" 25 26 struct bgscan_simple_data { 27 struct wpa_supplicant *wpa_s; 28 const struct wpa_ssid *ssid; 29 int scan_interval; 30 int signal_threshold; 31 int short_scan_count; /* counter for scans using short scan interval */ 32 int short_interval; /* use if signal < threshold */ 33 int long_interval; /* use if signal > threshold */ 34 struct os_time last_bgscan; 35 }; 36 37 38 static void bgscan_simple_timeout(void *eloop_ctx, void *timeout_ctx) 39 { 40 struct bgscan_simple_data *data = eloop_ctx; 41 struct wpa_supplicant *wpa_s = data->wpa_s; 42 struct wpa_driver_scan_params params; 43 44 os_memset(¶ms, 0, sizeof(params)); 45 params.num_ssids = 1; 46 params.ssids[0].ssid = data->ssid->ssid; 47 params.ssids[0].ssid_len = data->ssid->ssid_len; 48 params.freqs = data->ssid->scan_freq; 49 50 /* 51 * A more advanced bgscan module would learn about most like channels 52 * over time and request scans only for some channels (probing others 53 * every now and then) to reduce effect on the data connection. 54 */ 55 56 wpa_printf(MSG_DEBUG, "bgscan simple: Request a background scan"); 57 if (wpa_supplicant_trigger_scan(wpa_s, ¶ms)) { 58 wpa_printf(MSG_DEBUG, "bgscan simple: Failed to trigger scan"); 59 eloop_register_timeout(data->scan_interval, 0, 60 bgscan_simple_timeout, data, NULL); 61 } else { 62 if (data->scan_interval == data->short_interval) { 63 data->short_scan_count++; 64 /* 65 * Spend at most the duration of a long scan interval 66 * scanning at the short scan interval. After that, 67 * revert to the long scan interval. 68 */ 69 if (data->short_scan_count > 70 data->long_interval / data->short_interval + 1) { 71 data->scan_interval = data->long_interval; 72 wpa_printf(MSG_DEBUG, "bgscan simple: Backing " 73 "off to long scan interval"); 74 } 75 } 76 os_get_time(&data->last_bgscan); 77 } 78 } 79 80 81 static int bgscan_simple_get_params(struct bgscan_simple_data *data, 82 const char *params) 83 { 84 const char *pos; 85 86 if (params == NULL) 87 return 0; 88 89 data->short_interval = atoi(params); 90 91 pos = os_strchr(params, ':'); 92 if (pos == NULL) 93 return 0; 94 pos++; 95 data->signal_threshold = atoi(pos); 96 pos = os_strchr(pos, ':'); 97 if (pos == NULL) { 98 wpa_printf(MSG_ERROR, "bgscan simple: Missing scan interval " 99 "for high signal"); 100 return -1; 101 } 102 pos++; 103 data->long_interval = atoi(pos); 104 105 return 0; 106 } 107 108 109 static void * bgscan_simple_init(struct wpa_supplicant *wpa_s, 110 const char *params, 111 const struct wpa_ssid *ssid) 112 { 113 struct bgscan_simple_data *data; 114 115 data = os_zalloc(sizeof(*data)); 116 if (data == NULL) 117 return NULL; 118 data->wpa_s = wpa_s; 119 data->ssid = ssid; 120 if (bgscan_simple_get_params(data, params) < 0) { 121 os_free(data); 122 return NULL; 123 } 124 if (data->short_interval <= 0) 125 data->short_interval = 30; 126 if (data->long_interval <= 0) 127 data->long_interval = 30; 128 129 wpa_printf(MSG_DEBUG, "bgscan simple: Signal strength threshold %d " 130 "Short bgscan interval %d Long bgscan interval %d", 131 data->signal_threshold, data->short_interval, 132 data->long_interval); 133 134 if (data->signal_threshold && 135 wpa_drv_signal_monitor(wpa_s, data->signal_threshold, 4) < 0) { 136 wpa_printf(MSG_ERROR, "bgscan simple: Failed to enable " 137 "signal strength monitoring"); 138 } 139 140 data->scan_interval = data->short_interval; 141 if (data->signal_threshold) { 142 /* Poll for signal info to set initial scan interval */ 143 struct wpa_signal_info siginfo; 144 if (wpa_drv_signal_poll(wpa_s, &siginfo) == 0 && 145 siginfo.current_signal >= data->signal_threshold) 146 data->scan_interval = data->long_interval; 147 } 148 wpa_printf(MSG_DEBUG, "bgscan simple: Init scan interval: %d", 149 data->scan_interval); 150 eloop_register_timeout(data->scan_interval, 0, bgscan_simple_timeout, 151 data, NULL); 152 153 /* 154 * This function is called immediately after an association, so it is 155 * reasonable to assume that a scan was completed recently. This makes 156 * us skip an immediate new scan in cases where the current signal 157 * level is below the bgscan threshold. 158 */ 159 os_get_time(&data->last_bgscan); 160 161 return data; 162 } 163 164 165 static void bgscan_simple_deinit(void *priv) 166 { 167 struct bgscan_simple_data *data = priv; 168 eloop_cancel_timeout(bgscan_simple_timeout, data, NULL); 169 if (data->signal_threshold) 170 wpa_drv_signal_monitor(data->wpa_s, 0, 0); 171 os_free(data); 172 } 173 174 175 static int bgscan_simple_notify_scan(void *priv, 176 struct wpa_scan_results *scan_res) 177 { 178 struct bgscan_simple_data *data = priv; 179 180 wpa_printf(MSG_DEBUG, "bgscan simple: scan result notification"); 181 182 eloop_cancel_timeout(bgscan_simple_timeout, data, NULL); 183 eloop_register_timeout(data->scan_interval, 0, bgscan_simple_timeout, 184 data, NULL); 185 186 /* 187 * A more advanced bgscan could process scan results internally, select 188 * the BSS and request roam if needed. This sample uses the existing 189 * BSS/ESS selection routine. Change this to return 1 if selection is 190 * done inside the bgscan module. 191 */ 192 193 return 0; 194 } 195 196 197 static void bgscan_simple_notify_beacon_loss(void *priv) 198 { 199 wpa_printf(MSG_DEBUG, "bgscan simple: beacon loss"); 200 /* TODO: speed up background scanning */ 201 } 202 203 204 static void bgscan_simple_notify_signal_change(void *priv, int above, 205 int current_signal, 206 int current_noise, 207 int current_txrate) 208 { 209 struct bgscan_simple_data *data = priv; 210 int scan = 0; 211 struct os_time now; 212 213 if (data->short_interval == data->long_interval || 214 data->signal_threshold == 0) 215 return; 216 217 wpa_printf(MSG_DEBUG, "bgscan simple: signal level changed " 218 "(above=%d current_signal=%d current_noise=%d " 219 "current_txrate=%d))", above, current_signal, 220 current_noise, current_txrate); 221 if (data->scan_interval == data->long_interval && !above) { 222 wpa_printf(MSG_DEBUG, "bgscan simple: Start using short " 223 "bgscan interval"); 224 data->scan_interval = data->short_interval; 225 data->short_scan_count = 0; 226 os_get_time(&now); 227 if (now.sec > data->last_bgscan.sec + 1) 228 scan = 1; 229 else if (data->last_bgscan.sec + data->long_interval > 230 now.sec + data->scan_interval) { 231 /* 232 * Restart scan interval timer if currently scheduled 233 * scan is too far in the future. 234 */ 235 eloop_cancel_timeout(bgscan_simple_timeout, data, 236 NULL); 237 eloop_register_timeout(data->scan_interval, 0, 238 bgscan_simple_timeout, data, 239 NULL); 240 } 241 } else if (data->scan_interval == data->short_interval && above) { 242 wpa_printf(MSG_DEBUG, "bgscan simple: Start using long bgscan " 243 "interval"); 244 data->scan_interval = data->long_interval; 245 eloop_cancel_timeout(bgscan_simple_timeout, data, NULL); 246 eloop_register_timeout(data->scan_interval, 0, 247 bgscan_simple_timeout, data, NULL); 248 } else if (!above) { 249 /* 250 * Signal dropped further 4 dB. Request a new scan if we have 251 * not yet scanned in a while. 252 */ 253 os_get_time(&now); 254 if (now.sec > data->last_bgscan.sec + 10) 255 scan = 1; 256 } 257 258 if (scan) { 259 wpa_printf(MSG_DEBUG, "bgscan simple: Trigger immediate scan"); 260 eloop_cancel_timeout(bgscan_simple_timeout, data, NULL); 261 eloop_register_timeout(0, 0, bgscan_simple_timeout, data, 262 NULL); 263 } 264 } 265 266 267 const struct bgscan_ops bgscan_simple_ops = { 268 .name = "simple", 269 .init = bgscan_simple_init, 270 .deinit = bgscan_simple_deinit, 271 .notify_scan = bgscan_simple_notify_scan, 272 .notify_beacon_loss = bgscan_simple_notify_beacon_loss, 273 .notify_signal_change = bgscan_simple_notify_signal_change, 274 }; 275