]> git.ipfire.org Git - thirdparty/hostap.git/commitdiff
Prefer 5 GHz networks over 2.4 GHz networks
authorGary Morain <gmorain@chromium.org>
Fri, 5 Aug 2011 23:23:12 +0000 (16:23 -0700)
committerJouni Malinen <j@w1.fi>
Sun, 30 Oct 2011 19:08:21 +0000 (21:08 +0200)
In scan.c, merge a channel's noise value into the scan results. When
comparing scan results, compute the signal-to-noise ratio and use it
when available. Prefer a 5 GHz network if its SNR is really big (> 30)
or if its SNR is relatively close to the other network's.

src/drivers/driver_nl80211.c
wpa_supplicant/scan.c

index 8fc6dd9bf6bbebe359aa24f222ce7a61bff0d0d4..2ab10aef1a1341956e542aba74b16d7c0060de78 100644 (file)
@@ -1413,6 +1413,80 @@ static int nl80211_get_link_noise(struct wpa_driver_nl80211_data *drv,
 }
 
 
+static int get_noise_for_scan_results(struct nl_msg *msg, void *arg)
+{
+       struct nlattr *tb[NL80211_ATTR_MAX + 1];
+       struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg));
+       struct nlattr *sinfo[NL80211_SURVEY_INFO_MAX + 1];
+       static struct nla_policy survey_policy[NL80211_SURVEY_INFO_MAX + 1] = {
+               [NL80211_SURVEY_INFO_FREQUENCY] = { .type = NLA_U32 },
+               [NL80211_SURVEY_INFO_NOISE] = { .type = NLA_U8 },
+       };
+       struct wpa_scan_results *scan_results = arg;
+       struct wpa_scan_res *scan_res;
+       size_t i;
+
+       nla_parse(tb, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0),
+                 genlmsg_attrlen(gnlh, 0), NULL);
+
+       if (!tb[NL80211_ATTR_SURVEY_INFO]) {
+               wpa_printf(MSG_DEBUG, "nl80211: Survey data missing");
+               return NL_SKIP;
+       }
+
+       if (nla_parse_nested(sinfo, NL80211_SURVEY_INFO_MAX,
+                            tb[NL80211_ATTR_SURVEY_INFO],
+                            survey_policy)) {
+               wpa_printf(MSG_DEBUG, "nl80211: Failed to parse nested "
+                          "attributes");
+               return NL_SKIP;
+       }
+
+       if (!sinfo[NL80211_SURVEY_INFO_NOISE])
+               return NL_SKIP;
+
+       if (!sinfo[NL80211_SURVEY_INFO_FREQUENCY])
+               return NL_SKIP;
+
+       for (i = 0; i < scan_results->num; ++i) {
+               scan_res = scan_results->res[i];
+               if (!scan_res)
+                       continue;
+               if ((int) nla_get_u32(sinfo[NL80211_SURVEY_INFO_FREQUENCY]) !=
+                   scan_res->freq)
+                       continue;
+               if (!(scan_res->flags & WPA_SCAN_NOISE_INVALID))
+                       continue;
+               scan_res->noise = (s8)
+                       nla_get_u8(sinfo[NL80211_SURVEY_INFO_NOISE]);
+               scan_res->flags &= ~WPA_SCAN_NOISE_INVALID;
+       }
+
+       return NL_SKIP;
+}
+
+
+static int nl80211_get_noise_for_scan_results(
+       struct wpa_driver_nl80211_data *drv,
+       struct wpa_scan_results *scan_res)
+{
+       struct nl_msg *msg;
+
+       msg = nlmsg_alloc();
+       if (!msg)
+               return -ENOMEM;
+
+       nl80211_cmd(drv, msg, NLM_F_DUMP, NL80211_CMD_GET_SURVEY);
+
+       NLA_PUT_U32(msg, NL80211_ATTR_IFINDEX, drv->ifindex);
+
+       return send_and_recv_msgs(drv, msg, get_noise_for_scan_results,
+                                 scan_res);
+ nla_put_failure:
+       return -ENOBUFS;
+}
+
+
 static void nl80211_cqm_event(struct wpa_driver_nl80211_data *drv,
                              struct nlattr *tb[])
 {
@@ -3129,8 +3203,9 @@ nl80211_get_scan_results(struct wpa_driver_nl80211_data *drv)
        ret = send_and_recv_msgs(drv, msg, bss_info_handler, &arg);
        msg = NULL;
        if (ret == 0) {
-               wpa_printf(MSG_DEBUG, "Received scan results (%lu BSSes)",
-                          (unsigned long) res->num);
+               wpa_printf(MSG_DEBUG, "nl80211: Received scan results (%lu "
+                          "BSSes)", (unsigned long) res->num);
+               nl80211_get_noise_for_scan_results(drv, res);
                return res;
        }
        wpa_printf(MSG_DEBUG, "nl80211: Scan result fetch failed: ret=%d "
index 0f5335e9375e2715db44dba205ff2596c056969d..bb3fa86dc4a5b7584691d7fed436225211e95d35 100644 (file)
@@ -985,15 +985,28 @@ struct wpabuf * wpa_scan_get_vendor_ie_multi_beacon(
 }
 
 
+/*
+ * Channels with a great SNR can operate at full rate. What is a great SNR?
+ * This doc https://supportforums.cisco.com/docs/DOC-12954 says, "the general
+ * rule of thumb is that any SNR above 20 is good." This one
+ * http://www.cisco.com/en/US/tech/tk722/tk809/technologies_q_and_a_item09186a00805e9a96.shtml#qa23
+ * recommends 25 as a minimum SNR for 54 Mbps data rate. 30 is chosen here as a
+ * conservative value.
+ */
+#define GREAT_SNR 30
+
 /* Compare function for sorting scan results. Return >0 if @b is considered
  * better. */
 static int wpa_scan_result_compar(const void *a, const void *b)
 {
+#define IS_5GHZ(n) (n > 4000)
+#define MIN(a,b) a < b ? a : b
        struct wpa_scan_res **_wa = (void *) a;
        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;
 
        /* WPA/WPA2 support preferred */
        wpa_a = wpa_scan_get_vendor_ie(wa, WPA_IE_VENDOR_TYPE) != NULL ||
@@ -1014,23 +1027,46 @@ static int wpa_scan_result_compar(const void *a, const void *b)
            (wb->caps & IEEE80211_CAP_PRIVACY) == 0)
                return -1;
 
-       /* best/max rate preferred if signal level close enough XXX */
-       if ((wa->level && wb->level && abs(wb->level - wa->level) < 5) ||
+       if ((wa->flags & wb->flags & WPA_SCAN_LEVEL_DBM) &&
+           !((wa->flags | wb->flags) & WPA_SCAN_NOISE_INVALID)) {
+               snr_a = MIN(wa->level - wa->noise, GREAT_SNR);
+               snr_b = MIN(wb->level - wb->noise, GREAT_SNR);
+       } else {
+               /* Not suitable information to calculate SNR, so use level */
+               snr_a = wa->level;
+               snr_b = wb->level;
+       }
+
+       wpa_printf(MSG_EXCESSIVE, "BSS(a) " MACSTR " freq:%d level:%d "
+                  "noise:%d snr:%d flags:0x%x",
+                  MAC2STR(wa->bssid), wa->freq, wa->level, wa->noise, snr_a,
+                  wa->flags);
+       wpa_printf(MSG_EXCESSIVE, "BSS(b) " MACSTR " freq:%d level:%d "
+                  "noise:%d snr:%d flags:0x%x",
+                  MAC2STR(wb->bssid), wb->freq, wb->level, wb->noise, snr_b,
+                  wb->flags);
+
+       /* best/max rate preferred if SNR close enough */
+        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 (IS_5GHZ(wa->freq) ^ IS_5GHZ(wb->freq))
+                       return IS_5GHZ(wa->freq) ? -1 : 1;
        }
 
        /* use freq for channel preference */
 
-       /* all things being equal, use signal level; if signal levels are
+       /* 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 (wb->level == wa->level)
+       if (snr_b == snr_a)
                return wb->qual - wa->qual;
-       return wb->level - wa->level;
+       return snr_b - snr_a;
+#undef MIN
+#undef IS_5GHZ
 }