diff --git a/src/drivers/driver.h b/src/drivers/driver.h index 2c21fdb9f..d35309afb 100644 --- a/src/drivers/driver.h +++ b/src/drivers/driver.h @@ -213,6 +213,9 @@ struct hostapd_hw_modes { * @tsf: Timestamp * @age: Age of the information in milliseconds (i.e., how many milliseconds * ago the last Beacon or Probe Response frame was received) + * @est_throughput: Estimated throughput in kbps (this is calculated during + * scan result processing if left zero by the driver wrapper) + * @snr: Signal-to-noise ratio in dB (calculated during scan result processing) * @ie_len: length of the following IE field in octets * @beacon_ie_len: length of the following Beacon IE field in octets * @@ -241,6 +244,8 @@ struct wpa_scan_res { int level; u64 tsf; unsigned int age; + unsigned int est_throughput; + int snr; size_t ie_len; size_t beacon_ie_len; /* Followed by ie_len + beacon_ie_len octets of IE data */ diff --git a/wpa_supplicant/scan.c b/wpa_supplicant/scan.c index 10d6c0e2f..30a66572d 100644 --- a/wpa_supplicant/scan.c +++ b/wpa_supplicant/scan.c @@ -1605,8 +1605,8 @@ static int wpa_scan_result_compar(const void *a, const void *b) struct wpa_scan_res **_wb = (void *) b; struct wpa_scan_res *wa = *_wa; struct wpa_scan_res *wb = *_wb; - int wpa_a, wpa_b, maxrate_a, maxrate_b; - int snr_a, snr_b; + int wpa_a, wpa_b; + int snr_a, snr_b, snr_a_full, snr_b_full; /* WPA/WPA2 support preferred */ wpa_a = wpa_scan_get_vendor_ie(wa, WPA_IE_VENDOR_TYPE) != NULL || @@ -1628,22 +1628,22 @@ static int wpa_scan_result_compar(const void *a, const void *b) return -1; if (wa->flags & wb->flags & WPA_SCAN_LEVEL_DBM) { - snr_a = MIN(wa->level - wa->noise, GREAT_SNR); - snr_b = MIN(wb->level - wb->noise, GREAT_SNR); + snr_a_full = wa->snr; + snr_a = MIN(wa->snr, GREAT_SNR); + snr_b_full = wb->snr; + snr_b = MIN(wa->snr, GREAT_SNR); } else { /* Level is not in dBm, so we can't calculate * SNR. Just use raw level (units unknown). */ - snr_a = wa->level; - snr_b = wb->level; + snr_a = snr_a_full = wa->level; + snr_b = snr_b_full = wb->level; } /* if SNR is close, decide by max rate or frequency band */ if ((snr_a && snr_b && abs(snr_b - snr_a) < 5) || (wa->qual && wb->qual && abs(wb->qual - wa->qual) < 10)) { - maxrate_a = wpa_scan_get_max_rate(wa); - maxrate_b = wpa_scan_get_max_rate(wb); - if (maxrate_a != maxrate_b) - return maxrate_b - maxrate_a; + if (wa->est_throughput != wb->est_throughput) + return wb->est_throughput - wa->est_throughput; if (IS_5GHZ(wa->freq) ^ IS_5GHZ(wb->freq)) return IS_5GHZ(wa->freq) ? -1 : 1; } @@ -1651,9 +1651,9 @@ static int wpa_scan_result_compar(const void *a, const void *b) /* all things being equal, use SNR; if SNRs are * identical, use quality values since some drivers may only report * that value and leave the signal level zero */ - if (snr_b == snr_a) + if (snr_b_full == snr_a_full) return wb->qual - wa->qual; - return snr_b - snr_a; + return snr_b_full - snr_a_full; #undef MIN } @@ -1720,20 +1720,21 @@ static void dump_scan_res(struct wpa_scan_results *scan_res) struct wpa_scan_res *r = scan_res->res[i]; u8 *pos; if (r->flags & WPA_SCAN_LEVEL_DBM) { - int snr = r->level - r->noise; int noise_valid = !(r->flags & WPA_SCAN_NOISE_INVALID); wpa_printf(MSG_EXCESSIVE, MACSTR " freq=%d qual=%d " - "noise=%d%s level=%d snr=%d%s flags=0x%x age=%u", + "noise=%d%s level=%d snr=%d%s flags=0x%x age=%u est=%u", MAC2STR(r->bssid), r->freq, r->qual, r->noise, noise_valid ? "" : "~", r->level, - snr, snr >= GREAT_SNR ? "*" : "", r->flags, - r->age); + r->snr, r->snr >= GREAT_SNR ? "*" : "", + r->flags, + r->age, r->est_throughput); } else { wpa_printf(MSG_EXCESSIVE, MACSTR " freq=%d qual=%d " - "noise=%d level=%d flags=0x%x age=%u", + "noise=%d level=%d flags=0x%x age=%u est=%u", MAC2STR(r->bssid), r->freq, r->qual, - r->noise, r->level, r->flags, r->age); + r->noise, r->level, r->flags, r->age, + r->est_throughput); } pos = (u8 *) (r + 1); if (r->ie_len) @@ -1808,6 +1809,180 @@ static void filter_scan_res(struct wpa_supplicant *wpa_s, #define DEFAULT_NOISE_FLOOR_2GHZ (-89) #define DEFAULT_NOISE_FLOOR_5GHZ (-92) +static void scan_snr(struct wpa_scan_res *res) +{ + if (res->flags & WPA_SCAN_NOISE_INVALID) { + res->noise = IS_5GHZ(res->freq) ? + DEFAULT_NOISE_FLOOR_5GHZ : + DEFAULT_NOISE_FLOOR_2GHZ; + } + + if (res->flags & WPA_SCAN_LEVEL_DBM) { + res->snr = res->level - res->noise; + } else { + /* Level is not in dBm, so we can't calculate + * SNR. Just use raw level (units unknown). */ + res->snr = res->level; + } +} + + +static unsigned int max_ht20_rate(int snr) +{ + if (snr < 6) + return 6500; /* HT20 MCS0 */ + if (snr < 8) + return 13000; /* HT20 MCS1 */ + if (snr < 13) + return 19500; /* HT20 MCS2 */ + if (snr < 17) + return 26000; /* HT20 MCS3 */ + if (snr < 20) + return 39000; /* HT20 MCS4 */ + if (snr < 23) + return 52000; /* HT20 MCS5 */ + if (snr < 24) + return 58500; /* HT20 MCS6 */ + return 65000; /* HT20 MCS7 */ +} + + +static unsigned int max_ht40_rate(int snr) +{ + if (snr < 3) + return 13500; /* HT40 MCS0 */ + if (snr < 6) + return 27000; /* HT40 MCS1 */ + if (snr < 10) + return 40500; /* HT40 MCS2 */ + if (snr < 15) + return 54000; /* HT40 MCS3 */ + if (snr < 17) + return 81000; /* HT40 MCS4 */ + if (snr < 22) + return 108000; /* HT40 MCS5 */ + if (snr < 22) + return 121500; /* HT40 MCS6 */ + return 135000; /* HT40 MCS7 */ +} + + +static unsigned int max_vht80_rate(int snr) +{ + if (snr < 1) + return 0; + if (snr < 2) + return 29300; /* VHT80 MCS0 */ + if (snr < 5) + return 58500; /* VHT80 MCS1 */ + if (snr < 9) + return 87800; /* VHT80 MCS2 */ + if (snr < 11) + return 117000; /* VHT80 MCS3 */ + if (snr < 15) + return 175500; /* VHT80 MCS4 */ + if (snr < 16) + return 234000; /* VHT80 MCS5 */ + if (snr < 18) + return 263300; /* VHT80 MCS6 */ + if (snr < 20) + return 292500; /* VHT80 MCS7 */ + if (snr < 22) + return 351000; /* VHT80 MCS8 */ + return 390000; /* VHT80 MCS9 */ +} + + +static void scan_est_throughput(struct wpa_supplicant *wpa_s, + struct wpa_scan_res *res) +{ + enum local_hw_capab capab = wpa_s->hw_capab; + int rate; /* max legacy rate in 500 kb/s units */ + const u8 *ie; + unsigned int est, tmp; + int snr = res->snr; + + if (res->est_throughput) + return; + + /* Get maximum legacy rate */ + rate = wpa_scan_get_max_rate(res); + + /* Limit based on estimated SNR */ + if (rate > 1 * 2 && snr < 1) + rate = 1 * 2; + else if (rate > 2 * 2 && snr < 4) + rate = 2 * 2; + else if (rate > 6 * 2 && snr < 5) + rate = 6 * 2; + else if (rate > 9 * 2 && snr < 6) + rate = 9 * 2; + else if (rate > 12 * 2 && snr < 7) + rate = 12 * 2; + else if (rate > 18 * 2 && snr < 10) + rate = 18 * 2; + else if (rate > 24 * 2 && snr < 11) + rate = 24 * 2; + else if (rate > 36 * 2 && snr < 15) + rate = 36 * 2; + else if (rate > 48 * 2 && snr < 19) + rate = 48 * 2; + else if (rate > 54 * 2 && snr < 21) + rate = 54 * 2; + est = rate * 500; + + if (capab == CAPAB_HT || capab == CAPAB_HT40 || capab == CAPAB_VHT) { + ie = wpa_scan_get_ie(res, WLAN_EID_HT_CAP); + if (ie) { + tmp = max_ht20_rate(snr); + if (tmp > est) + est = tmp; + } + } + + if (capab == CAPAB_HT40 || capab == CAPAB_VHT) { + ie = wpa_scan_get_ie(res, WLAN_EID_HT_OPERATION); + if (ie && ie[1] >= 2 && + (ie[3] & HT_INFO_HT_PARAM_SECONDARY_CHNL_OFF_MASK)) { + tmp = max_ht40_rate(snr); + if (tmp > est) + est = tmp; + } + } + + if (capab == CAPAB_VHT) { + /* Use +1 to assume VHT is always faster than HT */ + ie = wpa_scan_get_ie(res, WLAN_EID_VHT_CAP); + if (ie) { + tmp = max_ht20_rate(snr) + 1; + if (tmp > est) + est = tmp; + + ie = wpa_scan_get_ie(res, WLAN_EID_HT_OPERATION); + if (ie && ie[1] >= 2 && + (ie[3] & + HT_INFO_HT_PARAM_SECONDARY_CHNL_OFF_MASK)) { + tmp = max_ht40_rate(snr) + 1; + if (tmp > est) + est = tmp; + } + + ie = wpa_scan_get_ie(res, WLAN_EID_VHT_OPERATION); + if (ie && ie[1] >= 1 && + (ie[2] & VHT_OPMODE_CHANNEL_WIDTH_MASK)) { + tmp = max_vht80_rate(snr) + 1; + if (tmp > est) + est = tmp; + } + } + } + + /* TODO: channel utilization and AP load (e.g., from AP Beacon) */ + + res->est_throughput = est; +} + + /** * wpa_supplicant_get_scan_results - Get scan results * @wpa_s: Pointer to wpa_supplicant data @@ -1844,12 +2019,8 @@ wpa_supplicant_get_scan_results(struct wpa_supplicant *wpa_s, for (i = 0; i < scan_res->num; i++) { struct wpa_scan_res *scan_res_item = scan_res->res[i]; - if (scan_res_item->flags & WPA_SCAN_NOISE_INVALID) { - scan_res_item->noise = - IS_5GHZ(scan_res_item->freq) ? - DEFAULT_NOISE_FLOOR_5GHZ : - DEFAULT_NOISE_FLOOR_2GHZ; - } + scan_snr(scan_res_item); + scan_est_throughput(wpa_s, scan_res_item); } #ifdef CONFIG_WPS diff --git a/wpa_supplicant/wpa_supplicant.c b/wpa_supplicant/wpa_supplicant.c index 29d1aaff4..05b785ea3 100644 --- a/wpa_supplicant/wpa_supplicant.c +++ b/wpa_supplicant/wpa_supplicant.c @@ -4071,6 +4071,23 @@ static int wpa_supplicant_init_iface(struct wpa_supplicant *wpa_s, wpa_s->hw.modes = wpa_drv_get_hw_feature_data(wpa_s, &wpa_s->hw.num_modes, &wpa_s->hw.flags); + if (wpa_s->hw.modes) { + u16 i; + + for (i = 0; i < wpa_s->hw.num_modes; i++) { + if (wpa_s->hw.modes[i].vht_capab) { + wpa_s->hw_capab = CAPAB_VHT; + break; + } + + if (wpa_s->hw.modes[i].ht_capab & + HT_CAP_INFO_SUPP_CHANNEL_WIDTH_SET) + wpa_s->hw_capab = CAPAB_HT40; + else if (wpa_s->hw.modes[i].ht_capab && + wpa_s->hw_capab == CAPAB_NO_HT_VHT) + wpa_s->hw_capab = CAPAB_HT; + } + } capa_res = wpa_drv_get_capa(wpa_s, &capa); if (capa_res == 0) { diff --git a/wpa_supplicant/wpa_supplicant_i.h b/wpa_supplicant/wpa_supplicant_i.h index 05c3d9238..7949a0186 100644 --- a/wpa_supplicant/wpa_supplicant_i.h +++ b/wpa_supplicant/wpa_supplicant_i.h @@ -888,6 +888,12 @@ struct wpa_supplicant { u16 num_modes; u16 flags; } hw; + enum local_hw_capab { + CAPAB_NO_HT_VHT, + CAPAB_HT, + CAPAB_HT40, + CAPAB_VHT, + } hw_capab; #ifdef CONFIG_MACSEC struct ieee802_1x_kay *kay; #endif /* CONFIG_MACSEC */