aboutsummaryrefslogtreecommitdiff
path: root/src/ap/acs.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/ap/acs.c')
-rw-r--r--src/ap/acs.c350
1 files changed, 292 insertions, 58 deletions
diff --git a/src/ap/acs.c b/src/ap/acs.c
index 0030edc2..1181c7d5 100644
--- a/src/ap/acs.c
+++ b/src/ap/acs.c
@@ -241,6 +241,57 @@
* [1] http://en.wikipedia.org/wiki/Near_and_far_field
*/
+enum bw_type {
+ ACS_BW40,
+ ACS_BW80,
+ ACS_BW160,
+};
+
+struct bw_item {
+ int first;
+ int last;
+ int center_chan;
+};
+
+static const struct bw_item bw_40[] = {
+ { 5180, 5200, 38 }, { 5220, 5240, 46 }, { 5260, 5280, 54 },
+ { 5300, 5320, 62 }, { 5500, 5520, 102 }, { 5540, 5560, 110 },
+ { 5580, 5600, 110 }, { 5620, 5640, 126}, { 5660, 5680, 134 },
+ { 5700, 5720, 142 }, { 5745, 5765, 151 }, { 5785, 5805, 159 },
+ { 5825, 5845, 167 }, { 5865, 5885, 175 },
+ { 5955, 5975, 3 }, { 5995, 6015, 11 }, { 6035, 6055, 19 },
+ { 6075, 6095, 27 }, { 6115, 6135, 35 }, { 6155, 6175, 43 },
+ { 6195, 6215, 51 }, { 6235, 6255, 59 }, { 6275, 6295, 67 },
+ { 6315, 6335, 75 }, { 6355, 6375, 83 }, { 6395, 6415, 91 },
+ { 6435, 6455, 99 }, { 6475, 6495, 107 }, { 6515, 6535, 115 },
+ { 6555, 6575, 123 }, { 6595, 6615, 131 }, { 6635, 6655, 139 },
+ { 6675, 6695, 147 }, { 6715, 6735, 155 }, { 6755, 6775, 163 },
+ { 6795, 6815, 171 }, { 6835, 6855, 179 }, { 6875, 6895, 187 },
+ { 6915, 6935, 195 }, { 6955, 6975, 203 }, { 6995, 7015, 211 },
+ { 7035, 7055, 219 }, { 7075, 7095, 227}, { -1, -1, -1 }
+};
+static const struct bw_item bw_80[] = {
+ { 5180, 5240, 42 }, { 5260, 5320, 58 }, { 5500, 5560, 106 },
+ { 5580, 5640, 122 }, { 5660, 5720, 138 }, { 5745, 5805, 155 },
+ { 5825, 5885, 171},
+ { 5955, 6015, 7 }, { 6035, 6095, 23 }, { 6115, 6175, 39 },
+ { 6195, 6255, 55 }, { 6275, 6335, 71 }, { 6355, 6415, 87 },
+ { 6435, 6495, 103 }, { 6515, 6575, 119 }, { 6595, 6655, 135 },
+ { 6675, 6735, 151 }, { 6755, 6815, 167 }, { 6835, 6895, 183 },
+ { 6915, 6975, 199 }, { 6995, 7055, 215 }, { -1, -1, -1 }
+};
+static const struct bw_item bw_160[] = {
+ { 5180, 5320, 50 }, { 5500, 5640, 114 }, { 5745, 5885, 163 },
+ { 5955, 6095, 15 }, { 6115, 6255, 47 }, { 6275, 6415, 79 },
+ { 6435, 6575, 111 }, { 6595, 6735, 143 },
+ { 6755, 6895, 175 }, { 6915, 7055, 207 }, { -1, -1, -1 }
+};
+static const struct bw_item *bw_desc[] = {
+ [ACS_BW40] = bw_40,
+ [ACS_BW80] = bw_80,
+ [ACS_BW160] = bw_160,
+};
+
static int acs_request_scan(struct hostapd_iface *iface);
static int acs_survey_is_sufficient(struct freq_survey *survey);
@@ -275,6 +326,7 @@ static void acs_cleanup_mode(struct hostapd_hw_modes *mode)
dl_list_init(&chan->survey_list);
chan->flag |= HOSTAPD_CHAN_SURVEY_LIST_INITIALIZED;
chan->min_nf = 0;
+ chan->punct_bitmap = 0;
}
}
@@ -370,48 +422,31 @@ acs_survey_chan_interference_factor(struct hostapd_iface *iface,
}
-static int acs_usable_bw40_chan(const struct hostapd_channel_data *chan)
+static bool acs_usable_bw_chan(const struct hostapd_channel_data *chan,
+ enum bw_type bw)
{
- const int allowed[] = { 5180, 5220, 5260, 5300, 5500, 5540, 5580, 5620,
- 5660, 5745, 5785, 4920, 4960, 5955, 5995, 6035,
- 6075, 6115, 6155, 6195, 6235, 6275, 6315, 6355,
- 6395, 6435, 6475, 6515, 6555, 6595, 6635, 6675,
- 6715, 6755, 6795, 6835, 6875, 6915, 6955, 6995,
- 7035, 7075 };
- unsigned int i;
-
- for (i = 0; i < ARRAY_SIZE(allowed); i++)
- if (chan->freq == allowed[i])
- return 1;
-
- return 0;
-}
-
-
-static int acs_usable_bw80_chan(const struct hostapd_channel_data *chan)
-{
- const int allowed[] = { 5180, 5260, 5500, 5580, 5660, 5745, 5955, 6035,
- 6115, 6195, 6275, 6355, 6435, 6515, 6595, 6675,
- 6755, 6835, 6915, 6995 };
- unsigned int i;
+ unsigned int i = 0;
- for (i = 0; i < ARRAY_SIZE(allowed); i++)
- if (chan->freq == allowed[i])
- return 1;
+ while (bw_desc[bw][i].first != -1) {
+ if (chan->freq == bw_desc[bw][i].first)
+ return true;
+ i++;
+ }
- return 0;
+ return false;
}
-static int acs_usable_bw160_chan(const struct hostapd_channel_data *chan)
+static int acs_get_bw_center_chan(int freq, enum bw_type bw)
{
- const int allowed[] = { 5180, 5500, 5955, 6115, 6275, 6435, 6595, 6755,
- 6915 };
- unsigned int i;
+ unsigned int i = 0;
- for (i = 0; i < ARRAY_SIZE(allowed); i++)
- if (chan->freq == allowed[i])
- return 1;
+ while (bw_desc[bw][i].first != -1) {
+ if (freq >= bw_desc[bw][i].first &&
+ freq <= bw_desc[bw][i].last)
+ return bw_desc[bw][i].center_chan;
+ i++;
+ }
return 0;
}
@@ -420,19 +455,24 @@ static int acs_usable_bw160_chan(const struct hostapd_channel_data *chan)
static int acs_survey_is_sufficient(struct freq_survey *survey)
{
if (!(survey->filled & SURVEY_HAS_NF)) {
- wpa_printf(MSG_INFO, "ACS: Survey is missing noise floor");
+ wpa_printf(MSG_INFO,
+ "ACS: Survey for freq %d is missing noise floor",
+ survey->freq);
return 0;
}
if (!(survey->filled & SURVEY_HAS_CHAN_TIME)) {
- wpa_printf(MSG_INFO, "ACS: Survey is missing channel time");
+ wpa_printf(MSG_INFO,
+ "ACS: Survey for freq %d is missing channel time",
+ survey->freq);
return 0;
}
if (!(survey->filled & SURVEY_HAS_CHAN_TIME_BUSY) &&
!(survey->filled & SURVEY_HAS_CHAN_TIME_RX)) {
wpa_printf(MSG_INFO,
- "ACS: Survey is missing RX and busy time (at least one is required)");
+ "ACS: Survey for freq %d is missing RX and busy time (at least one is required)",
+ survey->freq);
return 0;
}
@@ -540,6 +580,10 @@ static void acs_survey_mode_interference_factor(
if (!acs_usable_chan(chan))
continue;
+ if ((chan->flag & HOSTAPD_CHAN_RADAR) &&
+ iface->conf->acs_exclude_dfs)
+ continue;
+
if (!is_in_chanlist(iface, chan))
continue;
@@ -549,6 +593,10 @@ static void acs_survey_mode_interference_factor(
if (chan->max_tx_power < iface->conf->min_tx_power)
continue;
+ if ((chan->flag & HOSTAPD_CHAN_INDOOR_ONLY) &&
+ iface->conf->country[2] == 0x4f)
+ continue;
+
wpa_printf(MSG_DEBUG, "ACS: Survey analysis for channel %d (%d MHz)",
chan->chan, chan->freq);
@@ -594,6 +642,26 @@ acs_find_chan_mode(struct hostapd_hw_modes *mode, int freq)
}
+static enum hostapd_hw_mode
+acs_find_mode(struct hostapd_iface *iface, int freq)
+{
+ int i;
+ struct hostapd_hw_modes *mode;
+ struct hostapd_channel_data *chan;
+
+ for (i = 0; i < iface->num_hw_features; i++) {
+ mode = &iface->hw_features[i];
+ if (!hostapd_hw_skip_mode(iface, mode)) {
+ chan = acs_find_chan_mode(mode, freq);
+ if (chan)
+ return mode->mode;
+ }
+ }
+
+ return HOSTAPD_MODE_IEEE80211ANY;
+}
+
+
static struct hostapd_channel_data *
acs_find_chan(struct hostapd_iface *iface, int freq)
{
@@ -644,6 +712,62 @@ static int is_common_24ghz_chan(int chan)
#define ACS_24GHZ_PREFER_1_6_11 0.8
#endif /* ACS_24GHZ_PREFER_1_6_11 */
+
+#ifdef CONFIG_IEEE80211BE
+static void acs_update_puncturing_bitmap(struct hostapd_iface *iface,
+ struct hostapd_hw_modes *mode, u32 bw,
+ int n_chans,
+ struct hostapd_channel_data *chan,
+ long double factor,
+ int index_primary)
+{
+ struct hostapd_config *conf = iface->conf;
+ struct hostapd_channel_data *adj_chan = NULL, *first_chan = chan;
+ int i;
+ long double threshold;
+
+ /*
+ * If threshold is 0 or user configured puncturing pattern is
+ * available then don't add additional puncturing.
+ */
+ if (!conf->punct_acs_threshold || conf->punct_bitmap)
+ return;
+
+ if (is_24ghz_mode(mode->mode) || bw < 80)
+ return;
+
+ threshold = factor * conf->punct_acs_threshold / 100;
+ for (i = 0; i < n_chans; i++) {
+ int adj_freq;
+
+ if (i == index_primary)
+ continue; /* Cannot puncture primary channel */
+
+ if (i > index_primary)
+ adj_freq = chan->freq + (i - index_primary) * 20;
+ else
+ adj_freq = chan->freq - (index_primary - i) * 20;
+
+ adj_chan = acs_find_chan(iface, adj_freq);
+ if (!adj_chan) {
+ chan->punct_bitmap = 0;
+ return;
+ }
+
+ if (i == 0)
+ first_chan = adj_chan;
+
+ if (adj_chan->interference_factor > threshold)
+ chan->punct_bitmap |= BIT(i);
+ }
+
+ if (!is_punct_bitmap_valid(bw, (chan->freq - first_chan->freq) / 20,
+ chan->punct_bitmap))
+ chan->punct_bitmap = 0;
+}
+#endif /* CONFIG_IEEE80211BE */
+
+
static void
acs_find_ideal_chan_mode(struct hostapd_iface *iface,
struct hostapd_hw_modes *mode,
@@ -652,7 +776,7 @@ acs_find_ideal_chan_mode(struct hostapd_iface *iface,
struct hostapd_channel_data **ideal_chan,
long double *ideal_factor)
{
- struct hostapd_channel_data *chan, *adj_chan = NULL;
+ struct hostapd_channel_data *chan, *adj_chan = NULL, *best;
long double factor;
int i, j;
unsigned int k;
@@ -660,16 +784,27 @@ acs_find_ideal_chan_mode(struct hostapd_iface *iface,
for (i = 0; i < mode->num_channels; i++) {
double total_weight;
struct acs_bias *bias, tmp_bias;
+ bool update_best = true;
- chan = &mode->channels[i];
+ best = chan = &mode->channels[i];
/* Since in the current ACS implementation the first channel is
* always a primary channel, skip channels not available as
* primary until more sophisticated channel selection is
- * implemented. */
+ * implemented.
+ *
+ * If this implementation is changed to allow any channel in
+ * the bandwidth to be the primary one, the last parameter to
+ * acs_update_puncturing_bitmap() should be changed to the index
+ * of the primary channel
+ */
if (!chan_pri_allowed(chan))
continue;
+ if ((chan->flag & HOSTAPD_CHAN_RADAR) &&
+ iface->conf->acs_exclude_dfs)
+ continue;
+
if (!is_in_chanlist(iface, chan))
continue;
@@ -679,6 +814,10 @@ acs_find_ideal_chan_mode(struct hostapd_iface *iface,
if (chan->max_tx_power < iface->conf->min_tx_power)
continue;
+ if ((chan->flag & HOSTAPD_CHAN_INDOOR_ONLY) &&
+ iface->conf->country[2] == 0x4f)
+ continue;
+
if (!chan_bw_allowed(chan, bw, 1, 1)) {
wpa_printf(MSG_DEBUG,
"ACS: Channel %d: BW %u is not supported",
@@ -692,7 +831,7 @@ acs_find_ideal_chan_mode(struct hostapd_iface *iface,
((iface->conf->ieee80211n &&
iface->conf->secondary_channel) ||
is_6ghz_freq(chan->freq)) &&
- !acs_usable_bw40_chan(chan)) {
+ !acs_usable_bw_chan(chan, ACS_BW40)) {
wpa_printf(MSG_DEBUG,
"ACS: Channel %d: not allowed as primary channel for 40 MHz bandwidth",
chan->chan);
@@ -702,8 +841,8 @@ acs_find_ideal_chan_mode(struct hostapd_iface *iface,
if (mode->mode == HOSTAPD_MODE_IEEE80211A &&
(iface->conf->ieee80211ac || iface->conf->ieee80211ax)) {
if (hostapd_get_oper_chwidth(iface->conf) ==
- CHANWIDTH_80MHZ &&
- !acs_usable_bw80_chan(chan)) {
+ CONF_OPER_CHWIDTH_80MHZ &&
+ !acs_usable_bw_chan(chan, ACS_BW80)) {
wpa_printf(MSG_DEBUG,
"ACS: Channel %d: not allowed as primary channel for 80 MHz bandwidth",
chan->chan);
@@ -711,8 +850,8 @@ acs_find_ideal_chan_mode(struct hostapd_iface *iface,
}
if (hostapd_get_oper_chwidth(iface->conf) ==
- CHANWIDTH_160MHZ &&
- !acs_usable_bw160_chan(chan)) {
+ CONF_OPER_CHWIDTH_160MHZ &&
+ !acs_usable_bw_chan(chan, ACS_BW160)) {
wpa_printf(MSG_DEBUG,
"ACS: Channel %d: not allowed as primary channel for 160 MHz bandwidth",
chan->chan);
@@ -740,7 +879,15 @@ acs_find_ideal_chan_mode(struct hostapd_iface *iface,
if (acs_usable_chan(adj_chan)) {
factor += adj_chan->interference_factor;
total_weight += 1;
+ } else {
+ update_best = false;
}
+
+ /* find the best channel in this segment */
+ if (update_best &&
+ adj_chan->interference_factor <
+ best->interference_factor)
+ best = adj_chan;
}
if (j != n_chans) {
@@ -749,6 +896,18 @@ acs_find_ideal_chan_mode(struct hostapd_iface *iface,
continue;
}
+ /* If the AP is in the 5 GHz or 6 GHz band, lets prefer a less
+ * crowded primary channel if one was found in the segment */
+ if (iface->current_mode->mode == HOSTAPD_MODE_IEEE80211A &&
+ chan != best) {
+ wpa_printf(MSG_DEBUG,
+ "ACS: promoting channel %d over %d (less interference %Lg/%Lg)",
+ best->chan, chan->chan,
+ chan->interference_factor,
+ best->interference_factor);
+ chan = best;
+ }
+
/* 2.4 GHz has overlapping 20 MHz channels. Include adjacent
* channel interference factor. */
if (is_24ghz_mode(mode->mode)) {
@@ -817,8 +976,20 @@ acs_find_ideal_chan_mode(struct hostapd_iface *iface,
if (acs_usable_chan(chan) &&
(!*ideal_chan || factor < *ideal_factor)) {
+ /* Reset puncturing bitmap for the previous ideal
+ * channel */
+ if (*ideal_chan)
+ (*ideal_chan)->punct_bitmap = 0;
+
*ideal_factor = factor;
*ideal_chan = chan;
+
+#ifdef CONFIG_IEEE80211BE
+ if (iface->conf->ieee80211be)
+ acs_update_puncturing_bitmap(iface, mode, bw,
+ n_chans, chan,
+ factor, 0);
+#endif /* CONFIG_IEEE80211BE */
}
/* This channel would at least be usable */
@@ -865,12 +1036,14 @@ acs_find_ideal_chan(struct hostapd_iface *iface)
if (iface->conf->ieee80211ac || iface->conf->ieee80211ax) {
switch (hostapd_get_oper_chwidth(iface->conf)) {
- case CHANWIDTH_80MHZ:
+ case CONF_OPER_CHWIDTH_80MHZ:
n_chans = 4;
break;
- case CHANWIDTH_160MHZ:
+ case CONF_OPER_CHWIDTH_160MHZ:
n_chans = 8;
break;
+ default:
+ break;
}
}
@@ -893,28 +1066,70 @@ bw_selected:
if (ideal_chan) {
wpa_printf(MSG_DEBUG, "ACS: Ideal channel is %d (%d MHz) with total interference factor of %Lg",
ideal_chan->chan, ideal_chan->freq, ideal_factor);
+
+#ifdef CONFIG_IEEE80211BE
+ if (iface->conf->punct_acs_threshold)
+ wpa_printf(MSG_DEBUG, "ACS: RU puncturing bitmap 0x%x",
+ ideal_chan->punct_bitmap);
+#endif /* CONFIG_IEEE80211BE */
+
return ideal_chan;
}
+#ifdef CONFIG_IEEE80211BE
+ if (iface->conf->punct_acs_threshold)
+ wpa_printf(MSG_DEBUG, "ACS: RU puncturing bitmap 0x%x",
+ ideal_chan->punct_bitmap);
+#endif /* CONFIG_IEEE80211BE */
+
return rand_chan;
}
+static void acs_adjust_secondary(struct hostapd_iface *iface)
+{
+ unsigned int i;
+
+ /* When working with bandwidth over 20 MHz on the 5 GHz or 6 GHz band,
+ * ACS can return a secondary channel which is not the first channel of
+ * the segment and we need to adjust. */
+ if (!iface->conf->secondary_channel ||
+ acs_find_mode(iface, iface->freq) != HOSTAPD_MODE_IEEE80211A)
+ return;
+
+ wpa_printf(MSG_DEBUG, "ACS: Adjusting HT/VHT/HE secondary frequency");
+
+ for (i = 0; bw_desc[ACS_BW40][i].first != -1; i++) {
+ if (iface->freq == bw_desc[ACS_BW40][i].first)
+ iface->conf->secondary_channel = 1;
+ else if (iface->freq == bw_desc[ACS_BW40][i].last)
+ iface->conf->secondary_channel = -1;
+ }
+}
+
+
static void acs_adjust_center_freq(struct hostapd_iface *iface)
{
- int offset;
+ int center;
wpa_printf(MSG_DEBUG, "ACS: Adjusting VHT center frequency");
switch (hostapd_get_oper_chwidth(iface->conf)) {
- case CHANWIDTH_USE_HT:
- offset = 2 * iface->conf->secondary_channel;
+ case CONF_OPER_CHWIDTH_USE_HT:
+ if (iface->conf->secondary_channel &&
+ iface->freq >= 2400 && iface->freq < 2500)
+ center = iface->conf->channel +
+ 2 * iface->conf->secondary_channel;
+ else if (iface->conf->secondary_channel)
+ center = acs_get_bw_center_chan(iface->freq, ACS_BW40);
+ else
+ center = iface->conf->channel;
break;
- case CHANWIDTH_80MHZ:
- offset = 6;
+ case CONF_OPER_CHWIDTH_80MHZ:
+ center = acs_get_bw_center_chan(iface->freq, ACS_BW80);
break;
- case CHANWIDTH_160MHZ:
- offset = 14;
+ case CONF_OPER_CHWIDTH_160MHZ:
+ center = acs_get_bw_center_chan(iface->freq, ACS_BW160);
break;
default:
/* TODO: How can this be calculated? Adjust
@@ -924,8 +1139,7 @@ static void acs_adjust_center_freq(struct hostapd_iface *iface)
return;
}
- hostapd_set_oper_centr_freq_seg0_idx(iface->conf,
- iface->conf->channel + offset);
+ hostapd_set_oper_centr_freq_seg0_idx(iface->conf, center);
}
@@ -980,9 +1194,23 @@ static void acs_study(struct hostapd_iface *iface)
iface->conf->channel = ideal_chan->chan;
iface->freq = ideal_chan->freq;
+#ifdef CONFIG_IEEE80211BE
+ iface->conf->punct_bitmap = ideal_chan->punct_bitmap;
+#endif /* CONFIG_IEEE80211BE */
- if (iface->conf->ieee80211ac || iface->conf->ieee80211ax)
+ if (iface->conf->ieee80211ac || iface->conf->ieee80211ax) {
+ acs_adjust_secondary(iface);
acs_adjust_center_freq(iface);
+ }
+
+ err = hostapd_select_hw_mode(iface);
+ if (err) {
+ wpa_printf(MSG_ERROR,
+ "ACS: Could not (err: %d) select hw_mode for freq=%d channel=%d",
+ err, iface->freq, iface->conf->channel);
+ err = -1;
+ goto fail;
+ }
err = 0;
fail:
@@ -1044,7 +1272,9 @@ static int * acs_request_scan_add_freqs(struct hostapd_iface *iface,
for (i = 0; i < mode->num_channels; i++) {
chan = &mode->channels[i];
- if (chan->flag & HOSTAPD_CHAN_DISABLED)
+ if ((chan->flag & HOSTAPD_CHAN_DISABLED) ||
+ ((chan->flag & HOSTAPD_CHAN_RADAR) &&
+ iface->conf->acs_exclude_dfs))
continue;
if (!is_in_chanlist(iface, chan))
@@ -1056,6 +1286,10 @@ static int * acs_request_scan_add_freqs(struct hostapd_iface *iface,
if (chan->max_tx_power < iface->conf->min_tx_power)
continue;
+ if ((chan->flag & HOSTAPD_CHAN_INDOOR_ONLY) &&
+ iface->conf->country[2] == 0x4f)
+ continue;
+
*freq++ = chan->freq;
}