[RFC v2 58/99] wpa_supplicant: Support NAN local schedule configuration

Andrei Otcheretianski andrei.otcheretianski at intel.com
Tue Dec 23 03:52:02 PST 2025


Add a control interface API to configure local NAN schedule.

Signed-off-by: Andrei Otcheretianski <andrei.otcheretianski at intel.com>
---
 wpa_supplicant/ctrl_iface.c       |   3 +
 wpa_supplicant/nan_supplicant.c   | 433 ++++++++++++++++++++++++++++++
 wpa_supplicant/nan_supplicant.h   |   1 +
 wpa_supplicant/wpa_supplicant.c   |   4 +
 wpa_supplicant/wpa_supplicant_i.h |   8 +
 5 files changed, 449 insertions(+)

diff --git a/wpa_supplicant/ctrl_iface.c b/wpa_supplicant/ctrl_iface.c
index 6a5fa1cbe8..96c9716dca 100644
--- a/wpa_supplicant/ctrl_iface.c
+++ b/wpa_supplicant/ctrl_iface.c
@@ -14344,6 +14344,9 @@ char * wpa_supplicant_ctrl_iface_process(struct wpa_supplicant *wpa_s,
 	} else if (os_strncmp(buf, "NAN_UPDATE_CONF", 15) == 0) {
 		if (wpas_nan_update_conf(wpa_s) < 0)
 			reply_len = -1;
+	} else if (os_strncmp(buf, "NAN_SCHED_CONFIG_MAP ", 21) == 0) {
+		if (wpas_nan_sched_config_map(wpa_s, buf + 21) < 0)
+			reply_len = -1;
 #endif /* CONFIG_NAN */
 	} else {
 		os_memcpy(reply, "UNKNOWN COMMAND\n", 16);
diff --git a/wpa_supplicant/nan_supplicant.c b/wpa_supplicant/nan_supplicant.c
index bae7bef596..820786bbf2 100644
--- a/wpa_supplicant/nan_supplicant.c
+++ b/wpa_supplicant/nan_supplicant.c
@@ -20,6 +20,8 @@
 #include "pr_supplicant.h"
 #include "nan_supplicant.h"
 #include "utils/eloop.h"
+#include "common/ieee802_11_common.h"
+#include "bitfield.h"
 
 #define DEFAULT_NAN_MASTER_PREF 2
 #define DEFAULT_NAN_DUAL_BAND   0
@@ -35,6 +37,89 @@
 #define NAN_MIN_RSSI_MIDDLE -75
 #ifdef CONFIG_NAN
 
+static int get_center(u8 channel, const u8 *center_channels, size_t num_chan,
+		      int width)
+{
+	int span = (width - 20) / 10;
+	size_t i;
+
+	for (i = 0; i < num_chan; i++) {
+		if (channel >= center_channels[i] - span &&
+		    channel <= center_channels[i] + span)
+			return center_channels[i];
+	}
+
+	return 0;
+}
+
+
+static bool wpas_nan_valid_chan(struct wpa_supplicant *wpa_s,
+				enum hostapd_hw_mode mode,
+			        u8 channel, int bw, u8 op_class,
+				u8 *cf1)
+{
+	static const u8 nan_160mhz_5ghz_chans[] = { 50, 114, 163 };
+	static const u8 nan_80mhz_5ghz_chans[] =
+		{ 42, 58, 106, 122, 138, 155, 171 };
+
+	struct hostapd_hw_modes *hw_mode;
+	int width, span;
+	u8 c, center = 0;
+
+	hw_mode = get_mode(wpa_s->hw.modes, wpa_s->hw.num_modes, mode, false);
+	if (!hw_mode)
+		return false;
+
+	switch (bw) {
+		case BW20:
+			width = 20;
+			center = channel;
+			break;
+		case BW40PLUS:
+		case BW40MINUS:
+			width = 40;
+			center = bw == BW40PLUS ? channel + 2 : channel - 2;
+			break;
+		case BW80:
+			width = 80;
+			center = get_center(channel, nan_80mhz_5ghz_chans,
+					    ARRAY_SIZE(nan_80mhz_5ghz_chans),
+					    width);
+			break;
+		case BW160:
+			width = 160;
+			center = get_center(channel, nan_160mhz_5ghz_chans,
+					    ARRAY_SIZE(nan_160mhz_5ghz_chans),
+					    width);
+			break;
+		default:
+			return false;
+	}
+
+	if (!center)
+		return false;
+
+	span = (width - 20) / 10;
+	for (c = center - span; c <= center + span; c += 4) {
+		int freq = ieee80211_chan_to_freq(NULL, op_class, c);
+
+		if (freq < 0)
+			return false;
+
+		if (ieee80211_is_dfs(freq, wpa_s->hw.modes,
+				     wpa_s->hw.num_modes))
+			return false;
+	}
+
+	/* Wide channels use center */
+	if (width > 40)
+		channel = center;
+
+	*cf1 = center;
+	return verify_channel(hw_mode, op_class, channel, bw) == ALLOWED;
+}
+
+
 static int wpas_nan_start_cb(void *ctx, struct nan_cluster_config *config)
 {
 	struct wpa_supplicant *wpa_s = ctx;
@@ -52,9 +137,28 @@ static int wpas_nan_update_config_cb(void *ctx,
 }
 
 
+static void clear_sched_config(struct nan_schedule_config *sched_cfg)
+{
+	int i;
+
+	for (i = 0; i < sched_cfg->num_channels; i++)
+		wpabuf_free(sched_cfg->channels[i].time_bitmap);
+
+	os_memset(sched_cfg, 0, sizeof(*sched_cfg));
+}
+
+
 static void wpas_nan_stop_cb(void *ctx)
 {
 	struct wpa_supplicant *wpa_s = ctx;
+	int i;
+
+	for (i = 0; i < MAX_NAN_RADIOS; i++) {
+		if (wpa_s->nan_sched[i].num_channels) {
+			wpa_drv_nan_config_schedule(wpa_s, i + 1, NULL);
+			clear_sched_config(&wpa_s->nan_sched[i]);
+		}
+	}
 
 	wpa_drv_nan_stop(wpa_s);
 }
@@ -138,9 +242,14 @@ int wpas_nan_init(struct wpa_supplicant *wpa_s)
 
 void wpas_nan_deinit(struct wpa_supplicant *wpa_s)
 {
+	int i;
+
 	if (!wpa_s || !wpa_s->nan)
 		return;
 
+	for (i = 0; i < MAX_NAN_RADIOS; i++)
+		clear_sched_config(&wpa_s->nan_sched[i]);
+
 	nan_deinit(wpa_s->nan);
 	wpa_s->nan = NULL;
 }
@@ -273,6 +382,330 @@ int wpas_nan_update_conf(struct wpa_supplicant *wpa_s)
 }
 
 
+static u8 nan_select_40mhz_channel(u8 chan, u8 *op_class, int *bw)
+{
+	int op;
+
+	for (op = 0; global_op_class[op].op_class; op++) {
+		const struct oper_class_map *o = &global_op_class[op];
+		int c;
+
+		/* No support for 40 Mhz on 2.4 GHz */
+		if (o->mode != HOSTAPD_MODE_IEEE80211A)
+			continue;
+
+		/* Currenlty don't support NAN for 80+, 6 GHz etc. */
+		if (o->op_class > 129)
+			continue;
+
+		if (o->bw != BW40MINUS && o->bw != BW40PLUS)
+			continue;
+
+		for (c = o->min_chan; c <= o->max_chan; c += o->inc) {
+			if (c != chan)
+				continue;
+
+			*op_class = o->op_class;
+			*bw = o->bw;
+			if (o->bw == BW40MINUS)
+				return chan - 2;
+			else
+				return chan + 2;
+		}
+	}
+
+	return 0;
+}
+
+
+static int wpas_nan_select_channel_params(struct wpa_supplicant *wpa_s,
+					  int freq, int *center_freq1,
+					  int *center_freq2, int *bandwidth)
+{
+	u8 chan, op_class, center;
+	enum hostapd_hw_mode mode;
+	int bw;
+
+	mode = ieee80211_freq_to_channel_ext(freq, 0,
+					     CONF_OPER_CHWIDTH_USE_HT,
+					     &op_class, &chan);
+	if (mode == NUM_HOSTAPD_MODES) {
+		wpa_printf(MSG_DEBUG,
+			  "NAN: Invalid frequency %d", freq);
+		return -1;
+	}
+
+	if (!wpas_nan_valid_chan(wpa_s, mode, chan, BW20, op_class, &center)) {
+		wpa_printf(MSG_DEBUG,
+			   "NAN: Channel not valid for NAN (freq = %d)",
+			   freq);
+		return -1;
+	}
+
+	/* On 2.4 GHz use 20 MHz channels */
+	if (freq >= 2412 && freq <= 2484)
+		goto out;
+
+	/* TODO: Add support for NAN on other bands */
+	if (freq < 5180 || freq > 5885) {
+		wpa_printf(MSG_DEBUG,
+			   "NAN: Unsupported frequency %d", freq);
+		return -1;
+	}
+
+	if (wpas_nan_valid_chan(wpa_s, mode, chan, BW160, 129, &center)) {
+		*center_freq1 = ieee80211_chan_to_freq(NULL, op_class, center);
+		*center_freq2 = 0;
+		*bandwidth = 160;
+		return 0;
+	}
+
+	if (wpas_nan_valid_chan(wpa_s, mode, chan, BW80, 128, &center)) {
+		*center_freq1 = ieee80211_chan_to_freq(NULL, op_class, center);
+		*center_freq2 = 0;
+		*bandwidth = 80;
+		return 0;
+	}
+
+	if (nan_select_40mhz_channel(chan, &op_class, &bw) &&
+		wpas_nan_valid_chan(wpa_s, mode, center, bw, op_class, &center)) {
+		*center_freq1 = ieee80211_chan_to_freq(NULL, op_class,
+						       center);
+		*center_freq2 = 0;
+		*bandwidth = 40;
+		return 0;
+	}
+
+out:
+	/* Fallback to 20 MHz */
+	*center_freq1 = freq;
+	*center_freq2 = 0;
+	*bandwidth = 20;
+	return 0;
+}
+
+
+static void nan_dump_sched_config(const char *title,
+				  struct nan_schedule_config *sched_cfg)
+{
+	int i;
+
+	wpa_printf(MSG_DEBUG, "%s: num_channels=%d", title,
+		   sched_cfg->num_channels);
+	for (i = 0; i < sched_cfg->num_channels; i++) {
+		wpa_printf(MSG_DEBUG,
+			   "  Channel %d: freq=%d center_freq1=%d center_freq2=%d bandwidth=%d time_bitmap_len=%zu",
+			   i + 1,
+			   sched_cfg->channels[i].freq,
+			   sched_cfg->channels[i].center_freq1,
+			   sched_cfg->channels[i].center_freq2,
+			   sched_cfg->channels[i].bandwidth,
+			   wpabuf_len(sched_cfg->channels[i].time_bitmap));
+	}
+}
+
+
+/* Parse format NAN_SCHED_CONFIG_MAP map_id=<id> [freq:bitmap_hex]..
+   If no bitmaps provided - clear the map */
+int wpas_nan_sched_config_map(struct wpa_supplicant *wpa_s, const char *cmd)
+{
+	struct nan_schedule_config sched_cfg;
+	char *token, *context = NULL;
+	u8 map_id;
+	char *pos;
+	int *shared_freqs;
+	int shared_freqs_count, unused_freqs_count, ret = -1;
+	struct bitfield *bf_total;
+	int expected_bitmap_len;
+
+	if (!wpas_nan_ready(wpa_s))
+		return -1;
+
+	if (os_strncmp(cmd, "map_id=", 7) != 0) {
+		wpa_printf(MSG_DEBUG, "NAN: Invalid schedule map format");
+		return -1;
+	}
+
+	map_id = atoi(cmd + 7);
+
+	if (!map_id || map_id >= MAX_NAN_RADIOS) {
+		wpa_printf(MSG_DEBUG, "NAN: Invalid map_id %d", map_id);
+		return -1;
+	}
+
+	if (map_id > wpa_s->nan_num_radios) {
+		wpa_printf(MSG_DEBUG,
+			   "NAN: map_id %d exceeds number of supported NAN radios %d",
+			   map_id, wpa_s->nan_num_radios);
+		return -1;
+	}
+
+	if (!wpa_s->nan_schedule_period ||
+	    !wpa_s->nan_sched_slot_duration) {
+		    wpa_printf(MSG_DEBUG,
+			       "NAN: Driver doesn't advertise support for NAN scheduling");
+		    return -1;
+	}
+
+	expected_bitmap_len =(wpa_s->nan_schedule_period /
+			      wpa_s->nan_sched_slot_duration + 7) / 8;
+
+	os_memset(&sched_cfg, 0, sizeof(sched_cfg));
+
+	pos = os_strchr(cmd + 7, ' ');
+	if (!pos) {
+		clear_sched_config(&wpa_s->nan_sched[map_id - 1]);
+		wpa_printf(MSG_DEBUG,
+			   "NAN: Missing freq:timebitmap pairs - cleanup schedule");
+		return wpa_drv_nan_config_schedule(wpa_s, map_id, &sched_cfg);;
+	}
+
+	shared_freqs = os_calloc(wpa_s->num_multichan_concurrent,
+				 sizeof(int));
+	if (!shared_freqs) {
+		wpa_printf(MSG_DEBUG,
+			  "NAN: Failed to allocate memory for shared freqs");
+		return -1;
+	}
+
+	shared_freqs_count =
+		get_shared_radio_freqs(wpa_s, shared_freqs,
+				       wpa_s->num_multichan_concurrent,
+				       false);
+
+	unused_freqs_count = wpa_s->num_multichan_concurrent -
+		shared_freqs_count;
+
+	bf_total = bitfield_alloc(wpa_s->nan_schedule_period /
+				  wpa_s->nan_sched_slot_duration);
+	if (!bf_total) {
+		wpa_printf(MSG_DEBUG,
+			  "NAN: Failed to allocate bitfield for total schedule");
+		goto out;
+	}
+
+	/* Parse freq:timebitmap pairs */
+	pos++;
+	while ((token = str_token(pos, " ", &context))) {
+		int j, i = sched_cfg.num_channels;;
+		struct bitfield *bf_chan = NULL;
+		char *colon = os_strchr(token, ':');
+
+		if (i >= wpa_s->nan_max_channels_per_radio) {
+			wpa_printf(MSG_DEBUG,
+				   "NAN: Exceeded max channels per radio %u",
+				   wpa_s->nan_max_channels_per_radio);
+			goto out;
+		}
+
+		if (!colon) {
+			wpa_printf(MSG_DEBUG,
+				   "NAN: Invalid freq:timebitmap format");
+			goto out;
+		}
+
+		sched_cfg.channels[i].freq = atoi(token);
+		if (sched_cfg.channels[i].freq <= 0) {
+			wpa_printf(MSG_DEBUG, "NAN: Invalid frequency %d",
+				   sched_cfg.channels[i].freq);
+			goto out;
+		}
+
+		for (j = 0; j < i; j++) {
+			if (sched_cfg.channels[j].freq ==
+			    sched_cfg.channels[i].freq) {
+				wpa_printf(MSG_DEBUG,
+					   "NAN: Duplicate frequency %d",
+					   sched_cfg.channels[i].freq);
+				goto out;
+			}
+		}
+
+		if (wpas_nan_select_channel_params(wpa_s, sched_cfg.channels[i].freq,
+						   &sched_cfg.channels[i].center_freq1,
+						   &sched_cfg.channels[i].center_freq2,
+						   &sched_cfg.channels[i].bandwidth)) {
+			wpa_printf(MSG_DEBUG,
+				   "NAN: Failed to select channel params for freq %d",
+				   sched_cfg.channels[i].freq);
+			goto out;
+		}
+
+		if (!int_array_includes(shared_freqs,
+				        sched_cfg.channels[i].freq)) {
+			if (!unused_freqs_count) {
+				wpa_printf(MSG_DEBUG,
+					   "NAN: No unused radio frequency available for freq %d",
+					   sched_cfg.channels[i].freq);
+				goto out;
+			}
+
+			unused_freqs_count--;
+		}
+
+		sched_cfg.channels[i].time_bitmap = wpabuf_parse_bin(colon + 1);
+		if (!sched_cfg.channels[i].time_bitmap) {
+			wpa_printf(MSG_DEBUG, "NAN: Invalid time bitmap");
+			goto out;
+		}
+
+		sched_cfg.num_channels++;
+
+		if ((int)wpabuf_len(sched_cfg.channels[i].time_bitmap) !=
+		    expected_bitmap_len) {
+			wpa_printf(MSG_DEBUG,
+				   "NAN: Invalid bitmap length (%zu) for period=%d, slot length=%d",
+				   wpabuf_len(sched_cfg.channels[i].time_bitmap),
+				   wpa_s->nan_schedule_period,
+				   wpa_s->nan_sched_slot_duration);
+			goto out;
+		}
+
+		bf_chan = bitfield_alloc_data(
+			wpabuf_head(sched_cfg.channels[i].time_bitmap),
+			wpabuf_len(sched_cfg.channels[i].time_bitmap));
+		if (!bf_chan) {
+			wpa_printf(MSG_DEBUG,
+				   "NAN: Failed to allocate bitfield for channel schedule");
+			goto out;
+		}
+
+		if (bitfield_intersects(bf_total, bf_chan)) {
+			wpa_printf(MSG_DEBUG,
+				   "NAN: Overlapping time bitmap detected for freq %d",
+				   sched_cfg.channels[i].freq);
+			bitfield_free(bf_chan);
+			goto out;
+		}
+
+		bitfield_union_in_place(bf_total, bf_chan);
+		bitfield_free(bf_chan);
+	}
+
+	nan_dump_sched_config("NAN: Set schedule config", &sched_cfg);
+	ret = wpa_drv_nan_config_schedule(wpa_s, map_id, &sched_cfg);
+	if (ret < 0) {
+		wpa_printf(MSG_DEBUG,
+			   "NAN: Failed to configure NAN schedule map_id %d",
+			   map_id);
+		goto out;
+	}
+
+	/* Store the configured schedule */
+	wpa_s->schedule_sequence_id++;
+	clear_sched_config(&wpa_s->nan_sched[map_id - 1]);
+	os_memcpy(&wpa_s->nan_sched[map_id - 1], &sched_cfg,
+		  sizeof(sched_cfg));
+out:
+	os_free(bf_total);
+	os_free(shared_freqs);
+	if (ret)
+		clear_sched_config(&sched_cfg);
+
+	return ret;
+}
+
+
 void wpas_nan_cluster_join(struct wpa_supplicant *wpa_s,
 			   const u8 *cluster_id,
 			   bool new_cluster)
diff --git a/wpa_supplicant/nan_supplicant.h b/wpa_supplicant/nan_supplicant.h
index f1675f060b..7e521c7465 100644
--- a/wpa_supplicant/nan_supplicant.h
+++ b/wpa_supplicant/nan_supplicant.h
@@ -24,6 +24,7 @@ void wpas_nan_cluster_join(struct wpa_supplicant *wpa_s,
 			   const u8 *cluster_id,
 			   bool new_cluster);
 void wpas_nan_next_dw(struct wpa_supplicant *wpa_s, u32 freq);
+int wpas_nan_sched_config_map(struct wpa_supplicant *wpa_s, const char *cmd);
 
 #else /* CONFIG_NAN */
 
diff --git a/wpa_supplicant/wpa_supplicant.c b/wpa_supplicant/wpa_supplicant.c
index fb45a5893e..73c5730c3a 100644
--- a/wpa_supplicant/wpa_supplicant.c
+++ b/wpa_supplicant/wpa_supplicant.c
@@ -7984,6 +7984,10 @@ static int wpa_supplicant_init_iface(struct wpa_supplicant *wpa_s,
 
 #ifdef CONFIG_NAN
 	wpa_s->nan_drv_flags = capa.nan_flags;
+	wpa_s->nan_num_radios = capa.nan_num_radios;
+	wpa_s->nan_sched_slot_duration = capa.nan_slot_duration;
+	wpa_s->nan_schedule_period = capa.nan_schedule_period;
+	wpa_s->nan_max_channels_per_radio = capa.nan_sched_chans;
 #endif /* CONFIG_NAN */
 
 	if (wpa_supplicant_init_eapol(wpa_s) < 0)
diff --git a/wpa_supplicant/wpa_supplicant_i.h b/wpa_supplicant/wpa_supplicant_i.h
index 5609e38e12..b5b29edf31 100644
--- a/wpa_supplicant/wpa_supplicant_i.h
+++ b/wpa_supplicant/wpa_supplicant_i.h
@@ -1664,9 +1664,17 @@ struct wpa_supplicant {
 	bool nan_mgmt;
 
 #ifdef CONFIG_NAN
+#define MAX_NAN_RADIOS 2
 	u32 nan_drv_flags;
+	u8 nan_num_radios;
+	u8 nan_max_channels_per_radio;
+	u8 nan_sched_slot_duration;
+	u16 nan_schedule_period;
+
 	struct nan_data *nan;
 	struct nan_cluster_config nan_config;
+	u8 schedule_sequence_id;
+	struct nan_schedule_config nan_sched[MAX_NAN_RADIOS];
 #endif
 };
 
-- 
2.49.0




More information about the Hostap mailing list