diff --git a/src/ap/ctrl_iface_ap.c b/src/ap/ctrl_iface_ap.c index 976093342..ccbbab5b0 100644 --- a/src/ap/ctrl_iface_ap.c +++ b/src/ap/ctrl_iface_ap.c @@ -405,6 +405,7 @@ int hostapd_ctrl_iface_status(struct hostapd_data *hapd, char *buf, "num_sta_ht_no_gf=%d\n" "num_sta_no_ht=%d\n" "num_sta_ht_20_mhz=%d\n" + "num_sta_ht40_intolerant=%d\n" "olbc_ht=%d\n" "ht_op_mode=0x%x\n", hostapd_state_text(iface->state), @@ -417,6 +418,7 @@ int hostapd_ctrl_iface_status(struct hostapd_data *hapd, char *buf, iface->num_sta_ht_no_gf, iface->num_sta_no_ht, iface->num_sta_ht_20mhz, + iface->num_sta_ht40_intolerant, iface->olbc_ht, iface->ht_op_mode); if (ret < 0 || (size_t) ret >= buflen - len) diff --git a/src/ap/drv_callbacks.c b/src/ap/drv_callbacks.c index a8c24ebdf..b3cfade9f 100644 --- a/src/ap/drv_callbacks.c +++ b/src/ap/drv_callbacks.c @@ -9,6 +9,7 @@ #include "utils/includes.h" #include "utils/common.h" +#include "utils/eloop.h" #include "radius/radius.h" #include "drivers/driver.h" #include "common/ieee802_11_defs.h" @@ -30,6 +31,7 @@ #include "ap_config.h" #include "hw_features.h" #include "dfs.h" +#include "beacon.h" int hostapd_notif_assoc(struct hostapd_data *hapd, const u8 *addr, @@ -121,6 +123,24 @@ int hostapd_notif_assoc(struct hostapd_data *hapd, const u8 *addr, } #endif /* CONFIG_P2P */ +#ifdef CONFIG_IEEE80211N +#ifdef NEED_AP_MLME + if (elems.ht_capabilities && + elems.ht_capabilities_len >= + sizeof(struct ieee80211_ht_capabilities) && + (hapd->iface->conf->ht_capab & + HT_CAP_INFO_SUPP_CHANNEL_WIDTH_SET)) { + struct ieee80211_ht_capabilities *ht_cap = + (struct ieee80211_ht_capabilities *) + elems.ht_capabilities; + + if (le_to_host16(ht_cap->ht_capabilities_info) & + HT_CAP_INFO_40MHZ_INTOLERANT) + ht40_intolerant_add(hapd->iface, sta); + } +#endif /* NEED_AP_MLME */ +#endif /* CONFIG_IEEE80211N */ + #ifdef CONFIG_INTERWORKING if (elems.ext_capab && elems.ext_capab_len > 4) { if (elems.ext_capab[4] & 0x01) diff --git a/src/ap/hostapd.c b/src/ap/hostapd.c index 614a5bf40..33988598a 100644 --- a/src/ap/hostapd.c +++ b/src/ap/hostapd.c @@ -33,6 +33,7 @@ #include "p2p_hostapd.h" #include "gas_serv.h" #include "dfs.h" +#include "ieee802_11.h" static int hostapd_flush_old_stations(struct hostapd_data *hapd, u16 reason); @@ -1352,6 +1353,11 @@ void hostapd_interface_deinit(struct hostapd_iface *iface) if (iface == NULL) return; +#ifdef CONFIG_IEEE80211N +#ifdef NEED_AP_MLME + eloop_cancel_timeout(ap_ht2040_timeout, iface, NULL); +#endif /* NEED_AP_MLME */ +#endif /* CONFIG_IEEE80211N */ eloop_cancel_timeout(channel_list_update_timeout, iface, NULL); iface->wait_channel_update = 0; diff --git a/src/ap/hostapd.h b/src/ap/hostapd.h index 090544d5a..dba9bb29c 100644 --- a/src/ap/hostapd.h +++ b/src/ap/hostapd.h @@ -327,6 +327,9 @@ struct hostapd_iface { /* Number of HT associated stations 20 MHz */ int num_sta_ht_20mhz; + /* Number of HT40 intolerant stations */ + int num_sta_ht40_intolerant; + /* Overlapping BSS information */ int olbc_ht; @@ -351,6 +354,10 @@ struct hostapd_iface { unsigned int dfs_cac_ms; struct os_reltime dfs_cac_start; + /* Latched with the actual secondary channel information and will be + * used while juggling between HT20 and HT40 modes. */ + int secondary_ch; + #ifdef CONFIG_ACS unsigned int acs_num_completed_scans; #endif /* CONFIG_ACS */ diff --git a/src/ap/hw_features.c b/src/ap/hw_features.c index d47a366b8..43cb243b9 100644 --- a/src/ap/hw_features.c +++ b/src/ap/hw_features.c @@ -19,6 +19,8 @@ #include "ap_config.h" #include "ap_drv_ops.h" #include "acs.h" +#include "ieee802_11.h" +#include "beacon.h" #include "hw_features.h" @@ -414,6 +416,7 @@ static int ieee80211n_check_40mhz_2g4(struct hostapd_iface *iface, int pri = bss->freq; int sec = pri; int sec_chan, pri_chan; + struct ieee802_11_elems elems; ieee80211n_get_pri_sec_chan(bss, &pri_chan, &sec_chan); @@ -445,7 +448,23 @@ static int ieee80211n_check_40mhz_2g4(struct hostapd_iface *iface, } } - /* TODO: 40 MHz intolerant */ + ieee802_11_parse_elems((u8 *) (bss + 1), bss->ie_len, &elems, + 0); + if (elems.ht_capabilities && + elems.ht_capabilities_len >= + sizeof(struct ieee80211_ht_capabilities)) { + struct ieee80211_ht_capabilities *ht_cap = + (struct ieee80211_ht_capabilities *) + elems.ht_capabilities; + + if (le_to_host16(ht_cap->ht_capabilities_info) & + HT_CAP_INFO_40MHZ_INTOLERANT) { + wpa_printf(MSG_DEBUG, + "40 MHz Intolerant is set on channel %d in BSS " + MACSTR, pri, MAC2STR(bss->bssid)); + return 0; + } + } } return 1; @@ -475,6 +494,7 @@ static void ieee80211n_check_scan(struct hostapd_iface *iface) oper40 = ieee80211n_check_40mhz_2g4(iface, scan_res); wpa_scan_results_free(scan_res); + iface->secondary_ch = iface->conf->secondary_channel; if (!oper40) { wpa_printf(MSG_INFO, "20/40 MHz operation not permitted on " "channel pri=%d sec=%d based on overlapping BSSes", @@ -482,9 +502,21 @@ static void ieee80211n_check_scan(struct hostapd_iface *iface) iface->conf->channel + iface->conf->secondary_channel * 4); iface->conf->secondary_channel = 0; + if (iface->drv_flags & WPA_DRIVER_FLAGS_HT_2040_COEX) { + /* + * TODO: Could consider scheduling another scan to check + * if channel width can be changed if no coex reports + * are received from associating stations. + */ + } } res = ieee80211n_allowed_ht40_channel_pair(iface); + if (!res) { + iface->conf->secondary_channel = 0; + wpa_printf(MSG_INFO, "Fallback to 20 MHz"); + } + hostapd_setup_interface_complete(iface, !res); } diff --git a/src/ap/ieee802_11.h b/src/ap/ieee802_11.h index 809b4ca6c..5b9437151 100644 --- a/src/ap/ieee802_11.h +++ b/src/ap/ieee802_11.h @@ -39,6 +39,7 @@ static inline int ieee802_11_get_mib_sta(struct hostapd_data *hapd, #endif /* NEED_AP_MLME */ u16 hostapd_own_capab_info(struct hostapd_data *hapd, struct sta_info *sta, int probe); +void ap_ht2040_timeout(void *eloop_data, void *user_data); u8 * hostapd_eid_ext_capab(struct hostapd_data *hapd, u8 *eid); u8 * hostapd_eid_qos_map_set(struct hostapd_data *hapd, u8 *eid); u8 * hostapd_eid_supp_rates(struct hostapd_data *hapd, u8 *eid); @@ -59,6 +60,8 @@ void hostapd_get_vht_capab(struct hostapd_data *hapd, u16 copy_sta_ht_capab(struct hostapd_data *hapd, struct sta_info *sta, const u8 *ht_capab, size_t ht_capab_len); void update_ht_state(struct hostapd_data *hapd, struct sta_info *sta); +void ht40_intolerant_add(struct hostapd_iface *iface, struct sta_info *sta); +void ht40_intolerant_remove(struct hostapd_iface *iface, struct sta_info *sta); u16 copy_sta_vht_capab(struct hostapd_data *hapd, struct sta_info *sta, const u8 *vht_capab, size_t vht_capab_len); u16 set_sta_vht_opmode(struct hostapd_data *hapd, struct sta_info *sta, diff --git a/src/ap/ieee802_11_ht.c b/src/ap/ieee802_11_ht.c index 1d64748fc..38caaed27 100644 --- a/src/ap/ieee802_11_ht.c +++ b/src/ap/ieee802_11_ht.c @@ -10,12 +10,15 @@ #include "utils/includes.h" #include "utils/common.h" +#include "utils/eloop.h" #include "common/ieee802_11_defs.h" #include "hostapd.h" #include "ap_config.h" #include "sta_info.h" #include "beacon.h" #include "ieee802_11.h" +#include "hw_features.h" +#include "ap_drv_ops.h" u8 * hostapd_eid_ht_capabilities(struct hostapd_data *hapd, u8 *eid) @@ -200,6 +203,52 @@ u16 copy_sta_ht_capab(struct hostapd_data *hapd, struct sta_info *sta, } +void ht40_intolerant_add(struct hostapd_iface *iface, struct sta_info *sta) +{ + if (iface->current_mode->mode != HOSTAPD_MODE_IEEE80211G) + return; + + wpa_printf(MSG_INFO, "HT: Forty MHz Intolerant is set by STA " MACSTR + " in Association Request", MAC2STR(sta->addr)); + + if (sta->ht40_intolerant_set) + return; + + sta->ht40_intolerant_set = 1; + iface->num_sta_ht40_intolerant++; + eloop_cancel_timeout(ap_ht2040_timeout, iface, NULL); + + if (iface->conf->secondary_channel && + (iface->drv_flags & WPA_DRIVER_FLAGS_HT_2040_COEX)) { + iface->conf->secondary_channel = 0; + ieee802_11_set_beacons(iface); + } +} + + +void ht40_intolerant_remove(struct hostapd_iface *iface, struct sta_info *sta) +{ + if (!sta->ht40_intolerant_set) + return; + + sta->ht40_intolerant_set = 0; + iface->num_sta_ht40_intolerant--; + + if (iface->num_sta_ht40_intolerant == 0 && + (iface->conf->ht_capab & HT_CAP_INFO_SUPP_CHANNEL_WIDTH_SET) && + (iface->drv_flags & WPA_DRIVER_FLAGS_HT_2040_COEX)) { + unsigned int delay_time = OVERLAPPING_BSS_TRANS_DELAY_FACTOR * + iface->conf->obss_interval; + wpa_printf(MSG_DEBUG, + "HT: Start 20->40 MHz transition timer (%d seconds)", + delay_time); + eloop_cancel_timeout(ap_ht2040_timeout, iface, NULL); + eloop_register_timeout(delay_time, 0, ap_ht2040_timeout, + iface, NULL); + } +} + + static void update_sta_ht(struct hostapd_data *hapd, struct sta_info *sta) { u16 ht_capab; @@ -227,6 +276,9 @@ static void update_sta_ht(struct hostapd_data *hapd, struct sta_info *sta) __func__, MAC2STR(sta->addr), hapd->iface->num_sta_ht_20mhz); } + + if (ht_capab & HT_CAP_INFO_40MHZ_INTOLERANT) + ht40_intolerant_add(hapd->iface, sta); } @@ -288,3 +340,14 @@ void hostapd_get_ht_capab(struct hostapd_data *hapd, neg_ht_cap->ht_capabilities_info = host_to_le16(cap); } + + +void ap_ht2040_timeout(void *eloop_data, void *user_data) +{ + struct hostapd_iface *iface = eloop_data; + + wpa_printf(MSG_INFO, "Switching to 40 MHz operation"); + + iface->conf->secondary_channel = iface->secondary_ch; + ieee802_11_set_beacons(iface); +} diff --git a/src/ap/sta_info.c b/src/ap/sta_info.c index f5417de0c..60f07682e 100644 --- a/src/ap/sta_info.c +++ b/src/ap/sta_info.c @@ -206,6 +206,10 @@ void ap_free_sta(struct hostapd_data *hapd, struct sta_info *sta) hapd->iface->num_sta_ht_20mhz--; } +#ifdef CONFIG_IEEE80211N + ht40_intolerant_remove(hapd->iface, sta); +#endif /* CONFIG_IEEE80211N */ + #ifdef CONFIG_P2P if (sta->no_p2p_set) { sta->no_p2p_set = 0; diff --git a/src/ap/sta_info.h b/src/ap/sta_info.h index 2dbdeb181..03db98f66 100644 --- a/src/ap/sta_info.h +++ b/src/ap/sta_info.h @@ -54,6 +54,7 @@ struct sta_info { unsigned int no_short_preamble_set:1; unsigned int no_ht_gf_set:1; unsigned int no_ht_set:1; + unsigned int ht40_intolerant_set:1; unsigned int ht_20mhz_set:1; unsigned int no_p2p_set:1; unsigned int qos_map_enabled:1; diff --git a/src/common/ieee802_11_defs.h b/src/common/ieee802_11_defs.h index cb70130d3..8fe2e4a67 100644 --- a/src/common/ieee802_11_defs.h +++ b/src/common/ieee802_11_defs.h @@ -646,6 +646,7 @@ struct ieee80211_vht_operation { #define ERP_INFO_USE_PROTECTION BIT(1) #define ERP_INFO_BARKER_PREAMBLE_MODE BIT(2) +#define OVERLAPPING_BSS_TRANS_DELAY_FACTOR 5 /* HT Capabilities Info field within HT Capabilities element */ #define HT_CAP_INFO_LDPC_CODING_CAP ((u16) BIT(0))