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