nl80211: Handle CH_SWITCH event
authorThomas Pedersen <c_tpeder@qca.qualcomm.com>
Mon, 25 Jun 2012 11:45:14 +0000 (14:45 +0300)
committerJouni Malinen <j@w1.fi>
Mon, 25 Jun 2012 11:45:14 +0000 (14:45 +0300)
Some drivers may independently decide to switch channels. Handle this by
updating the hostapd and wpa_supplicant AP and GO configuration.

Signed-hostap: Thomas Pedersen <c_tpeder@qca.qualcomm.com>

src/ap/drv_callbacks.c
src/ap/hostapd.h
src/drivers/driver.h
src/drivers/driver_common.c
src/drivers/driver_nl80211.c
wpa_supplicant/ap.c
wpa_supplicant/ap.h
wpa_supplicant/events.c

index 4c0d0ab..7c2e87e 100644 (file)
@@ -26,6 +26,7 @@
 #include "wps_hostapd.h"
 #include "ap_drv_ops.h"
 #include "ap_config.h"
+#include "hw_features.h"
 
 
 int hostapd_notif_assoc(struct hostapd_data *hapd, const u8 *addr,
@@ -273,6 +274,31 @@ void hostapd_event_sta_low_ack(struct hostapd_data *hapd, const u8 *addr)
 }
 
 
+void hostapd_event_ch_switch(struct hostapd_data *hapd, int freq, int ht,
+                            int offset)
+{
+       int channel;
+
+       hostapd_logger(hapd, NULL, HOSTAPD_MODULE_IEEE80211,
+                      HOSTAPD_LEVEL_INFO, "driver had channel switch: "
+                      "freq=%d, ht=%d, offset=%d", freq, ht, offset);
+
+       hapd->iface->freq = freq;
+
+       channel = hostapd_hw_get_channel(hapd, freq);
+       if (!channel) {
+               hostapd_logger(hapd, NULL, HOSTAPD_MODULE_IEEE80211,
+                              HOSTAPD_LEVEL_WARNING, "driver switched to "
+                              "bad channel!");
+               return;
+       }
+
+       hapd->iconf->channel = channel;
+       hapd->iconf->ieee80211n = ht;
+       hapd->iconf->secondary_channel = offset;
+}
+
+
 int hostapd_probe_req_rx(struct hostapd_data *hapd, const u8 *sa, const u8 *da,
                         const u8 *bssid, const u8 *ie, size_t ie_len,
                         int ssi_signal)
@@ -590,6 +616,13 @@ void wpa_supplicant_event(void *ctx, enum wpa_event_type event,
                hostapd_rx_action(hapd, &data->rx_action);
                break;
 #endif /* NEED_AP_MLME */
+       case EVENT_CH_SWITCH:
+               if (!data)
+                       break;
+               hostapd_event_ch_switch(hapd, data->ch_switch.freq,
+                                       data->ch_switch.ht_enabled,
+                                       data->ch_switch.ch_offset);
+               break;
        default:
                wpa_printf(MSG_DEBUG, "Unknown event %d", event);
                break;
index 4e45d59..f7ed311 100644 (file)
@@ -283,5 +283,7 @@ void hostapd_event_sta_low_ack(struct hostapd_data *hapd, const u8 *addr);
 int hostapd_probe_req_rx(struct hostapd_data *hapd, const u8 *sa, const u8 *da,
                         const u8 *bssid, const u8 *ie, size_t ie_len,
                         int ssi_signal);
+void hostapd_event_ch_switch(struct hostapd_data *hapd, int freq, int ht,
+                            int offset);
 
 #endif /* HOSTAPD_H */
index 102ea46..9ed52ea 100644 (file)
@@ -2978,7 +2978,14 @@ enum wpa_event_type {
        /**
         * EVENT_EAPOL_TX_STATUS - notify of EAPOL TX status
         */
-       EVENT_EAPOL_TX_STATUS
+       EVENT_EAPOL_TX_STATUS,
+
+       /**
+        * EVENT_CH_SWITCH - AP or GO decided to switch channels
+        *
+        * Described in wpa_event_data.ch_switch
+        * */
+       EVENT_CH_SWITCH
 };
 
 
@@ -3573,6 +3580,18 @@ union wpa_event_data {
                int data_len;
                int ack;
        } eapol_tx_status;
+
+       /**
+        * struct ch_switch
+        * @freq: Frequency of new channel in MHz
+        * @ht_enabled: Whether this is an HT channel
+        * @ch_offset: Secondary channel offset
+        */
+       struct ch_switch {
+               int freq;
+               int ht_enabled;
+               int ch_offset;
+       } ch_switch;
 };
 
 /**
index 345e851..81856aa 100644 (file)
@@ -77,6 +77,7 @@ const char * event_to_string(enum wpa_event_type event)
        E2S(SCHED_SCAN_STOPPED);
        E2S(DRIVER_CLIENT_POLL_OK);
        E2S(EAPOL_TX_STATUS);
+       E2S(CH_SWITCH);
        }
 
        return "UNKNOWN";
index b403792..376f363 100644 (file)
@@ -1196,6 +1196,40 @@ static void mlme_event_disconnect(struct wpa_driver_nl80211_data *drv,
 }
 
 
+static void mlme_event_ch_switch(struct wpa_driver_nl80211_data *drv,
+                                struct nlattr *freq, struct nlattr *type)
+{
+       union wpa_event_data data;
+       int ht_enabled = 1;
+       int chan_offset = 0;
+
+       wpa_printf(MSG_DEBUG, "nl80211: Channel switch event");
+
+       if (!freq || !type)
+               return;
+
+       switch (nla_get_u32(type)) {
+       case NL80211_CHAN_NO_HT:
+               ht_enabled = 0;
+               break;
+       case NL80211_CHAN_HT20:
+               break;
+       case NL80211_CHAN_HT40PLUS:
+               chan_offset = 1;
+               break;
+       case NL80211_CHAN_HT40MINUS:
+               chan_offset = -1;
+               break;
+       }
+
+       data.ch_switch.freq = nla_get_u32(freq);
+       data.ch_switch.ht_enabled = ht_enabled;
+       data.ch_switch.ch_offset = chan_offset;
+
+       wpa_supplicant_event(drv->ctx, EVENT_CH_SWITCH, &data);
+}
+
+
 static void mlme_timeout_event(struct wpa_driver_nl80211_data *drv,
                               enum nl80211_commands cmd, struct nlattr *addr)
 {
@@ -2116,6 +2150,10 @@ static void do_process_drv_event(struct wpa_driver_nl80211_data *drv,
                                   tb[NL80211_ATTR_REQ_IE],
                                   tb[NL80211_ATTR_RESP_IE]);
                break;
+       case NL80211_CMD_CH_SWITCH_NOTIFY:
+               mlme_event_ch_switch(drv, tb[NL80211_ATTR_WIPHY_FREQ],
+                                    tb[NL80211_ATTR_WIPHY_CHANNEL_TYPE]);
+               break;
        case NL80211_CMD_DISCONNECT:
                mlme_event_disconnect(drv, tb[NL80211_ATTR_REASON_CODE],
                                      tb[NL80211_ATTR_MAC],
index 7e6441e..f9e0045 100644 (file)
@@ -957,6 +957,17 @@ int wpa_supplicant_ap_update_beacon(struct wpa_supplicant *wpa_s)
 }
 
 
+void wpas_ap_ch_switch(struct wpa_supplicant *wpa_s, int freq, int ht,
+                      int offset)
+{
+       if (!wpa_s->ap_iface)
+               return;
+
+       wpa_s->assoc_freq = freq;
+       hostapd_event_ch_switch(wpa_s->ap_iface->bss[0], freq, ht, offset);
+}
+
+
 int wpa_supplicant_ap_mac_addr_filter(struct wpa_supplicant *wpa_s,
                                      const u8 *addr)
 {
index 91ab423..bc953d9 100644 (file)
@@ -49,5 +49,7 @@ int wpa_supplicant_ap_update_beacon(struct wpa_supplicant *wpa_s);
 int wpa_supplicant_ap_mac_addr_filter(struct wpa_supplicant *wpa_s,
                                      const u8 *addr);
 void wpa_supplicant_ap_pwd_auth_fail(struct wpa_supplicant *wpa_s);
+void wpas_ap_ch_switch(struct wpa_supplicant *wpa_s, int freq, int ht,
+                      int offset);
 
 #endif /* AP_H */
index e7dfa4e..19c8280 100644 (file)
@@ -2407,6 +2407,21 @@ void wpa_supplicant_event(void *ctx, enum wpa_event_type event,
                ap_rx_from_unknown_sta(wpa_s, data->rx_from_unknown.addr,
                                       data->rx_from_unknown.wds);
                break;
+       case EVENT_CH_SWITCH:
+               if (!data)
+                       break;
+               if (!wpa_s->ap_iface) {
+                       wpa_dbg(wpa_s, MSG_DEBUG, "AP: Ignore channel switch "
+                               "event in non-AP mode");
+                       break;
+               }
+
+#ifdef CONFIG_AP
+               wpas_ap_ch_switch(wpa_s, data->ch_switch.freq,
+                                 data->ch_switch.ht_enabled,
+                                 data->ch_switch.ch_offset);
+#endif /* CONFIG_AP */
+               break;
        case EVENT_RX_MGMT: {
                u16 fc, stype;
                const struct ieee80211_mgmt *mgmt;