+/**
+ * p2p_reselect_channel - Re-select operating channel based on peer information
+ * @p2p: P2P module context from p2p_init()
+ * @intersection: Support channel list intersection from local and peer
+ *
+ * This function is used to re-select the best channel after having received
+ * information from the peer to allow supported channel lists to be intersected.
+ * This can be used to improve initial channel selection done in
+ * p2p_prepare_channel() prior to the start of GO Negotiation. In addition, this
+ * can be used for Invitation case.
+ */
+void p2p_reselect_channel(struct p2p_data *p2p,
+ struct p2p_channels *intersection)
+{
+ struct p2p_reg_class *cl;
+ int freq;
+ u8 op_reg_class, op_channel;
+ unsigned int i;
+ const int op_classes_5ghz[] = { 124, 125, 115, 0 };
+ const int op_classes_ht40[] = { 126, 127, 116, 117, 0 };
+ const int op_classes_vht[] = { 128, 129, 130, 0 };
+
+ if (p2p->own_freq_preference > 0 &&
+ p2p_freq_to_channel(p2p->own_freq_preference,
+ &op_reg_class, &op_channel) == 0 &&
+ p2p_channels_includes(intersection, op_reg_class, op_channel)) {
+ p2p_dbg(p2p, "Pick own channel preference (reg_class %u channel %u) from intersection",
+ op_reg_class, op_channel);
+ p2p->op_reg_class = op_reg_class;
+ p2p->op_channel = op_channel;
+ return;
+ }
+
+ if (p2p->best_freq_overall > 0 &&
+ p2p_freq_to_channel(p2p->best_freq_overall,
+ &op_reg_class, &op_channel) == 0 &&
+ p2p_channels_includes(intersection, op_reg_class, op_channel)) {
+ p2p_dbg(p2p, "Pick best overall channel (reg_class %u channel %u) from intersection",
+ op_reg_class, op_channel);
+ p2p->op_reg_class = op_reg_class;
+ p2p->op_channel = op_channel;
+ return;
+ }
+
+ /* First, try to pick the best channel from another band */
+ freq = p2p_channel_to_freq(p2p->op_reg_class, p2p->op_channel);
+ if (freq >= 2400 && freq < 2500 && p2p->best_freq_5 > 0 &&
+ !p2p_channels_includes(intersection, p2p->op_reg_class,
+ p2p->op_channel) &&
+ p2p_freq_to_channel(p2p->best_freq_5,
+ &op_reg_class, &op_channel) == 0 &&
+ p2p_channels_includes(intersection, op_reg_class, op_channel)) {
+ p2p_dbg(p2p, "Pick best 5 GHz channel (reg_class %u channel %u) from intersection",
+ op_reg_class, op_channel);
+ p2p->op_reg_class = op_reg_class;
+ p2p->op_channel = op_channel;
+ return;
+ }
+
+ if (freq >= 4900 && freq < 6000 && p2p->best_freq_24 > 0 &&
+ !p2p_channels_includes(intersection, p2p->op_reg_class,
+ p2p->op_channel) &&
+ p2p_freq_to_channel(p2p->best_freq_24,
+ &op_reg_class, &op_channel) == 0 &&
+ p2p_channels_includes(intersection, op_reg_class, op_channel)) {
+ p2p_dbg(p2p, "Pick best 2.4 GHz channel (reg_class %u channel %u) from intersection",
+ op_reg_class, op_channel);
+ p2p->op_reg_class = op_reg_class;
+ p2p->op_channel = op_channel;
+ return;
+ }
+
+ /* Select channel with highest preference if the peer supports it */
+ for (i = 0; p2p->cfg->pref_chan && i < p2p->cfg->num_pref_chan; i++) {
+ if (p2p_channels_includes(intersection,
+ p2p->cfg->pref_chan[i].op_class,
+ p2p->cfg->pref_chan[i].chan)) {
+ p2p->op_reg_class = p2p->cfg->pref_chan[i].op_class;
+ p2p->op_channel = p2p->cfg->pref_chan[i].chan;
+ p2p_dbg(p2p, "Pick highest preferred channel (op_class %u channel %u) from intersection",
+ p2p->op_reg_class, p2p->op_channel);
+ return;
+ }
+ }
+
+ /* Try a channel where we might be able to use VHT */
+ if (p2p_channel_select(intersection, op_classes_vht,
+ &p2p->op_reg_class, &p2p->op_channel) == 0) {
+ p2p_dbg(p2p, "Pick possible VHT channel (op_class %u channel %u) from intersection",
+ p2p->op_reg_class, p2p->op_channel);
+ return;
+ }
+
+ /* Try a channel where we might be able to use HT40 */
+ if (p2p_channel_select(intersection, op_classes_ht40,
+ &p2p->op_reg_class, &p2p->op_channel) == 0) {
+ p2p_dbg(p2p, "Pick possible HT40 channel (op_class %u channel %u) from intersection",
+ p2p->op_reg_class, p2p->op_channel);
+ return;
+ }
+
+ /* Prefer a 5 GHz channel */
+ if (p2p_channel_select(intersection, op_classes_5ghz,
+ &p2p->op_reg_class, &p2p->op_channel) == 0) {
+ p2p_dbg(p2p, "Pick possible 5 GHz channel (op_class %u channel %u) from intersection",
+ p2p->op_reg_class, p2p->op_channel);
+ return;
+ }
+
+ /*
+ * Try to see if the original channel is in the intersection. If
+ * so, no need to change anything, as it already contains some
+ * randomness.
+ */
+ if (p2p_channels_includes(intersection, p2p->op_reg_class,
+ p2p->op_channel)) {
+ p2p_dbg(p2p, "Using original operating class and channel (op_class %u channel %u) from intersection",
+ p2p->op_reg_class, p2p->op_channel);
+ return;
+ }
+
+ /*
+ * Fall back to whatever is included in the channel intersection since
+ * no better options seems to be available.
+ */
+ cl = &intersection->reg_class[0];
+ p2p_dbg(p2p, "Pick another channel (reg_class %u channel %u) from intersection",
+ cl->reg_class, cl->channel[0]);
+ p2p->op_reg_class = cl->reg_class;
+ p2p->op_channel = cl->channel[0];
+}
+
+
+int p2p_go_select_channel(struct p2p_data *p2p, struct p2p_device *dev,
+ u8 *status)
+{
+ struct p2p_channels tmp, intersection;
+
+ p2p_channels_dump(p2p, "own channels", &p2p->channels);
+ p2p_channels_dump(p2p, "peer channels", &dev->channels);
+ p2p_channels_intersect(&p2p->channels, &dev->channels, &tmp);
+ p2p_channels_dump(p2p, "intersection", &tmp);
+ p2p_channels_remove_freqs(&tmp, &p2p->no_go_freq);
+ p2p_channels_dump(p2p, "intersection after no-GO removal", &tmp);
+ p2p_channels_intersect(&tmp, &p2p->cfg->channels, &intersection);
+ p2p_channels_dump(p2p, "intersection with local channel list",
+ &intersection);
+ if (intersection.reg_classes == 0 ||
+ intersection.reg_class[0].channels == 0) {
+ *status = P2P_SC_FAIL_NO_COMMON_CHANNELS;
+ p2p_dbg(p2p, "No common channels found");
+ return -1;
+ }
+
+ if (!p2p_channels_includes(&intersection, p2p->op_reg_class,
+ p2p->op_channel)) {
+ if (dev->flags & P2P_DEV_FORCE_FREQ) {
+ *status = P2P_SC_FAIL_NO_COMMON_CHANNELS;
+ p2p_dbg(p2p, "Peer does not support the forced channel");
+ return -1;
+ }
+
+ p2p_dbg(p2p, "Selected operating channel (op_class %u channel %u) not acceptable to the peer",
+ p2p->op_reg_class, p2p->op_channel);
+ p2p_reselect_channel(p2p, &intersection);
+ } else if (!(dev->flags & P2P_DEV_FORCE_FREQ) &&
+ !p2p->cfg->cfg_op_channel) {
+ p2p_dbg(p2p, "Try to optimize channel selection with peer information received; previously selected op_class %u channel %u",
+ p2p->op_reg_class, p2p->op_channel);
+ p2p_reselect_channel(p2p, &intersection);
+ }
+
+ if (!p2p->ssid_set) {
+ p2p_build_ssid(p2p, p2p->ssid, &p2p->ssid_len);
+ p2p->ssid_set = 1;
+ }
+
+ return 0;
+}
+
+
+static void p2p_check_pref_chan_no_recv(struct p2p_data *p2p, int go,
+ struct p2p_device *dev,
+ struct p2p_message *msg,
+ unsigned freq_list[], unsigned int size)
+{
+ u8 op_class, op_channel;
+ unsigned int oper_freq = 0, i, j;
+ int found = 0;
+
+ p2p_dbg(p2p,
+ "Peer didn't provide a preferred frequency list, see if any of our preferred channels are supported by peer device");
+
+ /*
+ * Search for a common channel in our preferred frequency list which is
+ * also supported by the peer device.
+ */
+ for (i = 0; i < size && !found; i++) {
+ /*
+ * Make sure that the common frequency is:
+ * 1. Supported by peer
+ * 2. Allowed for P2P use.
+ */
+ oper_freq = freq_list[i];
+ if (p2p_freq_to_channel(oper_freq, &op_class,
+ &op_channel) < 0) {
+ p2p_dbg(p2p, "Unsupported frequency %u MHz", oper_freq);
+ continue;
+ }
+ if (!p2p_channels_includes(&p2p->cfg->channels,
+ op_class, op_channel) &&
+ (go || !p2p_channels_includes(&p2p->cfg->cli_channels,
+ op_class, op_channel))) {
+ p2p_dbg(p2p,
+ "Freq %u MHz (oper_class %u channel %u) not allowed for P2P",
+ oper_freq, op_class, op_channel);
+ break;
+ }
+ for (j = 0; j < msg->channel_list_len; j++) {
+
+ if (op_channel != msg->channel_list[j])
+ continue;
+
+ p2p->op_reg_class = op_class;
+ p2p->op_channel = op_channel;
+ os_memcpy(&p2p->channels, &p2p->cfg->channels,
+ sizeof(struct p2p_channels));
+ found = 1;
+ break;
+ }
+ }
+
+ if (found) {
+ p2p_dbg(p2p,
+ "Freq %d MHz is a preferred channel and is also supported by peer, use it as the operating channel",
+ oper_freq);
+ } else {
+ p2p_dbg(p2p,
+ "None of our preferred channels are supported by peer!. Use: %d MHz for oper_channel",
+ dev->oper_freq);
+ }
+}
+
+
+static void p2p_check_pref_chan_recv(struct p2p_data *p2p, int go,
+ struct p2p_device *dev,
+ struct p2p_message *msg,
+ unsigned freq_list[], unsigned int size)
+{
+ u8 op_class, op_channel;
+ unsigned int oper_freq = 0, i, j;
+ int found = 0;
+
+ /*
+ * Peer device supports a Preferred Frequency List.
+ * Search for a common channel in the preferred frequency lists
+ * of both peer and local devices.
+ */
+ for (i = 0; i < size && !found; i++) {
+ for (j = 2; j < (msg->pref_freq_list_len / 2); j++) {
+ oper_freq = p2p_channel_to_freq(
+ msg->pref_freq_list[2 * j],
+ msg->pref_freq_list[2 * j + 1]);
+ if (freq_list[i] != oper_freq)
+ continue;
+
+ /*
+ * Make sure that the found frequency is:
+ * 1. Supported
+ * 2. Allowed for P2P use.
+ */
+ if (p2p_freq_to_channel(oper_freq, &op_class,
+ &op_channel) < 0) {
+ p2p_dbg(p2p, "Unsupported frequency %u MHz",
+ oper_freq);
+ continue;
+ }
+
+ if (!p2p_channels_includes(&p2p->cfg->channels,
+ op_class, op_channel) &&
+ (go ||
+ !p2p_channels_includes(&p2p->cfg->cli_channels,
+ op_class, op_channel))) {
+ p2p_dbg(p2p,
+ "Freq %u MHz (oper_class %u channel %u) not allowed for P2P",
+ oper_freq, op_class, op_channel);
+ break;
+ }
+ p2p->op_reg_class = op_class;
+ p2p->op_channel = op_channel;
+ os_memcpy(&p2p->channels, &p2p->cfg->channels,
+ sizeof(struct p2p_channels));
+ found = 1;
+ break;
+ }
+ }
+
+ if (found) {
+ p2p_dbg(p2p,
+ "Freq %d MHz is a common preferred channel for both peer and local, use it as operating channel",
+ oper_freq);
+ } else {
+ p2p_dbg(p2p,
+ "No common preferred channels found! Use: %d MHz for oper_channel",
+ dev->oper_freq);
+ }
+}
+
+
+void p2p_check_pref_chan(struct p2p_data *p2p, int go,
+ struct p2p_device *dev, struct p2p_message *msg)
+{
+ unsigned int freq_list[P2P_MAX_PREF_CHANNELS], size;
+ unsigned int i;
+ u8 op_class, op_channel;
+
+ /*
+ * Use the preferred channel list from the driver only if there is no
+ * forced_freq, e.g., P2P_CONNECT freq=..., and no preferred operating
+ * channel hardcoded in the configuration file.
+ */
+ if (!p2p->cfg->get_pref_freq_list || p2p->cfg->num_pref_chan ||
+ (dev->flags & P2P_DEV_FORCE_FREQ) || p2p->cfg->cfg_op_channel)
+ return;
+
+ /* Obtain our preferred frequency list from driver based on P2P role. */
+ size = P2P_MAX_PREF_CHANNELS;
+ if (p2p->cfg->get_pref_freq_list(p2p->cfg->cb_ctx, go, &size,
+ freq_list))
+ return;
+
+ /*
+ * Check if peer's preference of operating channel is in
+ * our preferred channel list.
+ */
+ for (i = 0; i < size; i++) {
+ if (freq_list[i] == (unsigned int) dev->oper_freq)
+ break;
+ }
+ if (i != size) {
+ /* Peer operating channel preference matches our preference */
+ if (p2p_freq_to_channel(freq_list[i], &op_class, &op_channel) <
+ 0) {
+ p2p_dbg(p2p,
+ "Peer operating channel preference is unsupported frequency %u MHz",
+ freq_list[i]);
+ } else {
+ p2p->op_reg_class = op_class;
+ p2p->op_channel = op_channel;
+ os_memcpy(&p2p->channels, &p2p->cfg->channels,
+ sizeof(struct p2p_channels));
+ return;
+ }
+ }
+
+ p2p_dbg(p2p,
+ "Peer operating channel preference: %d MHz is not in our preferred channel list",
+ dev->oper_freq);
+
+ /*
+ Check if peer's preferred channel list is
+ * _not_ included in the GO Negotiation Request or Invitation Request.
+ */
+ if (msg->pref_freq_list_len == 0)
+ p2p_check_pref_chan_no_recv(p2p, go, dev, msg, freq_list, size);
+ else
+ p2p_check_pref_chan_recv(p2p, go, dev, msg, freq_list, size);
+}
+
+