xref: /linux/net/mac80211/nan.c (revision 91a4855d6c03e770e42f17c798a36a3c46e63de2)
1589c06e8SMiri Korenblit // SPDX-License-Identifier: GPL-2.0-only
2589c06e8SMiri Korenblit /*
3589c06e8SMiri Korenblit  * NAN mode implementation
4840492bfSMiri Korenblit  * Copyright(c) 2025-2026 Intel Corporation
5589c06e8SMiri Korenblit  */
6589c06e8SMiri Korenblit #include <net/mac80211.h>
7589c06e8SMiri Korenblit 
8589c06e8SMiri Korenblit #include "ieee80211_i.h"
9589c06e8SMiri Korenblit #include "driver-ops.h"
10840492bfSMiri Korenblit #include "sta_info.h"
11589c06e8SMiri Korenblit 
12589c06e8SMiri Korenblit static void
13589c06e8SMiri Korenblit ieee80211_nan_init_channel(struct ieee80211_nan_channel *nan_channel,
14589c06e8SMiri Korenblit 			   struct cfg80211_nan_channel *cfg_nan_channel)
15589c06e8SMiri Korenblit {
16589c06e8SMiri Korenblit 	memset(nan_channel, 0, sizeof(*nan_channel));
17589c06e8SMiri Korenblit 
18589c06e8SMiri Korenblit 	nan_channel->chanreq.oper = cfg_nan_channel->chandef;
19589c06e8SMiri Korenblit 	memcpy(nan_channel->channel_entry, cfg_nan_channel->channel_entry,
20589c06e8SMiri Korenblit 	       sizeof(nan_channel->channel_entry));
21589c06e8SMiri Korenblit 	nan_channel->needed_rx_chains = cfg_nan_channel->rx_nss;
22589c06e8SMiri Korenblit }
23589c06e8SMiri Korenblit 
24589c06e8SMiri Korenblit static void
25589c06e8SMiri Korenblit ieee80211_nan_update_channel(struct ieee80211_local *local,
26589c06e8SMiri Korenblit 			     struct ieee80211_nan_channel *nan_channel,
27589c06e8SMiri Korenblit 			     struct cfg80211_nan_channel *cfg_nan_channel,
28589c06e8SMiri Korenblit 			     bool deferred)
29589c06e8SMiri Korenblit {
30589c06e8SMiri Korenblit 	struct ieee80211_chanctx_conf *conf;
31589c06e8SMiri Korenblit 	bool reducing_nss;
32589c06e8SMiri Korenblit 
33589c06e8SMiri Korenblit 	if (WARN_ON(!cfg80211_chandef_identical(&nan_channel->chanreq.oper,
34589c06e8SMiri Korenblit 						&cfg_nan_channel->chandef)))
35589c06e8SMiri Korenblit 		return;
36589c06e8SMiri Korenblit 
37589c06e8SMiri Korenblit 	if (WARN_ON(memcmp(nan_channel->channel_entry,
38589c06e8SMiri Korenblit 			   cfg_nan_channel->channel_entry,
39589c06e8SMiri Korenblit 			   sizeof(nan_channel->channel_entry))))
40589c06e8SMiri Korenblit 		return;
41589c06e8SMiri Korenblit 
42589c06e8SMiri Korenblit 	if (nan_channel->needed_rx_chains == cfg_nan_channel->rx_nss)
43589c06e8SMiri Korenblit 		return;
44589c06e8SMiri Korenblit 
45589c06e8SMiri Korenblit 	reducing_nss = nan_channel->needed_rx_chains > cfg_nan_channel->rx_nss;
46589c06e8SMiri Korenblit 	nan_channel->needed_rx_chains = cfg_nan_channel->rx_nss;
47589c06e8SMiri Korenblit 
48589c06e8SMiri Korenblit 	conf = nan_channel->chanctx_conf;
49589c06e8SMiri Korenblit 
50589c06e8SMiri Korenblit 	/*
51589c06e8SMiri Korenblit 	 * If we are adding NSSs, we need to be ready before notifying the peer,
52589c06e8SMiri Korenblit 	 * if we are reducing NSSs, we need to wait until the peer is notified.
53589c06e8SMiri Korenblit 	 */
54589c06e8SMiri Korenblit 	if (!conf || (deferred && reducing_nss))
55589c06e8SMiri Korenblit 		return;
56589c06e8SMiri Korenblit 
57589c06e8SMiri Korenblit 	ieee80211_recalc_smps_chanctx(local, container_of(conf,
58589c06e8SMiri Korenblit 							  struct ieee80211_chanctx,
59589c06e8SMiri Korenblit 							  conf));
60589c06e8SMiri Korenblit }
61589c06e8SMiri Korenblit 
62589c06e8SMiri Korenblit static int
63589c06e8SMiri Korenblit ieee80211_nan_use_chanctx(struct ieee80211_sub_if_data *sdata,
64589c06e8SMiri Korenblit 			  struct ieee80211_nan_channel *nan_channel,
65589c06e8SMiri Korenblit 			  bool assign_on_failure)
66589c06e8SMiri Korenblit {
67589c06e8SMiri Korenblit 	struct ieee80211_chanctx *ctx;
68589c06e8SMiri Korenblit 	bool reused_ctx;
69589c06e8SMiri Korenblit 
70589c06e8SMiri Korenblit 	if (!nan_channel->chanreq.oper.chan)
71589c06e8SMiri Korenblit 		return -EINVAL;
72589c06e8SMiri Korenblit 
73589c06e8SMiri Korenblit 	if (ieee80211_check_combinations(sdata, &nan_channel->chanreq.oper,
74589c06e8SMiri Korenblit 					 IEEE80211_CHANCTX_SHARED, 0, -1))
75589c06e8SMiri Korenblit 		return -EBUSY;
76589c06e8SMiri Korenblit 
77589c06e8SMiri Korenblit 	ctx = ieee80211_find_or_create_chanctx(sdata, &nan_channel->chanreq,
78589c06e8SMiri Korenblit 					       IEEE80211_CHANCTX_SHARED,
79589c06e8SMiri Korenblit 					       assign_on_failure,
80589c06e8SMiri Korenblit 					       &reused_ctx);
81589c06e8SMiri Korenblit 	if (IS_ERR(ctx))
82589c06e8SMiri Korenblit 		return PTR_ERR(ctx);
83589c06e8SMiri Korenblit 
84589c06e8SMiri Korenblit 	nan_channel->chanctx_conf = &ctx->conf;
85589c06e8SMiri Korenblit 
86589c06e8SMiri Korenblit 	/*
87589c06e8SMiri Korenblit 	 * In case an existing channel context is being used, we marked it as
88589c06e8SMiri Korenblit 	 * will_be_used, now that it is assigned - clear this indication
89589c06e8SMiri Korenblit 	 */
90589c06e8SMiri Korenblit 	if (reused_ctx) {
91589c06e8SMiri Korenblit 		WARN_ON(!ctx->will_be_used);
92589c06e8SMiri Korenblit 		ctx->will_be_used = false;
93589c06e8SMiri Korenblit 	}
94589c06e8SMiri Korenblit 	ieee80211_recalc_chanctx_min_def(sdata->local, ctx);
95589c06e8SMiri Korenblit 	ieee80211_recalc_smps_chanctx(sdata->local, ctx);
96589c06e8SMiri Korenblit 
97589c06e8SMiri Korenblit 	return 0;
98589c06e8SMiri Korenblit }
99589c06e8SMiri Korenblit 
100589c06e8SMiri Korenblit static void
101840492bfSMiri Korenblit ieee80211_nan_update_peer_channels(struct ieee80211_sub_if_data *sdata,
102840492bfSMiri Korenblit 				   struct ieee80211_chanctx_conf *removed_conf)
103840492bfSMiri Korenblit {
104840492bfSMiri Korenblit 	struct ieee80211_local *local = sdata->local;
105840492bfSMiri Korenblit 	struct sta_info *sta;
106840492bfSMiri Korenblit 
107840492bfSMiri Korenblit 	lockdep_assert_wiphy(local->hw.wiphy);
108840492bfSMiri Korenblit 
109840492bfSMiri Korenblit 	list_for_each_entry(sta, &local->sta_list, list) {
110840492bfSMiri Korenblit 		struct ieee80211_nan_peer_sched *peer_sched;
111840492bfSMiri Korenblit 		int write_idx = 0;
112840492bfSMiri Korenblit 		bool updated = false;
113840492bfSMiri Korenblit 
114840492bfSMiri Korenblit 		if (sta->sdata != sdata)
115840492bfSMiri Korenblit 			continue;
116840492bfSMiri Korenblit 
117840492bfSMiri Korenblit 		peer_sched = sta->sta.nan_sched;
118840492bfSMiri Korenblit 		if (!peer_sched)
119840492bfSMiri Korenblit 			continue;
120840492bfSMiri Korenblit 
121840492bfSMiri Korenblit 		/* NULL out map slots for channels being removed */
122840492bfSMiri Korenblit 		for (int i = 0; i < peer_sched->n_channels; i++) {
123840492bfSMiri Korenblit 			if (peer_sched->channels[i].chanctx_conf != removed_conf)
124840492bfSMiri Korenblit 				continue;
125840492bfSMiri Korenblit 
126840492bfSMiri Korenblit 			for (int m = 0; m < CFG80211_NAN_MAX_PEER_MAPS; m++) {
127840492bfSMiri Korenblit 				struct ieee80211_nan_peer_map *map =
128840492bfSMiri Korenblit 					&peer_sched->maps[m];
129840492bfSMiri Korenblit 
130840492bfSMiri Korenblit 				if (map->map_id == CFG80211_NAN_INVALID_MAP_ID)
131840492bfSMiri Korenblit 					continue;
132840492bfSMiri Korenblit 
133840492bfSMiri Korenblit 				for (int s = 0; s < ARRAY_SIZE(map->slots); s++)
134840492bfSMiri Korenblit 					if (map->slots[s] == &peer_sched->channels[i])
135840492bfSMiri Korenblit 						map->slots[s] = NULL;
136840492bfSMiri Korenblit 			}
137840492bfSMiri Korenblit 		}
138840492bfSMiri Korenblit 
139840492bfSMiri Korenblit 		/* Compact channels array, removing those with removed_conf */
140840492bfSMiri Korenblit 		for (int i = 0; i < peer_sched->n_channels; i++) {
141840492bfSMiri Korenblit 			if (peer_sched->channels[i].chanctx_conf == removed_conf) {
142840492bfSMiri Korenblit 				updated = true;
143840492bfSMiri Korenblit 				continue;
144840492bfSMiri Korenblit 			}
145840492bfSMiri Korenblit 
146840492bfSMiri Korenblit 			if (write_idx != i) {
147840492bfSMiri Korenblit 				/* Update map pointers before moving */
148840492bfSMiri Korenblit 				for (int m = 0; m < CFG80211_NAN_MAX_PEER_MAPS; m++) {
149840492bfSMiri Korenblit 					struct ieee80211_nan_peer_map *map =
150840492bfSMiri Korenblit 						&peer_sched->maps[m];
151840492bfSMiri Korenblit 
152840492bfSMiri Korenblit 					if (map->map_id == CFG80211_NAN_INVALID_MAP_ID)
153840492bfSMiri Korenblit 						continue;
154840492bfSMiri Korenblit 
155840492bfSMiri Korenblit 					for (int s = 0; s < ARRAY_SIZE(map->slots); s++)
156840492bfSMiri Korenblit 						if (map->slots[s] == &peer_sched->channels[i])
157840492bfSMiri Korenblit 							map->slots[s] = &peer_sched->channels[write_idx];
158840492bfSMiri Korenblit 				}
159840492bfSMiri Korenblit 
160840492bfSMiri Korenblit 				peer_sched->channels[write_idx] = peer_sched->channels[i];
161840492bfSMiri Korenblit 			}
162840492bfSMiri Korenblit 			write_idx++;
163840492bfSMiri Korenblit 		}
164840492bfSMiri Korenblit 
165840492bfSMiri Korenblit 		/* Clear any remaining entries at the end */
166840492bfSMiri Korenblit 		for (int i = write_idx; i < peer_sched->n_channels; i++)
167840492bfSMiri Korenblit 			memset(&peer_sched->channels[i], 0, sizeof(peer_sched->channels[i]));
168840492bfSMiri Korenblit 
169840492bfSMiri Korenblit 		peer_sched->n_channels = write_idx;
170840492bfSMiri Korenblit 
171840492bfSMiri Korenblit 		if (updated)
172840492bfSMiri Korenblit 			drv_nan_peer_sched_changed(local, sdata, sta);
173840492bfSMiri Korenblit 	}
174840492bfSMiri Korenblit }
175840492bfSMiri Korenblit 
176840492bfSMiri Korenblit static void
177589c06e8SMiri Korenblit ieee80211_nan_remove_channel(struct ieee80211_sub_if_data *sdata,
178589c06e8SMiri Korenblit 			     struct ieee80211_nan_channel *nan_channel)
179589c06e8SMiri Korenblit {
180589c06e8SMiri Korenblit 	struct ieee80211_chanctx_conf *conf;
181589c06e8SMiri Korenblit 	struct ieee80211_chanctx *ctx;
182589c06e8SMiri Korenblit 	struct ieee80211_nan_sched_cfg *sched_cfg = &sdata->vif.cfg.nan_sched;
183589c06e8SMiri Korenblit 
184589c06e8SMiri Korenblit 	if (WARN_ON(!nan_channel))
185589c06e8SMiri Korenblit 		return;
186589c06e8SMiri Korenblit 
187589c06e8SMiri Korenblit 	lockdep_assert_wiphy(sdata->local->hw.wiphy);
188589c06e8SMiri Korenblit 
189589c06e8SMiri Korenblit 	if (!nan_channel->chanreq.oper.chan)
190589c06e8SMiri Korenblit 		return;
191589c06e8SMiri Korenblit 
192589c06e8SMiri Korenblit 	for (int slot = 0; slot < ARRAY_SIZE(sched_cfg->schedule); slot++)
193589c06e8SMiri Korenblit 		if (sched_cfg->schedule[slot] == nan_channel)
194589c06e8SMiri Korenblit 			sched_cfg->schedule[slot] = NULL;
195589c06e8SMiri Korenblit 
196589c06e8SMiri Korenblit 	conf = nan_channel->chanctx_conf;
197589c06e8SMiri Korenblit 
198840492bfSMiri Korenblit 	/* If any peer nan schedule uses this chanctx, update them */
199840492bfSMiri Korenblit 	if (conf)
200840492bfSMiri Korenblit 		ieee80211_nan_update_peer_channels(sdata, conf);
201840492bfSMiri Korenblit 
202589c06e8SMiri Korenblit 	memset(nan_channel, 0, sizeof(*nan_channel));
203589c06e8SMiri Korenblit 
204589c06e8SMiri Korenblit 	/* Update the driver before (possibly) releasing the channel context */
205589c06e8SMiri Korenblit 	drv_vif_cfg_changed(sdata->local, sdata, BSS_CHANGED_NAN_LOCAL_SCHED);
206589c06e8SMiri Korenblit 
207589c06e8SMiri Korenblit 	/* Channel might not have a chanctx if it was ULWed */
208589c06e8SMiri Korenblit 	if (!conf)
209589c06e8SMiri Korenblit 		return;
210589c06e8SMiri Korenblit 
211589c06e8SMiri Korenblit 	ctx = container_of(conf, struct ieee80211_chanctx, conf);
212589c06e8SMiri Korenblit 
213589c06e8SMiri Korenblit 	if (ieee80211_chanctx_num_assigned(sdata->local, ctx) > 0) {
214589c06e8SMiri Korenblit 		ieee80211_recalc_chanctx_chantype(sdata->local, ctx);
215589c06e8SMiri Korenblit 		ieee80211_recalc_smps_chanctx(sdata->local, ctx);
216589c06e8SMiri Korenblit 		ieee80211_recalc_chanctx_min_def(sdata->local, ctx);
217589c06e8SMiri Korenblit 	}
218589c06e8SMiri Korenblit 
219589c06e8SMiri Korenblit 	if (ieee80211_chanctx_refcount(sdata->local, ctx) == 0)
220589c06e8SMiri Korenblit 		ieee80211_free_chanctx(sdata->local, ctx, false);
221589c06e8SMiri Korenblit }
222589c06e8SMiri Korenblit 
223*e1d5c954SMiri Korenblit static void
224*e1d5c954SMiri Korenblit ieee80211_nan_update_all_ndi_carriers(struct ieee80211_local *local)
225*e1d5c954SMiri Korenblit {
226*e1d5c954SMiri Korenblit 	struct ieee80211_sub_if_data *sdata;
227*e1d5c954SMiri Korenblit 
228*e1d5c954SMiri Korenblit 	lockdep_assert_wiphy(local->hw.wiphy);
229*e1d5c954SMiri Korenblit 
230*e1d5c954SMiri Korenblit 	/* Iterate all interfaces and update carrier for NDI interfaces */
231*e1d5c954SMiri Korenblit 	list_for_each_entry(sdata, &local->interfaces, list) {
232*e1d5c954SMiri Korenblit 		if (!ieee80211_sdata_running(sdata) ||
233*e1d5c954SMiri Korenblit 		    sdata->vif.type != NL80211_IFTYPE_NAN_DATA)
234*e1d5c954SMiri Korenblit 			continue;
235*e1d5c954SMiri Korenblit 
236*e1d5c954SMiri Korenblit 		ieee80211_nan_update_ndi_carrier(sdata);
237*e1d5c954SMiri Korenblit 	}
238*e1d5c954SMiri Korenblit }
239*e1d5c954SMiri Korenblit 
240589c06e8SMiri Korenblit static struct ieee80211_nan_channel *
241589c06e8SMiri Korenblit ieee80211_nan_find_free_channel(struct ieee80211_nan_sched_cfg *sched_cfg)
242589c06e8SMiri Korenblit {
243589c06e8SMiri Korenblit 	for (int i = 0; i < ARRAY_SIZE(sched_cfg->channels); i++) {
244589c06e8SMiri Korenblit 		if (!sched_cfg->channels[i].chanreq.oper.chan)
245589c06e8SMiri Korenblit 			return &sched_cfg->channels[i];
246589c06e8SMiri Korenblit 	}
247589c06e8SMiri Korenblit 
248589c06e8SMiri Korenblit 	return NULL;
249589c06e8SMiri Korenblit }
250589c06e8SMiri Korenblit 
251589c06e8SMiri Korenblit int ieee80211_nan_set_local_sched(struct ieee80211_sub_if_data *sdata,
252589c06e8SMiri Korenblit 				  struct cfg80211_nan_local_sched *sched)
253589c06e8SMiri Korenblit {
254589c06e8SMiri Korenblit 	struct ieee80211_nan_channel *sched_idx_to_chan[IEEE80211_NAN_MAX_CHANNELS] = {};
255589c06e8SMiri Korenblit 	struct ieee80211_nan_sched_cfg *sched_cfg = &sdata->vif.cfg.nan_sched;
256589c06e8SMiri Korenblit 	struct ieee80211_nan_sched_cfg backup_sched;
257589c06e8SMiri Korenblit 	int ret;
258589c06e8SMiri Korenblit 
259589c06e8SMiri Korenblit 	if (sched->n_channels > IEEE80211_NAN_MAX_CHANNELS)
260589c06e8SMiri Korenblit 		return -EOPNOTSUPP;
261589c06e8SMiri Korenblit 
262589c06e8SMiri Korenblit 	if (sched->nan_avail_blob_len > IEEE80211_NAN_AVAIL_BLOB_MAX_LEN)
263589c06e8SMiri Korenblit 		return -EINVAL;
264589c06e8SMiri Korenblit 
265589c06e8SMiri Korenblit 	/*
266589c06e8SMiri Korenblit 	 * If a deferred schedule update is pending completion, new updates are
267589c06e8SMiri Korenblit 	 * not allowed. Only allow to configure an empty schedule so NAN can be
268589c06e8SMiri Korenblit 	 * stopped in the middle of a deferred update. This is fine because
269589c06e8SMiri Korenblit 	 * empty schedule means the local NAN device will not be available for
270589c06e8SMiri Korenblit 	 * peers anymore so there is no need to update peers about a new
271589c06e8SMiri Korenblit 	 * schedule.
272589c06e8SMiri Korenblit 	 */
273589c06e8SMiri Korenblit 	if (WARN_ON(sched_cfg->deferred && sched->n_channels))
274589c06e8SMiri Korenblit 		return -EBUSY;
275589c06e8SMiri Korenblit 
276589c06e8SMiri Korenblit 	bitmap_zero(sdata->u.nan.removed_channels, IEEE80211_NAN_MAX_CHANNELS);
277589c06e8SMiri Korenblit 
278589c06e8SMiri Korenblit 	memcpy(backup_sched.schedule, sched_cfg->schedule,
279589c06e8SMiri Korenblit 	       sizeof(backup_sched.schedule));
280589c06e8SMiri Korenblit 	memcpy(backup_sched.channels, sched_cfg->channels,
281589c06e8SMiri Korenblit 	       sizeof(backup_sched.channels));
282589c06e8SMiri Korenblit 	memcpy(backup_sched.avail_blob, sched_cfg->avail_blob,
283589c06e8SMiri Korenblit 	       sizeof(backup_sched.avail_blob));
284589c06e8SMiri Korenblit 	backup_sched.avail_blob_len = sched_cfg->avail_blob_len;
285589c06e8SMiri Korenblit 
286589c06e8SMiri Korenblit 	memcpy(sched_cfg->avail_blob, sched->nan_avail_blob,
287589c06e8SMiri Korenblit 	       sched->nan_avail_blob_len);
288589c06e8SMiri Korenblit 	sched_cfg->avail_blob_len = sched->nan_avail_blob_len;
289589c06e8SMiri Korenblit 
290589c06e8SMiri Korenblit 	/*
291589c06e8SMiri Korenblit 	 * Remove channels that are no longer in the new schedule to free up
292589c06e8SMiri Korenblit 	 * resources before adding new channels. For deferred schedule, channels
293589c06e8SMiri Korenblit 	 * will be removed when the schedule is applied.
294589c06e8SMiri Korenblit 	 * Create a mapping from sched index to sched_cfg channel
295589c06e8SMiri Korenblit 	 */
296589c06e8SMiri Korenblit 	for (int i = 0; i < ARRAY_SIZE(sched_cfg->channels); i++) {
297589c06e8SMiri Korenblit 		bool still_needed = false;
298589c06e8SMiri Korenblit 
299589c06e8SMiri Korenblit 		if (!sched_cfg->channels[i].chanreq.oper.chan)
300589c06e8SMiri Korenblit 			continue;
301589c06e8SMiri Korenblit 
302589c06e8SMiri Korenblit 		for (int j = 0; j < sched->n_channels; j++) {
303589c06e8SMiri Korenblit 			if (cfg80211_chandef_identical(&sched_cfg->channels[i].chanreq.oper,
304589c06e8SMiri Korenblit 						       &sched->nan_channels[j].chandef)) {
305589c06e8SMiri Korenblit 				sched_idx_to_chan[j] =
306589c06e8SMiri Korenblit 					&sched_cfg->channels[i];
307589c06e8SMiri Korenblit 				still_needed = true;
308589c06e8SMiri Korenblit 				break;
309589c06e8SMiri Korenblit 			}
310589c06e8SMiri Korenblit 		}
311589c06e8SMiri Korenblit 
312589c06e8SMiri Korenblit 		if (!still_needed) {
313589c06e8SMiri Korenblit 			__set_bit(i, sdata->u.nan.removed_channels);
314589c06e8SMiri Korenblit 			if (!sched->deferred)
315589c06e8SMiri Korenblit 				ieee80211_nan_remove_channel(sdata,
316589c06e8SMiri Korenblit 							     &sched_cfg->channels[i]);
317589c06e8SMiri Korenblit 		}
318589c06e8SMiri Korenblit 	}
319589c06e8SMiri Korenblit 
320589c06e8SMiri Korenblit 	for (int i = 0; i < sched->n_channels; i++) {
321589c06e8SMiri Korenblit 		struct ieee80211_nan_channel *chan = sched_idx_to_chan[i];
322589c06e8SMiri Korenblit 
323589c06e8SMiri Korenblit 		if (chan) {
324589c06e8SMiri Korenblit 			ieee80211_nan_update_channel(sdata->local, chan,
325589c06e8SMiri Korenblit 						     &sched->nan_channels[i],
326589c06e8SMiri Korenblit 						     sched->deferred);
327589c06e8SMiri Korenblit 		} else {
328589c06e8SMiri Korenblit 			chan = ieee80211_nan_find_free_channel(sched_cfg);
329589c06e8SMiri Korenblit 			if (WARN_ON(!chan)) {
330589c06e8SMiri Korenblit 				ret = -EINVAL;
331589c06e8SMiri Korenblit 				goto err;
332589c06e8SMiri Korenblit 			}
333589c06e8SMiri Korenblit 
334589c06e8SMiri Korenblit 			sched_idx_to_chan[i] = chan;
335589c06e8SMiri Korenblit 			ieee80211_nan_init_channel(chan,
336589c06e8SMiri Korenblit 						   &sched->nan_channels[i]);
337589c06e8SMiri Korenblit 
338589c06e8SMiri Korenblit 			ret = ieee80211_nan_use_chanctx(sdata, chan, false);
339589c06e8SMiri Korenblit 			if (ret) {
340589c06e8SMiri Korenblit 				memset(chan, 0, sizeof(*chan));
341589c06e8SMiri Korenblit 				goto err;
342589c06e8SMiri Korenblit 			}
343589c06e8SMiri Korenblit 		}
344589c06e8SMiri Korenblit 	}
345589c06e8SMiri Korenblit 
346589c06e8SMiri Korenblit 	for (int s = 0; s < ARRAY_SIZE(sched_cfg->schedule); s++) {
347589c06e8SMiri Korenblit 		if (sched->schedule[s] < ARRAY_SIZE(sched_idx_to_chan))
348589c06e8SMiri Korenblit 			sched_cfg->schedule[s] =
349589c06e8SMiri Korenblit 				sched_idx_to_chan[sched->schedule[s]];
350589c06e8SMiri Korenblit 		else
351589c06e8SMiri Korenblit 			sched_cfg->schedule[s] = NULL;
352589c06e8SMiri Korenblit 	}
353589c06e8SMiri Korenblit 
354589c06e8SMiri Korenblit 	sched_cfg->deferred = sched->deferred;
355589c06e8SMiri Korenblit 
356589c06e8SMiri Korenblit 	drv_vif_cfg_changed(sdata->local, sdata, BSS_CHANGED_NAN_LOCAL_SCHED);
357589c06e8SMiri Korenblit 
358589c06e8SMiri Korenblit 	/*
359589c06e8SMiri Korenblit 	 * For deferred update, don't update NDI carriers yet as the new
360589c06e8SMiri Korenblit 	 * schedule is not yet applied so common slots don't change. The NDI
361589c06e8SMiri Korenblit 	 * carrier will be updated once the driver notifies the new schedule is
362589c06e8SMiri Korenblit 	 * applied.
363589c06e8SMiri Korenblit 	 */
364589c06e8SMiri Korenblit 	if (sched_cfg->deferred)
365589c06e8SMiri Korenblit 		return 0;
366589c06e8SMiri Korenblit 
367*e1d5c954SMiri Korenblit 	ieee80211_nan_update_all_ndi_carriers(sdata->local);
368589c06e8SMiri Korenblit 	bitmap_zero(sdata->u.nan.removed_channels, IEEE80211_NAN_MAX_CHANNELS);
369589c06e8SMiri Korenblit 
370589c06e8SMiri Korenblit 	return 0;
371589c06e8SMiri Korenblit err:
372589c06e8SMiri Korenblit 	/* Remove newly added channels */
373589c06e8SMiri Korenblit 	for (int i = 0; i < ARRAY_SIZE(sched_cfg->channels); i++) {
374589c06e8SMiri Korenblit 		struct cfg80211_chan_def *chan_def =
375589c06e8SMiri Korenblit 			&sched_cfg->channels[i].chanreq.oper;
376589c06e8SMiri Korenblit 
377589c06e8SMiri Korenblit 		if (!chan_def->chan)
378589c06e8SMiri Korenblit 			continue;
379589c06e8SMiri Korenblit 
380589c06e8SMiri Korenblit 		if (!cfg80211_chandef_identical(&backup_sched.channels[i].chanreq.oper,
381589c06e8SMiri Korenblit 						chan_def))
382589c06e8SMiri Korenblit 			ieee80211_nan_remove_channel(sdata,
383589c06e8SMiri Korenblit 						     &sched_cfg->channels[i]);
384589c06e8SMiri Korenblit 	}
385589c06e8SMiri Korenblit 
386589c06e8SMiri Korenblit 	/* Re-add all backed up channels */
387589c06e8SMiri Korenblit 	for (int i = 0; i < ARRAY_SIZE(backup_sched.channels); i++) {
388589c06e8SMiri Korenblit 		struct ieee80211_nan_channel *chan = &sched_cfg->channels[i];
389589c06e8SMiri Korenblit 
390589c06e8SMiri Korenblit 		*chan = backup_sched.channels[i];
391589c06e8SMiri Korenblit 
392589c06e8SMiri Korenblit 		/*
393589c06e8SMiri Korenblit 		 * For deferred update, no channels were removed and the channel
394589c06e8SMiri Korenblit 		 * context didn't change, so nothing else to do.
395589c06e8SMiri Korenblit 		 */
396589c06e8SMiri Korenblit 		if (!chan->chanctx_conf || sched->deferred)
397589c06e8SMiri Korenblit 			continue;
398589c06e8SMiri Korenblit 
399589c06e8SMiri Korenblit 		if (test_bit(i, sdata->u.nan.removed_channels)) {
400589c06e8SMiri Korenblit 			/* Clear the stale chanctx pointer */
401589c06e8SMiri Korenblit 			chan->chanctx_conf = NULL;
402589c06e8SMiri Korenblit 			/*
403589c06e8SMiri Korenblit 			 * We removed the newly added channels so we don't lack
404589c06e8SMiri Korenblit 			 * resources. So the only reason that this would fail
405589c06e8SMiri Korenblit 			 * is a FW error which we ignore. Therefore, this
406589c06e8SMiri Korenblit 			 * should never fail.
407589c06e8SMiri Korenblit 			 */
408589c06e8SMiri Korenblit 			WARN_ON(ieee80211_nan_use_chanctx(sdata, chan, true));
409589c06e8SMiri Korenblit 		} else {
410589c06e8SMiri Korenblit 			struct ieee80211_chanctx_conf *conf = chan->chanctx_conf;
411589c06e8SMiri Korenblit 
412589c06e8SMiri Korenblit 			/* FIXME: detect no-op? */
413589c06e8SMiri Korenblit 			/* Channel was not removed but may have been updated */
414589c06e8SMiri Korenblit 			ieee80211_recalc_smps_chanctx(sdata->local,
415589c06e8SMiri Korenblit 						     container_of(conf,
416589c06e8SMiri Korenblit 								  struct ieee80211_chanctx,
417589c06e8SMiri Korenblit 								  conf));
418589c06e8SMiri Korenblit 		}
419589c06e8SMiri Korenblit 	}
420589c06e8SMiri Korenblit 
421589c06e8SMiri Korenblit 	memcpy(sched_cfg->schedule, backup_sched.schedule,
422589c06e8SMiri Korenblit 	       sizeof(backup_sched.schedule));
423589c06e8SMiri Korenblit 	memcpy(sched_cfg->avail_blob, backup_sched.avail_blob,
424589c06e8SMiri Korenblit 	       sizeof(backup_sched.avail_blob));
425589c06e8SMiri Korenblit 	sched_cfg->avail_blob_len = backup_sched.avail_blob_len;
426589c06e8SMiri Korenblit 	sched_cfg->deferred = false;
427589c06e8SMiri Korenblit 	bitmap_zero(sdata->u.nan.removed_channels, IEEE80211_NAN_MAX_CHANNELS);
428589c06e8SMiri Korenblit 
429589c06e8SMiri Korenblit 	drv_vif_cfg_changed(sdata->local, sdata, BSS_CHANGED_NAN_LOCAL_SCHED);
430*e1d5c954SMiri Korenblit 	ieee80211_nan_update_all_ndi_carriers(sdata->local);
431589c06e8SMiri Korenblit 	return ret;
432589c06e8SMiri Korenblit }
433589c06e8SMiri Korenblit 
434589c06e8SMiri Korenblit void ieee80211_nan_sched_update_done(struct ieee80211_vif *vif)
435589c06e8SMiri Korenblit {
436589c06e8SMiri Korenblit 	struct ieee80211_sub_if_data *sdata = vif_to_sdata(vif);
437589c06e8SMiri Korenblit 	struct ieee80211_nan_sched_cfg *sched_cfg = &vif->cfg.nan_sched;
438589c06e8SMiri Korenblit 	unsigned int i;
439589c06e8SMiri Korenblit 
440589c06e8SMiri Korenblit 	lockdep_assert_wiphy(sdata->local->hw.wiphy);
441589c06e8SMiri Korenblit 
442589c06e8SMiri Korenblit 	if (WARN_ON(!sched_cfg->deferred))
443589c06e8SMiri Korenblit 		return;
444589c06e8SMiri Korenblit 
445*e1d5c954SMiri Korenblit 	ieee80211_nan_update_all_ndi_carriers(sdata->local);
446*e1d5c954SMiri Korenblit 
447589c06e8SMiri Korenblit 	/*
448589c06e8SMiri Korenblit 	 * Clear the deferred flag before removing channels. Removing channels
449589c06e8SMiri Korenblit 	 * will trigger another schedule update to the driver, and there is no
450589c06e8SMiri Korenblit 	 * need for this update to be deferred since removed channels are not
451589c06e8SMiri Korenblit 	 * part of the schedule anymore, so no need to notify peers about
452589c06e8SMiri Korenblit 	 * removing them.
453589c06e8SMiri Korenblit 	 */
454589c06e8SMiri Korenblit 	sched_cfg->deferred = false;
455589c06e8SMiri Korenblit 
456589c06e8SMiri Korenblit 	for (i = 0; i < ARRAY_SIZE(sched_cfg->channels); i++) {
457589c06e8SMiri Korenblit 		struct ieee80211_nan_channel *chan = &sched_cfg->channels[i];
458589c06e8SMiri Korenblit 		struct ieee80211_chanctx_conf *conf = chan->chanctx_conf;
459589c06e8SMiri Korenblit 
460589c06e8SMiri Korenblit 		if (!chan->chanreq.oper.chan)
461589c06e8SMiri Korenblit 			continue;
462589c06e8SMiri Korenblit 
463589c06e8SMiri Korenblit 		if (test_bit(i, sdata->u.nan.removed_channels))
464589c06e8SMiri Korenblit 			ieee80211_nan_remove_channel(sdata, chan);
465589c06e8SMiri Korenblit 		else if (conf)
466589c06e8SMiri Korenblit 			/*
467589c06e8SMiri Korenblit 			 * We might have called this already for some channels,
468589c06e8SMiri Korenblit 			 * but this knows to handle a no-op.
469589c06e8SMiri Korenblit 			 */
470589c06e8SMiri Korenblit 			ieee80211_recalc_smps_chanctx(sdata->local,
471589c06e8SMiri Korenblit 						      container_of(conf,
472589c06e8SMiri Korenblit 								   struct ieee80211_chanctx,
473589c06e8SMiri Korenblit 								   conf));
474589c06e8SMiri Korenblit 	}
475589c06e8SMiri Korenblit 
476589c06e8SMiri Korenblit 	bitmap_zero(sdata->u.nan.removed_channels, IEEE80211_NAN_MAX_CHANNELS);
477589c06e8SMiri Korenblit 	cfg80211_nan_sched_update_done(ieee80211_vif_to_wdev(vif), true,
478589c06e8SMiri Korenblit 				       GFP_KERNEL);
479589c06e8SMiri Korenblit }
480589c06e8SMiri Korenblit EXPORT_SYMBOL(ieee80211_nan_sched_update_done);
481840492bfSMiri Korenblit 
482840492bfSMiri Korenblit void ieee80211_nan_free_peer_sched(struct ieee80211_nan_peer_sched *sched)
483840492bfSMiri Korenblit {
484840492bfSMiri Korenblit 	if (!sched)
485840492bfSMiri Korenblit 		return;
486840492bfSMiri Korenblit 
487840492bfSMiri Korenblit 	kfree(sched->init_ulw);
488840492bfSMiri Korenblit 	kfree(sched);
489840492bfSMiri Korenblit }
490840492bfSMiri Korenblit 
491840492bfSMiri Korenblit static int
492840492bfSMiri Korenblit ieee80211_nan_init_peer_channel(struct ieee80211_sub_if_data *sdata,
493840492bfSMiri Korenblit 				const struct sta_info *sta,
494840492bfSMiri Korenblit 				const struct cfg80211_nan_channel *cfg_chan,
495840492bfSMiri Korenblit 				struct ieee80211_nan_channel *new_chan)
496840492bfSMiri Korenblit {
497840492bfSMiri Korenblit 	struct ieee80211_nan_sched_cfg *sched_cfg = &sdata->vif.cfg.nan_sched;
498840492bfSMiri Korenblit 
499840492bfSMiri Korenblit 	/* Find compatible local channel */
500840492bfSMiri Korenblit 	for (int j = 0; j < ARRAY_SIZE(sched_cfg->channels); j++) {
501840492bfSMiri Korenblit 		struct ieee80211_nan_channel *local_chan =
502840492bfSMiri Korenblit 			&sched_cfg->channels[j];
503840492bfSMiri Korenblit 		const struct cfg80211_chan_def *compat;
504840492bfSMiri Korenblit 
505840492bfSMiri Korenblit 		if (!local_chan->chanreq.oper.chan)
506840492bfSMiri Korenblit 			continue;
507840492bfSMiri Korenblit 
508840492bfSMiri Korenblit 		compat = cfg80211_chandef_compatible(&local_chan->chanreq.oper,
509840492bfSMiri Korenblit 						     &cfg_chan->chandef);
510840492bfSMiri Korenblit 		if (!compat)
511840492bfSMiri Korenblit 			continue;
512840492bfSMiri Korenblit 
513840492bfSMiri Korenblit 		/* compat is the wider chandef, and we want the narrower one */
514840492bfSMiri Korenblit 		new_chan->chanreq.oper = compat == &local_chan->chanreq.oper ?
515840492bfSMiri Korenblit 					 cfg_chan->chandef : local_chan->chanreq.oper;
516840492bfSMiri Korenblit 		new_chan->needed_rx_chains = min(local_chan->needed_rx_chains,
517840492bfSMiri Korenblit 						 cfg_chan->rx_nss);
518840492bfSMiri Korenblit 		new_chan->chanctx_conf = local_chan->chanctx_conf;
519840492bfSMiri Korenblit 
520840492bfSMiri Korenblit 		break;
521840492bfSMiri Korenblit 	}
522840492bfSMiri Korenblit 
523840492bfSMiri Korenblit 	/*
524840492bfSMiri Korenblit 	 * nl80211 already validated that each peer channel is compatible
525840492bfSMiri Korenblit 	 * with at least one local channel, so this should never happen.
526840492bfSMiri Korenblit 	 */
527840492bfSMiri Korenblit 	if (WARN_ON(!new_chan->chanreq.oper.chan))
528840492bfSMiri Korenblit 		return -EINVAL;
529840492bfSMiri Korenblit 
530840492bfSMiri Korenblit 	memcpy(new_chan->channel_entry, cfg_chan->channel_entry,
531840492bfSMiri Korenblit 	       sizeof(new_chan->channel_entry));
532840492bfSMiri Korenblit 
533840492bfSMiri Korenblit 	return 0;
534840492bfSMiri Korenblit }
535840492bfSMiri Korenblit 
536840492bfSMiri Korenblit static void
537840492bfSMiri Korenblit ieee80211_nan_init_peer_map(struct ieee80211_nan_peer_sched *peer_sched,
538840492bfSMiri Korenblit 			    const struct cfg80211_nan_peer_map *cfg_map,
539840492bfSMiri Korenblit 			    struct ieee80211_nan_peer_map *new_map)
540840492bfSMiri Korenblit {
541840492bfSMiri Korenblit 	new_map->map_id = cfg_map->map_id;
542840492bfSMiri Korenblit 
543840492bfSMiri Korenblit 	if (new_map->map_id == CFG80211_NAN_INVALID_MAP_ID)
544840492bfSMiri Korenblit 		return;
545840492bfSMiri Korenblit 
546840492bfSMiri Korenblit 	/* Set up the slots array */
547840492bfSMiri Korenblit 	for (int slot = 0; slot < ARRAY_SIZE(new_map->slots); slot++) {
548840492bfSMiri Korenblit 		u8 chan_idx = cfg_map->schedule[slot];
549840492bfSMiri Korenblit 
550840492bfSMiri Korenblit 		if (chan_idx < peer_sched->n_channels)
551840492bfSMiri Korenblit 			new_map->slots[slot] = &peer_sched->channels[chan_idx];
552840492bfSMiri Korenblit 	}
553840492bfSMiri Korenblit }
554840492bfSMiri Korenblit 
555*e1d5c954SMiri Korenblit /*
556*e1d5c954SMiri Korenblit  * Check if the local schedule and a peer schedule have at least one common
557*e1d5c954SMiri Korenblit  * slot - a slot where both schedules are active on compatible channels.
558*e1d5c954SMiri Korenblit  */
559*e1d5c954SMiri Korenblit static bool
560*e1d5c954SMiri Korenblit ieee80211_nan_has_common_slots(struct ieee80211_sub_if_data *sdata,
561*e1d5c954SMiri Korenblit 			       struct ieee80211_nan_peer_sched *peer_sched)
562*e1d5c954SMiri Korenblit {
563*e1d5c954SMiri Korenblit 	for (int slot = 0; slot < CFG80211_NAN_SCHED_NUM_TIME_SLOTS; slot++) {
564*e1d5c954SMiri Korenblit 		struct ieee80211_nan_channel *local_chan =
565*e1d5c954SMiri Korenblit 			sdata->vif.cfg.nan_sched.schedule[slot];
566*e1d5c954SMiri Korenblit 
567*e1d5c954SMiri Korenblit 		if (!local_chan || !local_chan->chanctx_conf)
568*e1d5c954SMiri Korenblit 			continue;
569*e1d5c954SMiri Korenblit 
570*e1d5c954SMiri Korenblit 		/* Check all peer maps for this slot */
571*e1d5c954SMiri Korenblit 		for (int m = 0; m < CFG80211_NAN_MAX_PEER_MAPS; m++) {
572*e1d5c954SMiri Korenblit 			struct ieee80211_nan_peer_map *map = &peer_sched->maps[m];
573*e1d5c954SMiri Korenblit 			struct ieee80211_nan_channel *peer_chan;
574*e1d5c954SMiri Korenblit 
575*e1d5c954SMiri Korenblit 			if (map->map_id == CFG80211_NAN_INVALID_MAP_ID)
576*e1d5c954SMiri Korenblit 				continue;
577*e1d5c954SMiri Korenblit 
578*e1d5c954SMiri Korenblit 			peer_chan = map->slots[slot];
579*e1d5c954SMiri Korenblit 			if (!peer_chan)
580*e1d5c954SMiri Korenblit 				continue;
581*e1d5c954SMiri Korenblit 
582*e1d5c954SMiri Korenblit 			if (local_chan->chanctx_conf == peer_chan->chanctx_conf)
583*e1d5c954SMiri Korenblit 				return true;
584*e1d5c954SMiri Korenblit 		}
585*e1d5c954SMiri Korenblit 	}
586*e1d5c954SMiri Korenblit 
587*e1d5c954SMiri Korenblit 	return false;
588*e1d5c954SMiri Korenblit }
589*e1d5c954SMiri Korenblit 
590*e1d5c954SMiri Korenblit void ieee80211_nan_update_ndi_carrier(struct ieee80211_sub_if_data *ndi_sdata)
591*e1d5c954SMiri Korenblit {
592*e1d5c954SMiri Korenblit 	struct ieee80211_local *local = ndi_sdata->local;
593*e1d5c954SMiri Korenblit 	struct ieee80211_sub_if_data *nmi_sdata;
594*e1d5c954SMiri Korenblit 	struct sta_info *sta;
595*e1d5c954SMiri Korenblit 
596*e1d5c954SMiri Korenblit 	lockdep_assert_wiphy(local->hw.wiphy);
597*e1d5c954SMiri Korenblit 
598*e1d5c954SMiri Korenblit 	if (WARN_ON(ndi_sdata->vif.type != NL80211_IFTYPE_NAN_DATA ||
599*e1d5c954SMiri Korenblit 		    !ndi_sdata->dev) || !ieee80211_sdata_running(ndi_sdata))
600*e1d5c954SMiri Korenblit 		return;
601*e1d5c954SMiri Korenblit 
602*e1d5c954SMiri Korenblit 	nmi_sdata = wiphy_dereference(local->hw.wiphy, ndi_sdata->u.nan_data.nmi);
603*e1d5c954SMiri Korenblit 	if (WARN_ON(!nmi_sdata))
604*e1d5c954SMiri Korenblit 		return;
605*e1d5c954SMiri Korenblit 
606*e1d5c954SMiri Korenblit 	list_for_each_entry(sta, &local->sta_list, list) {
607*e1d5c954SMiri Korenblit 		struct ieee80211_sta *nmi_sta;
608*e1d5c954SMiri Korenblit 
609*e1d5c954SMiri Korenblit 		if (sta->sdata != ndi_sdata ||
610*e1d5c954SMiri Korenblit 		    !test_sta_flag(sta, WLAN_STA_AUTHORIZED))
611*e1d5c954SMiri Korenblit 			continue;
612*e1d5c954SMiri Korenblit 
613*e1d5c954SMiri Korenblit 		nmi_sta = wiphy_dereference(local->hw.wiphy, sta->sta.nmi);
614*e1d5c954SMiri Korenblit 		if (WARN_ON(!nmi_sta) || !nmi_sta->nan_sched)
615*e1d5c954SMiri Korenblit 			continue;
616*e1d5c954SMiri Korenblit 
617*e1d5c954SMiri Korenblit 		if (ieee80211_nan_has_common_slots(nmi_sdata, nmi_sta->nan_sched)) {
618*e1d5c954SMiri Korenblit 			netif_carrier_on(ndi_sdata->dev);
619*e1d5c954SMiri Korenblit 			return;
620*e1d5c954SMiri Korenblit 		}
621*e1d5c954SMiri Korenblit 	}
622*e1d5c954SMiri Korenblit 
623*e1d5c954SMiri Korenblit 	netif_carrier_off(ndi_sdata->dev);
624*e1d5c954SMiri Korenblit }
625*e1d5c954SMiri Korenblit 
626*e1d5c954SMiri Korenblit static void
627*e1d5c954SMiri Korenblit ieee80211_nan_update_peer_ndis_carrier(struct ieee80211_local *local,
628*e1d5c954SMiri Korenblit 				       struct sta_info *nmi_sta)
629*e1d5c954SMiri Korenblit {
630*e1d5c954SMiri Korenblit 	struct sta_info *sta;
631*e1d5c954SMiri Korenblit 
632*e1d5c954SMiri Korenblit 	lockdep_assert_wiphy(local->hw.wiphy);
633*e1d5c954SMiri Korenblit 
634*e1d5c954SMiri Korenblit 	list_for_each_entry(sta, &local->sta_list, list) {
635*e1d5c954SMiri Korenblit 		if (rcu_access_pointer(sta->sta.nmi) == &nmi_sta->sta)
636*e1d5c954SMiri Korenblit 			ieee80211_nan_update_ndi_carrier(sta->sdata);
637*e1d5c954SMiri Korenblit 	}
638*e1d5c954SMiri Korenblit }
639*e1d5c954SMiri Korenblit 
640840492bfSMiri Korenblit int ieee80211_nan_set_peer_sched(struct ieee80211_sub_if_data *sdata,
641840492bfSMiri Korenblit 				 struct cfg80211_nan_peer_sched *sched)
642840492bfSMiri Korenblit {
643840492bfSMiri Korenblit 	struct ieee80211_nan_peer_sched *new_sched, *old_sched, *to_free;
644840492bfSMiri Korenblit 	struct sta_info *sta;
645840492bfSMiri Korenblit 	int ret;
646840492bfSMiri Korenblit 
647840492bfSMiri Korenblit 	lockdep_assert_wiphy(sdata->local->hw.wiphy);
648840492bfSMiri Korenblit 
649840492bfSMiri Korenblit 	if (!sdata->u.nan.started)
650840492bfSMiri Korenblit 		return -EINVAL;
651840492bfSMiri Korenblit 
652840492bfSMiri Korenblit 	sta = sta_info_get(sdata, sched->peer_addr);
653840492bfSMiri Korenblit 	if (!sta)
654840492bfSMiri Korenblit 		return -ENOENT;
655840492bfSMiri Korenblit 
656840492bfSMiri Korenblit 	new_sched = kzalloc(struct_size(new_sched, channels, sched->n_channels),
657840492bfSMiri Korenblit 			    GFP_KERNEL);
658840492bfSMiri Korenblit 	if (!new_sched)
659840492bfSMiri Korenblit 		return -ENOMEM;
660840492bfSMiri Korenblit 
661840492bfSMiri Korenblit 	to_free = new_sched;
662840492bfSMiri Korenblit 
663840492bfSMiri Korenblit 	new_sched->seq_id = sched->seq_id;
664840492bfSMiri Korenblit 	new_sched->committed_dw = sched->committed_dw;
665840492bfSMiri Korenblit 	new_sched->max_chan_switch = sched->max_chan_switch;
666840492bfSMiri Korenblit 	new_sched->n_channels = sched->n_channels;
667840492bfSMiri Korenblit 
668840492bfSMiri Korenblit 	if (sched->ulw_size && sched->init_ulw) {
669840492bfSMiri Korenblit 		new_sched->init_ulw = kmemdup(sched->init_ulw, sched->ulw_size,
670840492bfSMiri Korenblit 					      GFP_KERNEL);
671840492bfSMiri Korenblit 		if (!new_sched->init_ulw) {
672840492bfSMiri Korenblit 			ret = -ENOMEM;
673840492bfSMiri Korenblit 			goto out;
674840492bfSMiri Korenblit 		}
675840492bfSMiri Korenblit 		new_sched->ulw_size = sched->ulw_size;
676840492bfSMiri Korenblit 	}
677840492bfSMiri Korenblit 
678840492bfSMiri Korenblit 	for (int i = 0; i < sched->n_channels; i++) {
679840492bfSMiri Korenblit 		ret = ieee80211_nan_init_peer_channel(sdata, sta,
680840492bfSMiri Korenblit 						      &sched->nan_channels[i],
681840492bfSMiri Korenblit 						      &new_sched->channels[i]);
682840492bfSMiri Korenblit 		if (ret)
683840492bfSMiri Korenblit 			goto out;
684840492bfSMiri Korenblit 	}
685840492bfSMiri Korenblit 
686840492bfSMiri Korenblit 	for (int m = 0; m < ARRAY_SIZE(sched->maps); m++)
687840492bfSMiri Korenblit 		ieee80211_nan_init_peer_map(new_sched, &sched->maps[m],
688840492bfSMiri Korenblit 					    &new_sched->maps[m]);
689840492bfSMiri Korenblit 
690840492bfSMiri Korenblit 	/* Install the new schedule before calling the driver */
691840492bfSMiri Korenblit 	old_sched = sta->sta.nan_sched;
692840492bfSMiri Korenblit 	sta->sta.nan_sched = new_sched;
693840492bfSMiri Korenblit 
694840492bfSMiri Korenblit 	ret = drv_nan_peer_sched_changed(sdata->local, sdata, sta);
695840492bfSMiri Korenblit 	if (ret) {
696840492bfSMiri Korenblit 		/* Revert to old schedule */
697840492bfSMiri Korenblit 		sta->sta.nan_sched = old_sched;
698840492bfSMiri Korenblit 		goto out;
699840492bfSMiri Korenblit 	}
700840492bfSMiri Korenblit 
701*e1d5c954SMiri Korenblit 	ieee80211_nan_update_peer_ndis_carrier(sdata->local, sta);
702*e1d5c954SMiri Korenblit 
703840492bfSMiri Korenblit 	/* Success - free old schedule */
704840492bfSMiri Korenblit 	to_free = old_sched;
705840492bfSMiri Korenblit 	ret = 0;
706840492bfSMiri Korenblit 
707840492bfSMiri Korenblit out:
708840492bfSMiri Korenblit 	ieee80211_nan_free_peer_sched(to_free);
709840492bfSMiri Korenblit 	return ret;
710840492bfSMiri Korenblit }
711