diff --git a/wpa_supplicant/config.c b/wpa_supplicant/config.c index 4841646f3..b1adab77b 100644 --- a/wpa_supplicant/config.c +++ b/wpa_supplicant/config.c @@ -3522,6 +3522,7 @@ struct wpa_config * wpa_config_alloc_empty(const char *ctrl_interface, config->fast_reauth = DEFAULT_FAST_REAUTH; config->p2p_go_intent = DEFAULT_P2P_GO_INTENT; config->p2p_intra_bss = DEFAULT_P2P_INTRA_BSS; + config->p2p_go_freq_change_policy = DEFAULT_P2P_GO_FREQ_MOVE; config->p2p_go_max_inactivity = DEFAULT_P2P_GO_MAX_INACTIVITY; config->p2p_optimize_listen_chan = DEFAULT_P2P_OPTIMIZE_LISTEN_CHAN; config->p2p_go_ctwindow = DEFAULT_P2P_GO_CTWINDOW; @@ -4176,6 +4177,7 @@ static const struct global_parse_data global_fields[] = { { INT_RANGE(persistent_reconnect, 0, 1), 0 }, { INT_RANGE(p2p_intra_bss, 0, 1), CFG_CHANGED_P2P_INTRA_BSS }, { INT(p2p_group_idle), 0 }, + { INT_RANGE(p2p_go_freq_change_policy, 0, P2P_GO_FREQ_MOVE_MAX), 0 }, { INT_RANGE(p2p_passphrase_len, 8, 63), CFG_CHANGED_P2P_PASSPHRASE_LEN }, { FUNC(p2p_pref_chan), CFG_CHANGED_P2P_PREF_CHAN }, diff --git a/wpa_supplicant/config.h b/wpa_supplicant/config.h index 0bc8fecc9..d42788348 100644 --- a/wpa_supplicant/config.h +++ b/wpa_supplicant/config.h @@ -738,6 +738,34 @@ struct wpa_config { */ int p2p_group_idle; + /** + * p2p_go_freq_change_policy - The GO frequency change policy + * + * This controls the behavior of the GO when there is a change in the + * map of the currently used frequencies in case more than one channel + * is supported. + * + * @P2P_GO_FREQ_MOVE_SCM: Prefer working in a single channel mode if + * possible. In case the GO is the only interface using its frequency + * and there are other station interfaces on other frequencies, the GO + * will migrate to one of these frequencies. + * + * @P2P_GO_FREQ_MOVE_SCM_PEER_SUPPORTS: Same as P2P_GO_FREQ_MOVE_SCM, + * but a transition is possible only in case one of the other used + * frequencies is one of the frequencies in the intersection of the + * frequency list of the local device and the peer device. + * + * @P2P_GO_FREQ_MOVE_STAY: Prefer to stay on the current frequency. + */ + enum { + P2P_GO_FREQ_MOVE_SCM = 0, + P2P_GO_FREQ_MOVE_SCM_PEER_SUPPORTS = 1, + P2P_GO_FREQ_MOVE_STAY = 2, + P2P_GO_FREQ_MOVE_MAX = P2P_GO_FREQ_MOVE_STAY, + } p2p_go_freq_change_policy; + +#define DEFAULT_P2P_GO_FREQ_MOVE P2P_GO_FREQ_MOVE_STAY + /** * p2p_passphrase_len - Passphrase length (8..63) for P2P GO * diff --git a/wpa_supplicant/config_file.c b/wpa_supplicant/config_file.c index 5bab821a6..2af187dd4 100644 --- a/wpa_supplicant/config_file.c +++ b/wpa_supplicant/config_file.c @@ -1133,6 +1133,9 @@ static void wpa_config_write_global(FILE *f, struct wpa_config *config) config->p2p_ignore_shared_freq); if (config->p2p_cli_probe) fprintf(f, "p2p_cli_probe=%d\n", config->p2p_cli_probe); + if (config->p2p_go_freq_change_policy != DEFAULT_P2P_GO_FREQ_MOVE) + fprintf(f, "p2p_go_freq_change_policy=%u\n", + config->p2p_go_freq_change_policy); #endif /* CONFIG_P2P */ if (config->country[0] && config->country[1]) { fprintf(f, "country=%c%c\n", diff --git a/wpa_supplicant/p2p_supplicant.c b/wpa_supplicant/p2p_supplicant.c index 04db85697..9c256a5b4 100644 --- a/wpa_supplicant/p2p_supplicant.c +++ b/wpa_supplicant/p2p_supplicant.c @@ -47,6 +47,12 @@ #define P2P_AUTO_PD_SCAN_ATTEMPTS 5 +/** + * Defines time interval in seconds when a GO needs to evacuate a frequency that + * it is currently using, but is no longer valid for P2P use cases. + */ +#define P2P_GO_FREQ_CHANGE_TIME 5 + #ifndef P2P_MAX_CLIENT_IDLE /* * How many seconds to try to reconnect to the GO when connection in P2P client @@ -94,7 +100,8 @@ enum p2p_group_removal_reason { P2P_GROUP_REMOVAL_UNAVAILABLE, P2P_GROUP_REMOVAL_GO_ENDING_SESSION, P2P_GROUP_REMOVAL_PSK_FAILURE, - P2P_GROUP_REMOVAL_FREQ_CONFLICT + P2P_GROUP_REMOVAL_FREQ_CONFLICT, + P2P_GROUP_REMOVAL_GO_LEAVE_CHANNEL }; @@ -128,6 +135,7 @@ static int wpas_p2p_add_group_interface(struct wpa_supplicant *wpa_s, enum wpa_driver_if_type type); static void wpas_p2p_group_formation_failed(struct wpa_supplicant *wpa_s, int already_deleted); +static void wpas_p2p_move_go(void *eloop_ctx, void *timeout_ctx); /* @@ -876,6 +884,7 @@ static int wpas_p2p_group_delete(struct wpa_supplicant *wpa_s, } wpa_s->p2p_in_invitation = 0; + eloop_cancel_timeout(wpas_p2p_move_go, wpa_s, NULL); /* * Make sure wait for the first client does not remain active after the @@ -1866,6 +1875,7 @@ static void wpas_p2p_clone_config(struct wpa_supplicant *dst, d->num_sec_device_types = s->num_sec_device_types; d->p2p_group_idle = s->p2p_group_idle; + d->p2p_go_freq_change_policy = s->p2p_go_freq_change_policy; d->p2p_intra_bss = s->p2p_intra_bss; d->persistent_reconnect = s->persistent_reconnect; d->max_num_sta = s->max_num_sta; @@ -2596,6 +2606,28 @@ static int freq_included(const struct p2p_channels *channels, unsigned int freq) } +/* + * Check if the given frequency is one of the possible operating frequencies + * set after the completion of the GO Negotiation. + */ +static int wpas_p2p_go_is_peer_freq(struct wpa_supplicant *wpa_s, int freq) +{ + unsigned int i; + + p2p_go_dump_common_freqs(wpa_s); + + /* assume no restrictions */ + if (!wpa_s->p2p_group_common_freqs_num) + return 1; + + for (i = 0; i < wpa_s->p2p_group_common_freqs_num; i++) { + if (wpa_s->p2p_group_common_freqs[i] == freq) + return 1; + } + return 0; +} + + /** * Pick the best frequency to use from all the currently used frequencies. */ @@ -8249,6 +8281,181 @@ static void wpas_p2p_optimize_listen_channel(struct wpa_supplicant *wpa_s, } +static int wpas_p2p_move_go_csa(struct wpa_supplicant *wpa_s) +{ + if (!(wpa_s->drv_flags & WPA_DRIVER_FLAGS_AP_CSA)) { + wpa_dbg(wpa_s, MSG_DEBUG, "CSA is not enabled"); + return -1; + } + + /* TODO: Add CSA support */ + wpa_dbg(wpa_s, MSG_DEBUG, "Moving GO with CSA is not implemented"); + return -1; +} + + +static void wpas_p2p_move_go_no_csa(struct wpa_supplicant *wpa_s) +{ + struct p2p_go_neg_results params; + struct wpa_ssid *current_ssid = wpa_s->current_ssid; + + wpa_msg_global(wpa_s, MSG_INFO, P2P_EVENT_REMOVE_AND_REFORM_GROUP); + + wpa_dbg(wpa_s, MSG_DEBUG, "P2P: Move GO from freq=%d MHz", + current_ssid->frequency); + + /* Stop the AP functionality */ + /* TODO: Should do this in a way that does not indicated to possible + * P2P Clients in the group that the group is terminated. */ + wpa_supplicant_ap_deinit(wpa_s); + + /* Reselect the GO frequency */ + if (wpas_p2p_init_go_params(wpa_s, ¶ms, 0, 0, 0, NULL)) { + wpa_dbg(wpa_s, MSG_DEBUG, "P2P: Failed to reselect freq"); + wpas_p2p_group_delete(wpa_s, + P2P_GROUP_REMOVAL_GO_LEAVE_CHANNEL); + return; + } + wpa_dbg(wpa_s, MSG_DEBUG, "P2P: New freq selected for the GO (%u MHz)", + params.freq); + + if (params.freq && + !p2p_supported_freq_go(wpa_s->global->p2p, params.freq)) { + wpa_printf(MSG_DEBUG, + "P2P: Selected freq (%u MHz) is not valid for P2P", + params.freq); + wpas_p2p_group_delete(wpa_s, + P2P_GROUP_REMOVAL_GO_LEAVE_CHANNEL); + return; + } + + /* Update the frequency */ + current_ssid->frequency = params.freq; + wpa_s->connect_without_scan = current_ssid; + wpa_s->reassociate = 1; + wpa_s->disconnected = 0; + wpa_supplicant_req_scan(wpa_s, 0, 0); +} + + +static void wpas_p2p_move_go(void *eloop_ctx, void *timeout_ctx) +{ + struct wpa_supplicant *wpa_s = eloop_ctx; + + if (!wpa_s->ap_iface || !wpa_s->current_ssid) + return; + + /* + * First, try a channel switch flow. If it is not supported or fails, + * take down the GO and bring it up again. + */ + if (wpas_p2p_move_go_csa(wpa_s) < 0) + wpas_p2p_move_go_no_csa(wpa_s); +} + + +/* + * Consider moving a GO from its currently used frequency: + * 1. It is possible that due to regulatory consideration the frequency + * can no longer be used and there is a need to evacuate the GO. + * 2. It is possible that due to MCC considerations, it would be preferable + * to move the GO to a channel that is currently used by some other + * station interface. + * + * In case a frequency that became invalid is once again valid, cancel a + * previously initiated GO frequency change. + */ +static void wpas_p2p_consider_moving_one_go(struct wpa_supplicant *wpa_s, + struct wpa_used_freq_data *freqs, + unsigned int num) +{ + unsigned int i, invalid_freq = 0, policy_move = 0, flags = 0; + unsigned int timeout; + int freq; + + freq = wpa_s->current_ssid->frequency; + for (i = 0, invalid_freq = 0; i < num; i++) { + if (freqs[i].freq == freq) { + flags = freqs[i].flags; + + /* The channel is invalid, must change it */ + if (!p2p_supported_freq_go(wpa_s->global->p2p, freq)) { + wpa_dbg(wpa_s, MSG_DEBUG, + "P2P: Freq=%d MHz no longer valid for GO", + freq); + invalid_freq = 1; + } + } else if (freqs[i].flags == 0) { + /* Freq is not used by any other station interface */ + continue; + } else if (!p2p_supported_freq(wpa_s->global->p2p, + freqs[i].freq)) { + /* Freq is not valid for P2P use cases */ + continue; + } else if (wpa_s->conf->p2p_go_freq_change_policy == + P2P_GO_FREQ_MOVE_SCM) { + policy_move = 1; + } else if (wpa_s->conf->p2p_go_freq_change_policy == + P2P_GO_FREQ_MOVE_SCM_PEER_SUPPORTS && + wpas_p2p_go_is_peer_freq(wpa_s, freqs[i].freq)) { + policy_move = 1; + } + } + + wpa_dbg(wpa_s, MSG_DEBUG, + "P2P: GO move: invalid_freq=%u, policy_move=%u, flags=0x%X", + invalid_freq, policy_move, flags); + + /* + * The channel is valid, or we are going to have a policy move, so + * cancel timeout. + */ + if (!invalid_freq || policy_move) { + wpa_dbg(wpa_s, MSG_DEBUG, + "P2P: Cancel a GO move from freq=%d MHz", freq); + eloop_cancel_timeout(wpas_p2p_move_go, wpa_s, NULL); + } + + if (!invalid_freq && (!policy_move || flags != 0)) { + wpa_dbg(wpa_s, MSG_DEBUG, + "P2P: Not initiating a GO frequency change"); + return; + } + + if (invalid_freq && !wpas_p2p_disallowed_freq(wpa_s->global, freq)) + timeout = P2P_GO_FREQ_CHANGE_TIME; + else + timeout = 0; + + wpa_dbg(wpa_s, MSG_DEBUG, "P2P: Move GO from freq=%d MHz in %d secs", + freq, timeout); + eloop_cancel_timeout(wpas_p2p_move_go, wpa_s, NULL); + eloop_register_timeout(timeout, 0, wpas_p2p_move_go, wpa_s, NULL); +} + + +static void wpas_p2p_consider_moving_gos(struct wpa_supplicant *wpa_s, + struct wpa_used_freq_data *freqs, + unsigned int num) +{ + struct wpa_supplicant *ifs; + + /* + * Travers all the radio interfaces, and for each GO interface, check + * if there is a need to move the GO from the frequency it is using, + * or in case the frequency is valid again, cancel the evacuation flow. + */ + dl_list_for_each(ifs, &wpa_s->radio->ifaces, struct wpa_supplicant, + radio_list) { + if (ifs->current_ssid == NULL || + ifs->current_ssid->mode != WPAS_MODE_P2P_GO) + continue; + + wpas_p2p_consider_moving_one_go(ifs, freqs, num); + } +} + + void wpas_p2p_indicate_state_change(struct wpa_supplicant *wpa_s) { struct wpa_used_freq_data *freqs; @@ -8274,6 +8481,16 @@ void wpas_p2p_indicate_state_change(struct wpa_supplicant *wpa_s) num = get_shared_radio_freqs_data(wpa_s, freqs, num); wpas_p2p_optimize_listen_channel(wpa_s, freqs, num); + + /* + * The used frequencies map changed, so it is possible that a GO is + * using a channel that is no longer valid for P2P use. It is also + * possible that due to policy consideration, it would be preferable to + * move the group to a frequency already used by other station + * interfaces. + */ + wpas_p2p_consider_moving_gos(wpa_s, freqs, num); + os_free(freqs); }