diff mbox series

ACS: cover not-offloaded-ACS with hw_mode=any

Message ID 1578904288-5418-1-git-send-email-neojou@gmail.com
State Accepted
Headers show
Series ACS: cover not-offloaded-ACS with hw_mode=any | expand

Commit Message

Neo Jou Jan. 13, 2020, 8:31 a.m. UTC
From: Neo Jou <neojou@gmail.com>

This patch is to extend the existing hw_mode=any to cover
the not-offloaded-ACS case.

We DONOT duplicate for the mode HOSTAPD_MODE_IEEE80211ANY in
wpa_driver_nl80211_postprocess_modes(), so hostapd_select_hw_mode()
cannot find suitable hw_mode and iface->current_mode is NULL. Thus,
ACS code combine channels from multiple hw_mode lists, to determine
which channel needs to be surveyed. Thus, there are some patches in
src/ap/acs.c to handle the case that iface->current_mode is NULL,
and to combine channels.

Also in hostapd_is_usable_chans() called by hostapd_check_chans(),
it updates the hw_mode, based on the frequency channel ACS picked.

By this way, "hw_mode=any" can be used for not-offloaded-ACS case.

Signed-off-by: Neo Jou <neojou@gmail.com>
---
 src/ap/acs.c           | 327 +++++++++++++++++++++++++++++++++++++------------
 src/ap/drv_callbacks.c |  23 +++-
 src/ap/hostapd.h       |   1 +
 src/ap/hw_features.c   |  50 +++++++-
 4 files changed, 311 insertions(+), 90 deletions(-)

Comments

Jouni Malinen Feb. 29, 2020, 10:20 a.m. UTC | #1
On Mon, Jan 13, 2020 at 04:31:28PM +0800, neojou@gmail.com wrote:
> This patch is to extend the existing hw_mode=any to cover
> the not-offloaded-ACS case.
> 
> We DONOT duplicate for the mode HOSTAPD_MODE_IEEE80211ANY in
> wpa_driver_nl80211_postprocess_modes(), so hostapd_select_hw_mode()
> cannot find suitable hw_mode and iface->current_mode is NULL. Thus,
> ACS code combine channels from multiple hw_mode lists, to determine
> which channel needs to be surveyed. Thus, there are some patches in
> src/ap/acs.c to handle the case that iface->current_mode is NULL,
> and to combine channels.
> 
> Also in hostapd_is_usable_chans() called by hostapd_check_chans(),
> it updates the hw_mode, based on the frequency channel ACS picked.
> 
> By this way, "hw_mode=any" can be used for not-offloaded-ACS case.

Thanks, applied with some cleanup and fixes.
Neo Jou March 5, 2020, 3:01 a.m. UTC | #2
The cleanup and fixes are better than that I wrote at the first. Learn
from this and thanks for kind guidance.

BR,
Neo Jou

Jouni Malinen <j@w1.fi>
>
> On Mon, Jan 13, 2020 at 04:31:28PM +0800, neojou@gmail.com wrote:
> > This patch is to extend the existing hw_mode=any to cover
> > the not-offloaded-ACS case.
> >
> > We DONOT duplicate for the mode HOSTAPD_MODE_IEEE80211ANY in
> > wpa_driver_nl80211_postprocess_modes(), so hostapd_select_hw_mode()
> > cannot find suitable hw_mode and iface->current_mode is NULL. Thus,
> > ACS code combine channels from multiple hw_mode lists, to determine
> > which channel needs to be surveyed. Thus, there are some patches in
> > src/ap/acs.c to handle the case that iface->current_mode is NULL,
> > and to combine channels.
> >
> > Also in hostapd_is_usable_chans() called by hostapd_check_chans(),
> > it updates the hw_mode, based on the frequency channel ACS picked.
> >
> > By this way, "hw_mode=any" can be used for not-offloaded-ACS case.
>
> Thanks, applied with some cleanup and fixes.
>
> --
> Jouni Malinen                                            PGP id EFC895FA
diff mbox series

Patch

diff --git a/src/ap/acs.c b/src/ap/acs.c
index 232afa8..2ffa2ff 100644
--- a/src/ap/acs.c
+++ b/src/ap/acs.c
@@ -261,13 +261,15 @@  static void acs_clean_chan_surveys(struct hostapd_channel_data *chan)
 }
 
 
-void acs_cleanup(struct hostapd_iface *iface)
+static inline
+void acs_cleanup_by_mode(struct hostapd_iface *iface,
+			 struct hostapd_hw_modes *mode)
 {
 	int i;
 	struct hostapd_channel_data *chan;
 
-	for (i = 0; i < iface->current_mode->num_channels; i++) {
-		chan = &iface->current_mode->channels[i];
+	for (i = 0; i < mode->num_channels; i++) {
+		chan = &mode->channels[i];
 
 		if (chan->flag & HOSTAPD_CHAN_SURVEY_LIST_INITIALIZED)
 			acs_clean_chan_surveys(chan);
@@ -276,12 +278,31 @@  void acs_cleanup(struct hostapd_iface *iface)
 		chan->flag |= HOSTAPD_CHAN_SURVEY_LIST_INITIALIZED;
 		chan->min_nf = 0;
 	}
+}
+
+void acs_cleanup(struct hostapd_iface *iface)
+{
+	if (iface->current_mode == NULL) {
+		int i;
+		struct hostapd_hw_modes *mode;
+
+		for (i = 0; i < iface->num_hw_features; i++) {
+			mode = &iface->hw_features[i];
+
+			if (iface->hw_modes_has_11g &&
+			    (mode->mode == HOSTAPD_MODE_IEEE80211B))
+				continue;
+
+			acs_cleanup_by_mode(iface, mode);
+		}
+	} else {
+		acs_cleanup_by_mode(iface, iface->current_mode);
+	}
 
 	iface->chans_surveyed = 0;
 	iface->acs_num_completed_scans = 0;
 }
 
-
 static void acs_fail(struct hostapd_iface *iface)
 {
 	wpa_printf(MSG_ERROR, "ACS: Failed to start");
@@ -453,19 +474,45 @@  static int acs_survey_list_is_sufficient(struct hostapd_channel_data *chan)
 }
 
 
-static int acs_surveys_are_sufficient(struct hostapd_iface *iface)
+static inline
+int acs_surveys_are_sufficient_by_mode(struct hostapd_iface *iface,
+				       struct hostapd_hw_modes *mode)
 {
 	int i;
 	struct hostapd_channel_data *chan;
 	int valid = 0;
 
-	for (i = 0; i < iface->current_mode->num_channels; i++) {
-		chan = &iface->current_mode->channels[i];
+	for (i = 0; i < mode->num_channels; i++) {
+		chan = &mode->channels[i];
 		if (!(chan->flag & HOSTAPD_CHAN_DISABLED) &&
 		    acs_survey_list_is_sufficient(chan))
 			valid++;
 	}
 
+	return valid;
+}
+
+static int acs_surveys_are_sufficient(struct hostapd_iface *iface)
+{
+	int valid = 0;
+
+	if (iface->current_mode == NULL) {
+		int i;
+		struct hostapd_hw_modes *mode;
+
+		for (i = 0; i < iface->num_hw_features; i++) {
+			mode = &iface->hw_features[i];
+
+			if (iface->hw_modes_has_11g &&
+			    (mode->mode == HOSTAPD_MODE_IEEE80211B))
+				continue;
+
+			valid = acs_surveys_are_sufficient_by_mode(iface, mode);
+		}
+	} else {
+		valid = acs_surveys_are_sufficient_by_mode(iface, iface->current_mode);
+	}
+
 	/* We need at least survey data for one channel */
 	return !!valid;
 }
@@ -489,14 +536,15 @@  static int is_in_chanlist(struct hostapd_iface *iface,
 }
 
 
-static void acs_survey_all_chans_intereference_factor(
-	struct hostapd_iface *iface)
+static void acs_survey_all_chans_intereference_factor_by_mode(
+	struct hostapd_iface *iface,
+	struct hostapd_hw_modes *mode)
 {
 	int i;
 	struct hostapd_channel_data *chan;
 
-	for (i = 0; i < iface->current_mode->num_channels; i++) {
-		chan = &iface->current_mode->channels[i];
+	for (i = 0; i < mode->num_channels; i++) {
+		chan = &mode->channels[i];
 
 		if (!acs_usable_chan(chan))
 			continue;
@@ -514,15 +562,40 @@  static void acs_survey_all_chans_intereference_factor(
 	}
 }
 
+static void acs_survey_all_chans_intereference_factor(
+	struct hostapd_iface *iface)
+{
+	if (iface->current_mode == NULL) {
+		int i;
+		struct hostapd_hw_modes *mode;
 
-static struct hostapd_channel_data *acs_find_chan(struct hostapd_iface *iface,
-						  int freq)
+		for (i = 0; i < iface->num_hw_features; i++) {
+			mode = &iface->hw_features[i];
+
+			if (iface->hw_modes_has_11g &&
+			    (mode->mode == HOSTAPD_MODE_IEEE80211B))
+				continue;
+
+			acs_survey_all_chans_intereference_factor_by_mode(
+				iface, mode);
+
+		}
+	} else
+		acs_survey_all_chans_intereference_factor_by_mode(
+			iface, iface->current_mode);
+}
+
+
+static inline struct hostapd_channel_data *
+acs_find_chan_by_mode(struct hostapd_iface *iface,
+	int freq,
+	struct hostapd_hw_modes *mode)
 {
 	struct hostapd_channel_data *chan;
 	int i;
 
-	for (i = 0; i < iface->current_mode->num_channels; i++) {
-		chan = &iface->current_mode->channels[i];
+	for (i = 0; i < mode->num_channels; i++) {
+		chan = &mode->channels[i];
 
 		if (chan->flag & HOSTAPD_CHAN_DISABLED)
 			continue;
@@ -534,6 +607,30 @@  static struct hostapd_channel_data *acs_find_chan(struct hostapd_iface *iface,
 	return NULL;
 }
 
+static struct hostapd_channel_data *acs_find_chan(struct hostapd_iface *iface,
+						  int freq)
+{
+	struct hostapd_channel_data *chan = NULL;
+
+	if (iface->current_mode == NULL) {
+		int i;
+		struct hostapd_hw_modes *mode;
+
+		for (i = 0; i < iface->num_hw_features; i++) {
+			mode = &iface->hw_features[i];
+
+			if (iface->hw_modes_has_11g &&
+			    (mode->mode == HOSTAPD_MODE_IEEE80211B))
+				continue;
+
+			chan = acs_find_chan_by_mode(iface, freq, mode);
+		}
+	} else {
+		chan = acs_find_chan_by_mode(iface, freq, iface->current_mode);
+	}
+
+	return chan;
+}
 
 static int is_24ghz_mode(enum hostapd_hw_mode mode)
 {
@@ -565,58 +662,25 @@  static int is_common_24ghz_chan(int chan)
 #define ACS_24GHZ_PREFER_1_6_11 0.8
 #endif /* ACS_24GHZ_PREFER_1_6_11 */
 
-/*
- * At this point it's assumed chan->interface_factor has been computed.
- * This function should be reusable regardless of interference computation
- * option (survey, BSS, spectral, ...). chan->interference factor must be
- * summable (i.e., must be always greater than zero).
- */
-static struct hostapd_channel_data *
-acs_find_ideal_chan(struct hostapd_iface *iface)
+static void
+acs_find_ideal_chan_by_mode(struct hostapd_iface *iface,
+			    struct hostapd_hw_modes *mode,
+			    int n_chans,
+			    u32 bw,
+			    struct hostapd_channel_data **rand_chan,
+			    struct hostapd_channel_data **ideal_chan,
+			    long double *ideal_factor)
 {
-	struct hostapd_channel_data *chan, *adj_chan, *ideal_chan = NULL,
-		*rand_chan = NULL;
-	long double factor, ideal_factor = 0;
+	struct hostapd_channel_data *chan, *adj_chan = NULL;
+	long double factor;
 	int i, j;
-	int n_chans = 1;
-	u32 bw;
 	unsigned int k;
 
-	/* TODO: HT40- support */
-
-	if (iface->conf->ieee80211n &&
-	    iface->conf->secondary_channel == -1) {
-		wpa_printf(MSG_ERROR, "ACS: HT40- is not supported yet. Please try HT40+");
-		return NULL;
-	}
-
-	if (iface->conf->ieee80211n &&
-	    iface->conf->secondary_channel)
-		n_chans = 2;
-
-	if (iface->conf->ieee80211ac || iface->conf->ieee80211ax) {
-		switch (hostapd_get_oper_chwidth(iface->conf)) {
-		case CHANWIDTH_80MHZ:
-			n_chans = 4;
-			break;
-		case CHANWIDTH_160MHZ:
-			n_chans = 8;
-			break;
-		}
-	}
-
-	bw = num_chan_to_bw(n_chans);
-
-	/* TODO: VHT/HE80+80. Update acs_adjust_center_freq() too. */
-
-	wpa_printf(MSG_DEBUG,
-		   "ACS: Survey analysis for selected bandwidth %d MHz", bw);
-
-	for (i = 0; i < iface->current_mode->num_channels; i++) {
+	for (i = 0; i < mode->num_channels; i++) {
 		double total_weight;
 		struct acs_bias *bias, tmp_bias;
 
-		chan = &iface->current_mode->channels[i];
+		chan = &mode->channels[i];
 
 		/* Since in the current ACS implementation the first channel is
 		 * always a primary channel, skip channels not available as
@@ -637,7 +701,7 @@  acs_find_ideal_chan(struct hostapd_iface *iface)
 
 		/* HT40 on 5 GHz has a limited set of primary channels as per
 		 * 11n Annex J */
-		if (iface->current_mode->mode == HOSTAPD_MODE_IEEE80211A &&
+		if (mode->mode == HOSTAPD_MODE_IEEE80211A &&
 		    iface->conf->ieee80211n &&
 		    iface->conf->secondary_channel &&
 		    !acs_usable_ht40_chan(chan)) {
@@ -646,7 +710,7 @@  acs_find_ideal_chan(struct hostapd_iface *iface)
 			continue;
 		}
 
-		if (iface->current_mode->mode == HOSTAPD_MODE_IEEE80211A &&
+		if (mode->mode == HOSTAPD_MODE_IEEE80211A &&
 		    (iface->conf->ieee80211ac || iface->conf->ieee80211ax)) {
 			if (hostapd_get_oper_chwidth(iface->conf) ==
 			    CHANWIDTH_80MHZ &&
@@ -698,7 +762,7 @@  acs_find_ideal_chan(struct hostapd_iface *iface)
 
 		/* 2.4 GHz has overlapping 20 MHz channels. Include adjacent
 		 * channel interference factor. */
-		if (is_24ghz_mode(iface->current_mode->mode)) {
+		if (is_24ghz_mode(mode->mode)) {
 			for (j = 0; j < n_chans; j++) {
 				adj_chan = acs_find_chan(iface, chan->freq +
 							 (j * 20) - 5);
@@ -744,7 +808,7 @@  acs_find_ideal_chan(struct hostapd_iface *iface)
 					break;
 				bias = NULL;
 			}
-		} else if (is_24ghz_mode(iface->current_mode->mode) &&
+		} else if (is_24ghz_mode(mode->mode) &&
 			   is_common_24ghz_chan(chan->chan)) {
 			tmp_bias.channel = chan->chan;
 			tmp_bias.bias = ACS_24GHZ_PREFER_1_6_11;
@@ -763,14 +827,78 @@  acs_find_ideal_chan(struct hostapd_iface *iface)
 		}
 
 		if (acs_usable_chan(chan) &&
-		    (!ideal_chan || factor < ideal_factor)) {
-			ideal_factor = factor;
-			ideal_chan = chan;
+		    (!*ideal_chan || factor < *ideal_factor)) {
+			*ideal_factor = factor;
+			*ideal_chan = chan;
 		}
 
 		/* This channel would at least be usable */
-		if (!rand_chan)
-			rand_chan = chan;
+		if (!(*rand_chan))
+			*rand_chan = chan;
+	}
+}
+
+
+
+/*
+ * At this point it's assumed chan->interface_factor has been computed.
+ * This function should be reusable regardless of interference computation
+ * option (survey, BSS, spectral, ...). chan->interference factor must be
+ * summable (i.e., must be always greater than zero).
+ */
+static struct hostapd_channel_data *
+acs_find_ideal_chan(struct hostapd_iface *iface)
+{
+	struct hostapd_channel_data *ideal_chan = NULL,
+		*rand_chan = NULL;
+	long double ideal_factor = 0;
+	int i;
+	int n_chans = 1;
+	u32 bw;
+	struct hostapd_hw_modes *mode;
+
+	/* TODO: HT40- support */
+
+	if (iface->conf->ieee80211n &&
+	    iface->conf->secondary_channel == -1) {
+		wpa_printf(MSG_ERROR, "ACS: HT40- is not supported yet. Please try HT40+");
+		return NULL;
+	}
+
+	if (iface->conf->ieee80211n &&
+	    iface->conf->secondary_channel)
+		n_chans = 2;
+
+	if (iface->conf->ieee80211ac || iface->conf->ieee80211ax) {
+		switch (hostapd_get_oper_chwidth(iface->conf)) {
+		case CHANWIDTH_80MHZ:
+			n_chans = 4;
+			break;
+		case CHANWIDTH_160MHZ:
+			n_chans = 8;
+			break;
+		}
+	}
+
+	bw = num_chan_to_bw(n_chans);
+
+	/* TODO: VHT/HE80+80. Update acs_adjust_center_freq() too. */
+
+	wpa_printf(MSG_DEBUG,
+		   "ACS: Survey analysis for selected bandwidth %d MHz", bw);
+
+	if (iface->current_mode == NULL) {
+		for (i = 0; i < iface->num_hw_features; i++) {
+			mode = &iface->hw_features[i];
+
+			if (iface->hw_modes_has_11g &&
+			    (mode->mode == HOSTAPD_MODE_IEEE80211B))
+				continue;
+
+			acs_find_ideal_chan_by_mode(iface, mode, n_chans, bw, &rand_chan, &ideal_chan, &ideal_factor);
+		}
+	} else {
+		acs_find_ideal_chan_by_mode(iface, iface->current_mode, n_chans, bw, &rand_chan, &ideal_chan, &ideal_factor);
 	}
 
 	if (ideal_chan) {
@@ -917,30 +1045,68 @@  fail:
 	acs_fail(iface);
 }
 
+static int *acs_request_scan_add_freq_by_mode(struct hostapd_iface *iface,
+					struct hostapd_hw_modes *mode,
+					int *freq)
+{
+	int i;
+	struct hostapd_channel_data *chan;
+
+	for (i = 0; i < mode->num_channels; i++) {
+		chan = &mode->channels[i];
+
+		if (chan->flag & HOSTAPD_CHAN_DISABLED)
+			continue;
+
+		if (!is_in_chanlist(iface, chan))
+			continue;
+
+		*freq++ = chan->freq;
+	}
+
+	return freq;
+}
+
 
 static int acs_request_scan(struct hostapd_iface *iface)
 {
 	struct wpa_driver_scan_params params;
-	struct hostapd_channel_data *chan;
 	int i, *freq;
+	int num_channels;
+	struct hostapd_hw_modes *mode;
 
 	os_memset(&params, 0, sizeof(params));
-	params.freqs = os_calloc(iface->current_mode->num_channels + 1,
-				 sizeof(params.freqs[0]));
+	if (iface->current_mode == NULL) {
+		num_channels = 0;
+		for (i = 0; i < iface->num_hw_features; i++) {
+			mode = &iface->hw_features[i];
+			num_channels += mode->num_channels;
+		}
+	} else {
+		num_channels = iface->current_mode->num_channels;
+	}
+
+	params.freqs = os_calloc(num_channels + 1,
+			 	sizeof(params.freqs[0]));
 	if (params.freqs == NULL)
 		return -1;
 
 	freq = params.freqs;
-	for (i = 0; i < iface->current_mode->num_channels; i++) {
-		chan = &iface->current_mode->channels[i];
-		if (chan->flag & HOSTAPD_CHAN_DISABLED)
-			continue;
 
-		if (!is_in_chanlist(iface, chan))
-			continue;
+	if (iface->current_mode == NULL) {
+		for (i = 0; i < iface->num_hw_features; i++) {
+			mode = &iface->hw_features[i];
 
-		*freq++ = chan->freq;
+			if (iface->hw_modes_has_11g &&
+			    (mode->mode == HOSTAPD_MODE_IEEE80211B))
+				continue;
+
+			freq = acs_request_scan_add_freq_by_mode(iface, mode, freq);
+		}
+	} else {
+		freq = acs_request_scan_add_freq_by_mode(iface, iface->current_mode, freq);
 	}
+
 	*freq = 0;
 
 	if (params.freqs == freq) {
@@ -978,7 +1144,8 @@  enum hostapd_chan_status acs_init(struct hostapd_iface *iface)
 		return HOSTAPD_CHAN_ACS;
 	}
 
-	if (!iface->current_mode)
+	if ((!iface->current_mode) &&
+	    (iface->conf->hw_mode != HOSTAPD_MODE_IEEE80211ANY))
 		return HOSTAPD_CHAN_INVALID;
 
 	acs_cleanup(iface);
diff --git a/src/ap/drv_callbacks.c b/src/ap/drv_callbacks.c
index 8a4b0ef..4b3e4ea 100644
--- a/src/ap/drv_callbacks.c
+++ b/src/ap/drv_callbacks.c
@@ -1429,13 +1429,26 @@  static void hostapd_event_eapol_rx(struct hostapd_data *hapd, const u8 *src,
 static struct hostapd_channel_data * hostapd_get_mode_channel(
 	struct hostapd_iface *iface, unsigned int freq)
 {
-	int i;
+	int i, j;
 	struct hostapd_channel_data *chan;
+	struct hostapd_hw_modes *mode;
 
-	for (i = 0; i < iface->current_mode->num_channels; i++) {
-		chan = &iface->current_mode->channels[i];
-		if ((unsigned int) chan->freq == freq)
-			return chan;
+	if (iface->current_mode == NULL) {
+		for (i = 0; i < iface->num_hw_features; i++) {
+			mode = &iface->hw_features[i];
+
+			for (j = 0; j < mode->num_channels; j++) {
+				chan = &mode->channels[j];
+				if ((unsigned int) chan->freq == freq)
+					return chan;
+			}
+		}
+	} else {
+		for (i = 0; i < iface->current_mode->num_channels; i++) {
+			chan = &iface->current_mode->channels[i];
+			if ((unsigned int) chan->freq == freq)
+				return chan;
+		}
 	}
 
 	return NULL;
diff --git a/src/ap/hostapd.h b/src/ap/hostapd.h
index 2358d16..2b4d656 100644
--- a/src/ap/hostapd.h
+++ b/src/ap/hostapd.h
@@ -480,6 +480,7 @@  struct hostapd_iface {
 
 	struct hostapd_hw_modes *hw_features;
 	int num_hw_features;
+	Boolean hw_modes_has_11g;
 	struct hostapd_hw_modes *current_mode;
 	/* Rates that are currently used (i.e., filtered copy of
 	 * current_mode->channels */
diff --git a/src/ap/hw_features.c b/src/ap/hw_features.c
index ba10752..7ce1f74 100644
--- a/src/ap/hw_features.c
+++ b/src/ap/hw_features.c
@@ -859,8 +859,37 @@  static int hostapd_is_usable_chans(struct hostapd_iface *iface)
 	int secondary_freq;
 	struct hostapd_channel_data *pri_chan;
 
-	if (!iface->current_mode)
-		return 0;
+	if (!iface->current_mode) {
+		if (iface->conf->hw_mode == HOSTAPD_MODE_IEEE80211ANY) {
+			int i;
+			enum hostapd_hw_mode target_mode;
+
+			if (iface->freq < 4000)
+				if (iface->hw_modes_has_11g)
+					target_mode = HOSTAPD_MODE_IEEE80211G;
+				else
+					target_mode = HOSTAPD_MODE_IEEE80211B;
+			else if (iface->freq > 50000)
+				target_mode = HOSTAPD_MODE_IEEE80211AD;
+			else
+				target_mode = HOSTAPD_MODE_IEEE80211A;
+
+			for (i = 0; i < iface->num_hw_features; i++) {
+				struct hostapd_hw_modes *mode = &iface->hw_features[i];
+				if (mode->mode == target_mode) {
+					iface->current_mode = mode;
+					break;
+				}
+			}
+
+			if (iface->current_mode->mode == HOSTAPD_MODE_IEEE80211ANY) {
+				wpa_printf(MSG_ERROR, "ACS: cannot decide mode");
+				return 0;
+			}
+		} else 
+			return 0;
+	}
+	
 	pri_chan = hw_get_channel_freq(iface->current_mode->mode,
 				       iface->freq, NULL,
 				       iface->hw_features,
@@ -1031,10 +1060,21 @@  int hostapd_select_hw_mode(struct hostapd_iface *iface)
 		}
 	}
 
+	/* set if there is 11g already */
+	iface->hw_modes_has_11g = FALSE;
+	for (i = 0; i < iface->num_hw_features; i++) {
+		struct hostapd_hw_modes *mode = &iface->hw_features[i];
+
+		if (mode->mode == HOSTAPD_MODE_IEEE80211G) {
+			iface->hw_modes_has_11g = TRUE;
+			break;
+		}
+	}
+
 	if (iface->current_mode == NULL) {
-		if (!(iface->drv_flags & WPA_DRIVER_FLAGS_ACS_OFFLOAD) ||
-		    !(iface->drv_flags & WPA_DRIVER_FLAGS_SUPPORT_HW_MODE_ANY))
-		{
+		if ((!(iface->drv_flags & WPA_DRIVER_FLAGS_ACS_OFFLOAD) ||
+		     !(iface->drv_flags & WPA_DRIVER_FLAGS_SUPPORT_HW_MODE_ANY)) &&
+		    (iface->conf->hw_mode != HOSTAPD_MODE_IEEE80211ANY)) {
 			wpa_printf(MSG_ERROR,
 				   "Hardware does not support configured mode");
 			hostapd_logger(iface->bss[0], NULL,