root/drivers/net/wireless/virtual/mac80211_hwsim_nan.c
// SPDX-License-Identifier: GPL-2.0-only
/*
 * mac80211_hwsim_nan - NAN software simulation for mac80211_hwsim
 * Copyright (C) 2025-2026 Intel Corporation
 */

#include <net/cfg80211.h>
#include "mac80211_hwsim_i.h"

/* Defined as the lower 23 bits being zero */
#define DW0_TSF_MASK            GENMASK(22, 0)

/* DWs are repeated every 512 TUs */
#define DWST_TU                 512
#define DWST_TSF_MASK           (ieee80211_tu_to_usec(DWST_TU) - 1)

#define SLOT_TU                 16
#define SLOT_TSF_MASK           (ieee80211_tu_to_usec(DWST_TU) - 1)

/* The 2.4 GHz DW is at the start, the 5 GHz is in slot 8 (after 128 TUs) */
#define DW_5G_OFFSET_TU         128

#define SLOT_24GHZ_DW           0
#define SLOT_5GHZ_DW            (DW_5G_OFFSET_TU / SLOT_TU)

/* The special DW0 happens every 16 DWSTs (8192 TUs) */
static_assert(16 * DWST_TU * 1024 == 8192 * 1024);
static_assert(DW0_TSF_MASK + 1 == 8192 * 1024);

/* warmup phase should be 120 seconds, which is approximately 225 DWSTs */
#define NAN_WARMUP_DWST         225

#define NAN_RSSI_CLOSE (-60)
#define NAN_RSSI_MIDDLE (-75)

/* Quiet time at the end of each slot where TX is suppressed */
#define NAN_CHAN_SWITCH_TIME_US         256

struct hwsim_nan_sta_iter_ctx {
        struct ieee80211_hw *hw;
        bool can_tx;
};

struct hwsim_nan_mcast_data_iter_ctx {
        struct ieee80211_hw *hw;
        struct ieee80211_vif *vif;
        size_t n_vif_sta;
        size_t n_sta_can_tx;
};

static void mac80211_hwsim_nan_resume_txqs(struct mac80211_hwsim_data *data);

static u64 hwsim_nan_get_timer_tsf(struct mac80211_hwsim_data *data)
{
        ktime_t expires = hrtimer_get_expires(&data->nan.slot_timer);

        return mac80211_hwsim_boottime_to_tsf(data, expires);
}

static u8 hwsim_nan_slot_from_tsf(u64 tsf)
{
        return (tsf & DWST_TSF_MASK) / ieee80211_tu_to_usec(SLOT_TU);
}

static u64 hwsim_nan_encode_master_rank(u8 master_pref, u8 random_factor,
                                        const u8 *addr)
{
        return ((u64)master_pref << 56) +
                ((u64)random_factor << 48) +
                ((u64)addr[5] << 40) +
                ((u64)addr[4] << 32) +
                ((u64)addr[3] << 24) +
                ((u64)addr[2] << 16) +
                ((u64)addr[1] << 8) +
                ((u64)addr[0] << 0);
}

static u64 hwsim_nan_get_master_rank(struct mac80211_hwsim_data *data)
{
        u8 master_pref = 0;
        u8 random_factor = 0;

        if (data->nan.phase == MAC80211_HWSIM_NAN_PHASE_UP) {
                master_pref = data->nan.master_pref;
                random_factor = data->nan.random_factor;
        }

        return hwsim_nan_encode_master_rank(master_pref, random_factor,
                                            data->nan.device_vif->addr);
}

static bool mac80211_hwsim_nan_is_dw_slot(struct mac80211_hwsim_data *data,
                                          u8 slot)
{
        return slot == SLOT_24GHZ_DW ||
                (slot == SLOT_5GHZ_DW &&
                 (data->nan.bands & BIT(NL80211_BAND_5GHZ)));
}

static bool
hwsim_nan_rx_chandef_compatible(struct mac80211_hwsim_data *data, u8 slot,
                                struct ieee80211_channel *rx_chan, u8 rx_bw)
{
        static const int bw_to_mhz[] = {
                [RATE_INFO_BW_20] = 20, [RATE_INFO_BW_40] = 40,
                [RATE_INFO_BW_80] = 80, [RATE_INFO_BW_160] = 160,
        };
        struct cfg80211_chan_def sched_chandef;
        int rx_mhz, sched_mhz;

        scoped_guard(spinlock_bh, &data->nan.state_lock)
                sched_chandef = data->nan.local_sched[slot];

        if (!sched_chandef.chan ||
            sched_chandef.chan->center_freq != rx_chan->center_freq)
                return false;

        if (rx_bw >= ARRAY_SIZE(bw_to_mhz) || !bw_to_mhz[rx_bw])
                return false;

        rx_mhz = bw_to_mhz[rx_bw];
        sched_mhz = cfg80211_chandef_get_width(&sched_chandef);

        /* Accept RX at narrower or equal bandwidth */
        return rx_mhz <= sched_mhz;
}

static bool hwsim_nan_peer_present_in_dw(struct hwsim_sta_priv *sp, u64 tsf)
{
        u8 slot = hwsim_nan_slot_from_tsf(tsf);
        u8 cdw = 0;
        u8 dw_index, wake_interval;
        u16 committed_dw;

        scoped_guard(spinlock_bh, &sp->nan_sched.lock)
                committed_dw = sp->nan_sched.committed_dw;

        /* If peer doesn't advertise committed DW, assume presence in
         * all 2.4 GHz DW slots
         */
        if (!committed_dw)
                return slot == SLOT_24GHZ_DW;

        /* Get DW index (0-15) within the 16-DWST DW0 cycle */
        dw_index = (tsf / ieee80211_tu_to_usec(DWST_TU)) & 0xf;

        /* Extract CDW for the appropriate band (spec Table 80) */
        if (slot == SLOT_24GHZ_DW)
                cdw = committed_dw & 0x7;
        else if (slot == SLOT_5GHZ_DW)
                cdw = (committed_dw >> 3) & 0x7;

        if (cdw == 0)
                return false;

        /* Peer wakes every 2^(cdw-1) DWs: 1, 2, 4, 8, or 16 */
        wake_interval = 1 << (cdw - 1);

        return (dw_index % wake_interval) == 0;
}

static bool
hwsim_nan_peer_present_in_faw(struct hwsim_sta_priv *sp,
                              struct mac80211_hwsim_data *data, u8 slot)
{
        struct cfg80211_chan_def local_chandef;

        scoped_guard(spinlock_bh, &data->nan.state_lock)
                local_chandef = data->nan.local_sched[slot];

        if (!local_chandef.chan)
                return false;

        scoped_guard(spinlock_bh, &sp->nan_sched.lock) {
                for (int i = 0; i < CFG80211_NAN_MAX_PEER_MAPS; i++) {
                        struct cfg80211_chan_def *peer_chandef;

                        if (sp->nan_sched.maps[i].map_id ==
                            CFG80211_NAN_INVALID_MAP_ID)
                                continue;

                        peer_chandef = &sp->nan_sched.maps[i].chans[slot];
                        if (!peer_chandef->chan)
                                continue;

                        if (cfg80211_chandef_compatible(&local_chandef,
                                                        peer_chandef))
                                return true;
                }
        }

        return false;
}

static void
mac80211_hwsim_nan_schedule_slot(struct mac80211_hwsim_data *data, u8 slot,
                                 bool discontinuity)
{
        u64 tsf;

        if (!discontinuity)
                tsf = hwsim_nan_get_timer_tsf(data);
        else
                tsf = mac80211_hwsim_get_tsf(data->hw, data->nan.device_vif);

        /* Only called by mac80211_hwsim_nan_dw_timer from softirq context */
        lockdep_assert_in_softirq();

        tsf &= ~DWST_TSF_MASK;
        tsf += ieee80211_tu_to_usec(slot * SLOT_TU);

        hrtimer_set_expires(&data->nan.slot_timer,
                            mac80211_hwsim_tsf_to_boottime(data, tsf));
}

void mac80211_hwsim_nan_rx(struct ieee80211_hw *hw,
                           struct sk_buff *skb)
{
        struct mac80211_hwsim_data *data = hw->priv;
        const struct ieee80211_mgmt *mgmt = (void *)skb->data;
        struct element *nan_elem = (void *)mgmt->u.beacon.variable;
        struct ieee80211_nan_anchor_master_info *ami = NULL;
        const struct ieee80211_nan_attr *nan_attr;
        struct ieee80211_rx_status rx_status;
        bool joined_cluster = false;
        bool adopt_tsf = false;
        bool is_sync_beacon;
        bool is_same_cluster;
        u64 master_rank = 0;
        ssize_t data_len;
        u8 slot;

        /* Need a NAN vendor element at the start */
        if (skb->len < (offsetofend(struct ieee80211_mgmt, u.beacon) + 6) ||
            !ieee80211_is_beacon(mgmt->frame_control))
                return;

        data_len = skb->len - offsetofend(struct ieee80211_mgmt, u.beacon);

        /* Copy the RX status to add a MAC timestamp if needed */
        memcpy(&rx_status, IEEE80211_SKB_RXCB(skb),
               sizeof(struct ieee80211_rx_status));

        /* And deal with the lack of mac time stamp */
        if ((rx_status.flag & RX_FLAG_MACTIME) != RX_FLAG_MACTIME_START) {
                u64 tsf = mac80211_hwsim_get_tsf(hw, data->nan.device_vif);;

                /* In that case there should be no timestamp */
                WARN_ON_ONCE(rx_status.flag & RX_FLAG_MACTIME);

                /* No mac timestamp, set current TSF for the frame end */
                rx_status.flag |= RX_FLAG_MACTIME_END;
                rx_status.mactime = tsf;

                /* And translate to the start for the rest of the code */
                rx_status.mactime =
                        ieee80211_calculate_rx_timestamp(hw, &rx_status,
                                                         skb->len, 0);
                rx_status.flag &= ~RX_FLAG_MACTIME;
                rx_status.flag |= RX_FLAG_MACTIME_START;

                /* Match mac80211_hwsim_nan_receive, see comment there */
                slot = hwsim_nan_slot_from_tsf(tsf + 128);
        } else {
                slot = hwsim_nan_slot_from_tsf(rx_status.mactime);
        }

        /*
         * (overly) simplify things, only track 2.4 GHz here. Also, ignore
         * frames outside of the 2.4 GHz DW slot, unless in the initial SCAN
         * phase.
         */
        if ((slot != SLOT_24GHZ_DW &&
             data->nan.phase != MAC80211_HWSIM_NAN_PHASE_SCAN) ||
            rx_status.freq != 2437)
                return;

        /* Just ignore low RSSI beacons that we cannot sync to */
        if (rx_status.signal < NAN_RSSI_MIDDLE)
                return;

        /* Needs to be a valid NAN cluster ID in A3 */
        if (get_unaligned_be32(mgmt->bssid) != ((WLAN_OUI_WFA << 8) | 0x01))
                return;

        /* We are only interested in NAN beacons */
        if (nan_elem->id != WLAN_EID_VENDOR_SPECIFIC ||
            nan_elem->datalen < 4 ||
            get_unaligned_be32(nan_elem->data) !=
            (WLAN_OUI_WFA << 8 | WLAN_OUI_TYPE_WFA_NAN))
                return;

        u8 *nan_defragmented __free(kfree) = kzalloc(data_len, GFP_ATOMIC);
        if (!nan_defragmented)
                return;

        data_len = cfg80211_defragment_element(nan_elem,
                                               mgmt->u.beacon.variable,
                                               data_len,
                                               nan_defragmented, data_len,
                                               WLAN_EID_FRAGMENT);

        if (data_len < 0)
                return;

        /* Assume it is a synchronization beacon if beacon_int is 512 TUs */
        is_sync_beacon = le16_to_cpu(mgmt->u.beacon.beacon_int) == DWST_TU;
        is_same_cluster = ether_addr_equal(mgmt->bssid, data->nan.cluster_id);

        for_each_nan_attr(nan_attr, nan_defragmented + 4, data_len - 4) {
                if (nan_attr->attr == NAN_ATTR_MASTER_INDICATION &&
                    le16_to_cpu(nan_attr->length) >=
                    sizeof(struct ieee80211_nan_master_indication)) {
                        struct ieee80211_nan_master_indication *mi =
                                (void *)nan_attr->data;

                        master_rank =
                                hwsim_nan_encode_master_rank(mi->master_pref,
                                                             mi->random_factor,
                                                             mgmt->sa);
                }

                if (nan_attr->attr == NAN_ATTR_CLUSTER_INFO &&
                    le16_to_cpu(nan_attr->length) >=
                    sizeof(struct ieee80211_nan_anchor_master_info)) {
                        ami = (void *)nan_attr->data;

                        /*
                         * The AMBTT should be set to the beacon timestamp when
                         * the sender is the anchor master. We can simply
                         * modify the structure because we created a copy when
                         * defragmenting the NAN element.
                         */
                        if (ami->hop_count == 0)
                                ami->ambtt = cpu_to_le32(
                                        le64_to_cpu(mgmt->u.beacon.timestamp));
                }
        }

        /* Do the rest of the processing under lock */
        spin_lock_bh(&data->nan.state_lock);

        /*
         * sync beacon should be discarded if the master rank is the same
         * and the AMBTT is older than 16 * 512 TUs compared to our own TSF.
         *
         * Subtract the AMBTT from the lowered TSF. If the AMBTT is older
         * (smaller) then the calculation will not underflow.
         */
        if (is_sync_beacon && ami &&
            ami->master_rank == data->nan.current_ami.master_rank &&
            (((u32)rx_status.mactime -
              ieee80211_tu_to_usec(16 * 512)) -
             le32_to_cpu(ami->ambtt)) < 0x8000000) {
                wiphy_dbg(hw->wiphy,
                          "NAN: ignoring sync beacon with old AMBTT\n");
                is_sync_beacon = false;
        }

        if (is_same_cluster && is_sync_beacon &&
            master_rank > hwsim_nan_get_master_rank(data)) {
                if (rx_status.signal > NAN_RSSI_CLOSE)
                        data->nan.master_transition_score += 3;
                else
                        data->nan.master_transition_score += 1;
        }

        if (is_same_cluster && is_sync_beacon && ami &&
            ((ami->master_rank == data->nan.current_ami.master_rank &&
              ami->hop_count < data->nan.current_ami.hop_count) ||
             (master_rank > hwsim_nan_get_master_rank(data) &&
              ami->hop_count == data->nan.current_ami.hop_count))) {
                if (rx_status.signal > NAN_RSSI_CLOSE)
                        data->nan.sync_transition_score += 3;
                else
                        data->nan.sync_transition_score += 1;
        }

        /*
         * Decide on TSF adjustments before updating any other state
         */
        if (is_same_cluster && is_sync_beacon && ami &&
            data->nan.current_ami.hop_count != 0) {
                if (le64_to_cpu(ami->master_rank) >
                    le64_to_cpu(data->nan.current_ami.master_rank) &&
                    ami->master_rank != data->nan.last_ami.master_rank)
                        adopt_tsf = true;

                if (le64_to_cpu(ami->master_rank) >
                    le64_to_cpu(data->nan.current_ami.master_rank) &&
                    ami->master_rank == data->nan.last_ami.master_rank &&
                    le32_to_cpu(ami->ambtt) >
                    le32_to_cpu(data->nan.last_ami.ambtt))
                        adopt_tsf = true;

                if (le64_to_cpu(ami->master_rank) <
                    le64_to_cpu(data->nan.current_ami.master_rank) &&
                    le64_to_cpu(ami->master_rank) >
                    hwsim_nan_get_master_rank(data) &&
                    ether_addr_equal(ami->master_addr,
                                     data->nan.current_ami.master_addr))
                        adopt_tsf = true;

                if (ami->master_rank == data->nan.current_ami.master_rank &&
                    le32_to_cpu(ami->ambtt) >
                    le32_to_cpu(data->nan.current_ami.ambtt))
                        adopt_tsf = true;

                /* Anchor Master case is handled below */
        }

        /*
         * NAN Cluster merging
         */
        if (!is_same_cluster && ami) {
                u64 curr_amr;
                u64 own_cg;
                u64 frame_amr;
                u64 cg;

                /* Shifted down by 19 bits compared to spec */
                frame_amr = le64_to_cpu(ami->master_rank);
                cg = (u64)ami->master_pref << (64 - 19);
                cg += le64_to_cpu(mgmt->u.beacon.timestamp) >> 19;

                curr_amr = le64_to_cpu(data->nan.current_ami.master_rank);
                own_cg = (u64)data->nan.current_ami.master_pref << (64 - 19);
                own_cg += rx_status.mactime >> 19;

                /*
                 * Check if the cluster shall be joined
                 *
                 * When in the "scan" phase, just join immediately.
                 */
                if (cg > own_cg ||
                    (cg == own_cg && frame_amr > curr_amr) ||
                    data->nan.phase == MAC80211_HWSIM_NAN_PHASE_SCAN) {
                        /* Avoid a state transition */
                        data->nan.master_transition_score = 0;
                        data->nan.sync_transition_score = 0;

                        /*
                         * NOTE: The spec says we should TX sync beacons on the
                         * old schedule after joining. We do not implement this.
                         */

                        wiphy_dbg(hw->wiphy, "NAN: joining cluster %pM\n",
                                  mgmt->bssid);

                        joined_cluster = true;
                        adopt_tsf = true;

                        memcpy(&data->nan.last_ami, &data->nan.current_ami,
                               sizeof(data->nan.last_ami));
                        memcpy(&data->nan.current_ami, ami,
                               sizeof(data->nan.last_ami));
                        data->nan.current_ami.hop_count += 1;

                        memcpy(data->nan.cluster_id, mgmt->bssid, ETH_ALEN);

                        /*
                         * Assume we are UP if we joined a cluster.
                         *
                         * If the other anchor master is still in the warmup
                         * phase, then we may temporarily become the anchor
                         * master until it sets its own master preference to
                         * be non-zero.
                         */
                        data->nan.phase = MAC80211_HWSIM_NAN_PHASE_UP;
                        data->nan.random_factor_valid_dwst = 0;
                }
        }

        /*
         * Anchor master selection
         */
        /* We are not anchor master */
        if (is_same_cluster && is_sync_beacon && ami &&
            data->nan.current_ami.hop_count != 0) {
                if (le64_to_cpu(data->nan.current_ami.master_rank) <
                    le64_to_cpu(ami->master_rank)) {
                        if (ami->master_rank == data->nan.last_ami.master_rank &&
                            le32_to_cpu(ami->ambtt) <=
                            le32_to_cpu(data->nan.last_ami.ambtt)) {
                                /* disregard frame */
                        } else {
                                memcpy(&data->nan.last_ami,
                                       &data->nan.current_ami,
                                       sizeof(data->nan.last_ami));
                                memcpy(&data->nan.current_ami, ami,
                                       sizeof(data->nan.last_ami));
                                data->nan.current_ami.hop_count += 1;
                        }
                }

                if (le64_to_cpu(data->nan.current_ami.master_rank) >
                    le64_to_cpu(ami->master_rank)) {
                        if (!ether_addr_equal(data->nan.current_ami.master_addr,
                                              ami->master_addr)) {
                                /* disregard frame */
                        } else {
                                u64 amr = hwsim_nan_get_master_rank(data);

                                if (amr > le64_to_cpu(ami->master_rank)) {
                                        /* assume ourselves as anchor master */
                                        wiphy_dbg(hw->wiphy,
                                                  "NAN: assume anchor master role\n");
                                        data->nan.current_ami.master_rank =
                                                cpu_to_le64(amr);
                                        data->nan.current_ami.hop_count = 0;
                                        memset(&data->nan.last_ami, 0,
                                               sizeof(data->nan.last_ami));
                                        data->nan.last_ami.ambtt =
                                                data->nan.current_ami.ambtt;
                                        data->nan.current_ami.ambtt = 0;
                                } else {
                                        memcpy(&data->nan.last_ami,
                                               &data->nan.current_ami,
                                               sizeof(data->nan.last_ami));
                                        memcpy(&data->nan.current_ami, ami,
                                               sizeof(data->nan.last_ami));
                                        data->nan.current_ami.hop_count += 1;
                                }
                        }
                }

                if (data->nan.current_ami.master_rank == ami->master_rank) {
                        if (le32_to_cpu(data->nan.current_ami.ambtt) <
                            le32_to_cpu(ami->ambtt)) {
                                data->nan.current_ami.ambtt = ami->ambtt;
                        }

                        if (data->nan.current_ami.hop_count >
                            ami->hop_count + 1) {
                                data->nan.current_ami.hop_count =
                                        ami->hop_count + 1;
                        }
                }
        }

        /* We are anchor master */
        if (is_same_cluster && is_sync_beacon && ami &&
            data->nan.current_ami.hop_count == 0) {
                WARN_ON_ONCE(!ether_addr_equal(data->nan.current_ami.master_addr,
                                               data->nan.device_vif->addr));

                if (le64_to_cpu(ami->master_rank) <
                    le64_to_cpu(data->nan.current_ami.master_rank) ||
                    ether_addr_equal(ami->master_addr,
                                     data->nan.current_ami.master_addr)) {
                        /* disregard */
                } else {
                        wiphy_dbg(hw->wiphy, "NAN: lost anchor master role\n");
                        adopt_tsf = true;
                        memcpy(&data->nan.last_ami, &data->nan.current_ami,
                               sizeof(data->nan.last_ami));
                        memcpy(&data->nan.current_ami, ami,
                               sizeof(data->nan.last_ami));
                        data->nan.current_ami.hop_count += 1;
                }
        }

        if (adopt_tsf && !data->nan.tsf_adjusted) {
                int threshold = 5;
                s64 adjustment;

                /* Timestamp is likely inaccurate (and late) in this case */
                if (!(IEEE80211_SKB_RXCB(skb)->flag & RX_FLAG_MACTIME))
                        threshold = 128;

                adjustment =
                        le64_to_cpu(mgmt->u.beacon.timestamp) -
                        ieee80211_calculate_rx_timestamp(hw, &rx_status,
                                                         skb->len, 24);

                scoped_guard(spinlock_bh, &data->tsf_offset_lock) {
                        if (adjustment < -threshold || adjustment > threshold) {
                                if (adjustment < -(s64)ieee80211_tu_to_usec(4) ||
                                    adjustment > (s64)ieee80211_tu_to_usec(4))
                                        data->nan.tsf_discontinuity = true;

                                wiphy_debug(hw->wiphy,
                                            "NAN: Adjusting TSF by +/- %d us or more: %lld us (discontinuity: %d, from: %pM, old offset: %lld)\n",
                                            threshold, adjustment,
                                            data->nan.tsf_discontinuity, mgmt->sa,
                                            data->tsf_offset);
                        } else {
                                /* smooth things out a little bit */
                                adjustment /= 2;
                        }

                        /*
                         * Do the TSF adjustment
                         * The flag prevents further adjustments until the next
                         * 2.4 GHz DW starts to avoid race conditions for
                         * in-flight packets.
                         */
                        data->nan.tsf_adjusted = true;
                        data->tsf_offset += adjustment;
                }
        }

        spin_unlock_bh(&data->nan.state_lock);

        if (joined_cluster)
                ieee80211_nan_cluster_joined(data->nan.device_vif,
                                             data->nan.cluster_id, false,
                                             GFP_ATOMIC);
}

static void
mac80211_hwsim_nan_exec_state_transitions(struct mac80211_hwsim_data *data)
{
        bool notify_join = false;

        /*
         * Handle NAN role and state transitions at the end of the DW period
         * in accordance to Wi-Fi Aware version 4.0 section 3.3.7 point 2, i.e.
         * end of 5 GHz DW if enabled else at the end of the 2.4 GHz DW.
         */

        spin_lock(&data->nan.state_lock);

        /* Handle role transitions, Wi-Fi Aware version 4.0 section 3.3.6  */
        if (data->nan.master_transition_score < 3)
                data->nan.role = MAC80211_HWSIM_NAN_ROLE_MASTER;
        else if (data->nan.role == MAC80211_HWSIM_NAN_ROLE_MASTER &&
                 data->nan.master_transition_score >= 3)
                data->nan.role = MAC80211_HWSIM_NAN_ROLE_SYNC;
        else if (data->nan.role == MAC80211_HWSIM_NAN_ROLE_SYNC &&
                 data->nan.sync_transition_score >= 3)
                data->nan.role = MAC80211_HWSIM_NAN_ROLE_NON_SYNC;
        else if (data->nan.role == MAC80211_HWSIM_NAN_ROLE_NON_SYNC &&
                 data->nan.sync_transition_score < 3)
                data->nan.role = MAC80211_HWSIM_NAN_ROLE_SYNC;

        /*
         * The discovery beacon timer will stop automatically. Make sure it is
         * running if we are master. Do not bother with a proper alignment it
         * will sync itself to the TSF after the first TX.
         */
        if (data->nan.role == MAC80211_HWSIM_NAN_ROLE_MASTER &&
            !hrtimer_active(&data->nan.discovery_beacon_timer))
                hrtimer_start(&data->nan.discovery_beacon_timer,
                              ns_to_ktime(10 * NSEC_PER_USEC),
                              HRTIMER_MODE_REL_SOFT);

        data->nan.master_transition_score = 0;
        data->nan.sync_transition_score = 0;

        if (data->nan.random_factor_valid_dwst == 0) {
                u64 amr;

                if (data->nan.phase == MAC80211_HWSIM_NAN_PHASE_SCAN) {
                        data->nan.phase = MAC80211_HWSIM_NAN_PHASE_WARMUP;
                        data->nan.random_factor_valid_dwst = NAN_WARMUP_DWST;

                        notify_join = true;
                } else {
                        data->nan.phase = MAC80211_HWSIM_NAN_PHASE_UP;
                        data->nan.random_factor_valid_dwst =
                                get_random_u32_inclusive(120, 240);
                        data->nan.random_factor = get_random_u8();
                }

                amr = hwsim_nan_get_master_rank(data);

                if (data->nan.current_ami.hop_count == 0) {
                        /* Update if we are already anchor master */
                        data->nan.current_ami.master_rank = cpu_to_le64(amr);
                } else if (le64_to_cpu(data->nan.current_ami.master_rank) < amr) {
                        /* assume role if we have a higher rank */
                        wiphy_dbg(data->hw->wiphy,
                                  "NAN: assume anchor master role\n");
                        data->nan.current_ami.master_rank = cpu_to_le64(amr);
                        data->nan.current_ami.hop_count = 0;
                        memset(&data->nan.last_ami, 0,
                               sizeof(data->nan.last_ami));
                        data->nan.last_ami.ambtt = data->nan.current_ami.ambtt;
                        data->nan.current_ami.ambtt = 0;
                }
        } else {
                data->nan.random_factor_valid_dwst--;
        }

        spin_unlock(&data->nan.state_lock);

        if (notify_join)
                ieee80211_nan_cluster_joined(data->nan.device_vif,
                                             data->nan.cluster_id, true,
                                             GFP_ATOMIC);
}

static void
mac80211_hwsim_nan_tx_beacon(struct mac80211_hwsim_data *data,
                             bool is_discovery,
                             struct ieee80211_channel *channel)
{
        struct ieee80211_vendor_ie nan_ie = {
                .element_id = WLAN_EID_VENDOR_SPECIFIC,
                .len = 27 - 2,
                .oui = { u32_get_bits(WLAN_OUI_WFA, 0xff0000),
                         u32_get_bits(WLAN_OUI_WFA, 0xff00),
                         u32_get_bits(WLAN_OUI_WFA, 0xff) },
                .oui_type = WLAN_OUI_TYPE_WFA_NAN,
        };
        size_t alloc_size =
                IEEE80211_TX_STATUS_HEADROOM +
                offsetofend(struct ieee80211_mgmt, u.beacon) +
                27 /* size of NAN vendor element */;
        struct ieee80211_nan_master_indication master_indication;
        struct ieee80211_nan_attr nan_attr;
        struct ieee80211_mgmt *mgmt;
        struct sk_buff *skb;

        /*
         * TODO: Should the configured vendor elements or NAN attributes be
         * included in some of these beacons?
         */

        skb = alloc_skb(alloc_size, GFP_ATOMIC);
        if (!skb)
                return;

        spin_lock(&data->nan.state_lock);

        skb_reserve(skb, IEEE80211_TX_STATUS_HEADROOM);
        mgmt = skb_put(skb, offsetofend(struct ieee80211_mgmt, u.beacon));

        memset(mgmt, 0, offsetofend(struct ieee80211_mgmt, u.beacon));
        memcpy(mgmt->sa, data->nan.device_vif->addr, ETH_ALEN);
        memset(mgmt->da, 0xff, ETH_ALEN);
        memcpy(mgmt->bssid, data->nan.cluster_id, ETH_ALEN);

        mgmt->frame_control = cpu_to_le16(IEEE80211_FTYPE_MGMT |
                                          IEEE80211_STYPE_BEACON);
        mgmt->u.beacon.beacon_int = cpu_to_le16(is_discovery ? 100 : DWST_TU);
        mgmt->u.beacon.capab_info =
                cpu_to_le16(WLAN_CAPABILITY_SHORT_SLOT_TIME |
                            WLAN_CAPABILITY_SHORT_PREAMBLE);

        /* FIXME: set these to saner values? */
        mgmt->duration = 0;
        mgmt->seq_ctrl = 0;

        /* Put the NAN element */
        skb_put_data(skb, &nan_ie, sizeof(nan_ie));

        nan_attr.attr = NAN_ATTR_MASTER_INDICATION;
        nan_attr.length = cpu_to_le16(sizeof(master_indication));
        if (data->nan.phase == MAC80211_HWSIM_NAN_PHASE_UP) {
                master_indication.master_pref = data->nan.master_pref;
                master_indication.random_factor = data->nan.random_factor;
        } else {
                master_indication.master_pref = 0;
                master_indication.random_factor = 0;
        }

        skb_put_data(skb, &nan_attr, sizeof(nan_attr));
        skb_put_data(skb, &master_indication, sizeof(master_indication));

        nan_attr.attr = NAN_ATTR_CLUSTER_INFO;
        nan_attr.length = cpu_to_le16(sizeof(data->nan.current_ami));
        skb_put_data(skb, &nan_attr, sizeof(nan_attr));
        skb_put_data(skb, &data->nan.current_ami,
                     sizeof(data->nan.current_ami));

        spin_unlock(&data->nan.state_lock);

        mac80211_hwsim_tx_frame(data->hw, skb, channel);
}

enum hrtimer_restart
mac80211_hwsim_nan_slot_timer(struct hrtimer *timer)
{
        struct mac80211_hwsim_data *data =
                container_of(timer, struct mac80211_hwsim_data,
                             nan.slot_timer);
        struct ieee80211_hw *hw = data->hw;
        struct ieee80211_channel *notify_dw_chan = NULL;
        struct ieee80211_channel *beacon_sync_chan = NULL;
        u64 tsf = hwsim_nan_get_timer_tsf(data);
        u8 slot = hwsim_nan_slot_from_tsf(tsf);
        bool dwst_of_dw0 = false;
        bool dw_end = false;
        bool tx_sync_beacon;

        if (!data->nan.device_vif)
                return HRTIMER_NORESTART;

        if ((tsf & DW0_TSF_MASK & ~DWST_TSF_MASK) == 0)
                dwst_of_dw0 = true;


        scoped_guard(spinlock, &data->nan.state_lock) {
                if (data->nan.tsf_discontinuity) {
                        data->nan.tsf_discontinuity = false;

                        mac80211_hwsim_nan_schedule_slot(data, 32, true);

                        return HRTIMER_RESTART;
                }

                if (slot == SLOT_24GHZ_DW)
                        data->nan.tsf_adjusted = false;

                tx_sync_beacon =
                        data->nan.phase != MAC80211_HWSIM_NAN_PHASE_SCAN &&
                        data->nan.role != MAC80211_HWSIM_NAN_ROLE_NON_SYNC;
        }

        switch (slot) {
        case SLOT_24GHZ_DW:
                wiphy_dbg(data->hw->wiphy, "Start of 2.4 GHz DW, is DW0=%d\n",
                          dwst_of_dw0);
                beacon_sync_chan = ieee80211_get_channel(hw->wiphy, 2437);
                break;

        case SLOT_24GHZ_DW + 1:
                if (!(data->nan.bands & BIT(NL80211_BAND_5GHZ))) {
                        notify_dw_chan = ieee80211_get_channel(hw->wiphy, 2437);
                        dw_end = true;
                } else {
                        notify_dw_chan = ieee80211_get_channel(hw->wiphy, 5745);
                }
                break;

        case SLOT_5GHZ_DW:
                if (data->nan.bands & BIT(NL80211_BAND_5GHZ)) {
                        wiphy_dbg(data->hw->wiphy, "Start of 5 GHz DW\n");
                        beacon_sync_chan =
                                ieee80211_get_channel(hw->wiphy, 5745);
                }
                break;

        case SLOT_5GHZ_DW + 1:
                if (data->nan.bands & BIT(NL80211_BAND_5GHZ)) {
                        notify_dw_chan =
                                ieee80211_get_channel(hw->wiphy, 2437);
                        dw_end = true;
                }
                break;
        }

        /* TODO: This does not implement DW contention mitigation */
        if (beacon_sync_chan && tx_sync_beacon)
                mac80211_hwsim_nan_tx_beacon(data, false, beacon_sync_chan);

        if (dw_end)
                mac80211_hwsim_nan_exec_state_transitions(data);

        if (data->nan.notify_dw && notify_dw_chan) {
                struct wireless_dev *wdev =
                        ieee80211_vif_to_wdev(data->nan.device_vif);

                cfg80211_next_nan_dw_notif(wdev, notify_dw_chan, GFP_ATOMIC);
        }

        mac80211_hwsim_nan_resume_txqs(data);

        mac80211_hwsim_nan_schedule_slot(data, slot + 1, false);

        return HRTIMER_RESTART;
}

enum hrtimer_restart
mac80211_hwsim_nan_discovery_beacon_timer(struct hrtimer *timer)
{
        struct mac80211_hwsim_data *data =
                container_of(timer, struct mac80211_hwsim_data,
                             nan.discovery_beacon_timer);
        u32 remainder;
        u64 tsf_now;
        u64 tbtt;

        if (!data->nan.device_vif)
                return HRTIMER_NORESTART;

        scoped_guard(spinlock, &data->nan.state_lock) {
                if (data->nan.phase == MAC80211_HWSIM_NAN_PHASE_SCAN ||
                    data->nan.role != MAC80211_HWSIM_NAN_ROLE_MASTER)
                        return HRTIMER_NORESTART;
        }

        mac80211_hwsim_nan_tx_beacon(
                data, true, ieee80211_get_channel(data->hw->wiphy, 2437));

        if (data->nan.bands & BIT(NL80211_BAND_5GHZ))
                mac80211_hwsim_nan_tx_beacon(
                        data, true,
                        ieee80211_get_channel(data->hw->wiphy, 5745));

        /* Read the TSF from the current time in case of adjustments */
        tsf_now = mac80211_hwsim_get_tsf(data->hw, data->nan.device_vif);

        /* Wrap value to be after the next TBTT */
        tbtt = tsf_now + ieee80211_tu_to_usec(100);

        /* Round TBTT down to the correct time */
        div_u64_rem(tbtt, ieee80211_tu_to_usec(100), &remainder);
        tbtt = tbtt - remainder;

        hrtimer_set_expires(&data->nan.discovery_beacon_timer,
                            mac80211_hwsim_tsf_to_boottime(data, tbtt));

        return HRTIMER_RESTART;
}

int mac80211_hwsim_nan_start(struct ieee80211_hw *hw,
                             struct ieee80211_vif *vif,
                             struct cfg80211_nan_conf *conf)
{
        struct mac80211_hwsim_data *data = hw->priv;

        if (vif->type != NL80211_IFTYPE_NAN)
                return -EINVAL;

        if (data->nan.device_vif)
                return -EALREADY;

        /* set this before starting the timer, as preemption might occur */
        data->nan.device_vif = vif;
        data->nan.bands = conf->bands;

        scoped_guard(spinlock_bh, &data->nan.state_lock) {
                /* Start in the "scan" phase and stay there for a little bit */
                data->nan.phase = MAC80211_HWSIM_NAN_PHASE_SCAN;
                data->nan.random_factor_valid_dwst = 1;
                data->nan.random_factor = 0;
                data->nan.master_pref = conf->master_pref;
                data->nan.role = MAC80211_HWSIM_NAN_ROLE_MASTER;
                memset(&data->nan.current_ami, 0,
                       sizeof(data->nan.current_ami));
                memset(&data->nan.last_ami, 0, sizeof(data->nan.last_ami));
                data->nan.current_ami.master_rank =
                        cpu_to_le64(hwsim_nan_get_master_rank(data));
        }

        /* Just run this "soon" and start in a random schedule position */
        hrtimer_start(&data->nan.slot_timer,
                      ns_to_ktime(10 * NSEC_PER_USEC),
                      HRTIMER_MODE_REL_SOFT);

        ether_addr_copy(data->nan.cluster_id, conf->cluster_id);

        data->nan.notify_dw = conf->enable_dw_notification;

        return 0;
}

int mac80211_hwsim_nan_stop(struct ieee80211_hw *hw,
                            struct ieee80211_vif *vif)
{
        struct mac80211_hwsim_data *data = hw->priv;

        if (vif->type != NL80211_IFTYPE_NAN || !data->nan.device_vif ||
            data->nan.device_vif != vif)
                return -EINVAL;

        hrtimer_cancel(&data->nan.slot_timer);
        hrtimer_cancel(&data->nan.resume_txqs_timer);
        hrtimer_cancel(&data->nan.discovery_beacon_timer);
        data->nan.device_vif = NULL;

        return 0;
}

int mac80211_hwsim_nan_change_config(struct ieee80211_hw *hw,
                                     struct ieee80211_vif *vif,
                                     struct cfg80211_nan_conf *conf,
                                     u32 changes)
{
        struct mac80211_hwsim_data *data = hw->priv;

        if (vif->type != NL80211_IFTYPE_NAN)
                return -EINVAL;

        if (!data->nan.device_vif)
                return -EINVAL;

        wiphy_debug(hw->wiphy, "nan_config_changed: changes=0x%x\n", changes);

        /* Handle only the changes we care about for simulation purposes */
        if (changes & CFG80211_NAN_CONF_CHANGED_BANDS)
                data->nan.bands = conf->bands;

        if (changes & CFG80211_NAN_CONF_CHANGED_CONFIG)
                data->nan.notify_dw = conf->enable_dw_notification;

        if (changes & CFG80211_NAN_CONF_CHANGED_PREF) {
                scoped_guard(spinlock_bh, &data->nan.state_lock)
                        data->nan.master_pref = conf->master_pref;
        }

        return 0;
}

static void hwsim_nan_can_sta_transmit(void *_ctx, struct ieee80211_sta *sta)
{
        struct hwsim_nan_sta_iter_ctx *ctx = _ctx;

        if (ctx->can_tx)
                return;

        for (int i = 0; i < ARRAY_SIZE(sta->txq); i++) {
                struct ieee80211_txq *txq = sta->txq[i];

                if (!txq)
                        continue;

                if (txq->vif->type != NL80211_IFTYPE_NAN &&
                    txq->vif->type != NL80211_IFTYPE_NAN_DATA)
                        return;

                if (mac80211_hwsim_nan_txq_transmitting(ctx->hw, txq)) {
                        ctx->can_tx = true;
                        return;
                }
        }
}

static void mac80211_hwsim_nan_resume_txqs(struct mac80211_hwsim_data *data)
{
        u64 tsf = mac80211_hwsim_get_tsf(data->hw, data->nan.device_vif);
        u8 slot = hwsim_nan_slot_from_tsf(tsf);
        bool is_dw_slot = mac80211_hwsim_nan_is_dw_slot(data, slot);
        struct hwsim_nan_sta_iter_ctx ctx = {
                .hw = data->hw,
                .can_tx = false,
        };
        u32 timeout_ns;

        /* Outside DW, require local FAW schedule to proceed */
        if (!is_dw_slot) {
                scoped_guard(spinlock_bh, &data->nan.state_lock) {
                        if (!data->nan.local_sched[slot].chan)
                                return;
                }
        }

        guard(rcu)();

        /* Check if management queue can transmit */
        if (mac80211_hwsim_nan_txq_transmitting(data->hw,
                                                data->nan.device_vif->txq_mgmt))
                goto resume_txqs_timer;

        /* Check if any STA queue can transmit */
        ieee80211_iterate_stations_atomic(data->hw,
                                          hwsim_nan_can_sta_transmit,
                                          &ctx);

        if (!ctx.can_tx)
                return;

resume_txqs_timer:
        /*
         * Wait a bit and also randomize things so that not everyone is TXing
         * at the same time. Each slot is 16 TU long, this waits between 100 us
         * and 5 ms before starting to TX (unless a new frame arrives).
         */
        timeout_ns = get_random_u32_inclusive(100 * NSEC_PER_USEC,
                                              5 * NSEC_PER_MSEC);

        hrtimer_start(&data->nan.resume_txqs_timer,
                      ns_to_ktime(timeout_ns),
                      HRTIMER_MODE_REL_SOFT);
}

static void hwsim_nan_wake_sta_iter(void *_data, struct ieee80211_sta *sta)
{
        struct ieee80211_hw *hw = _data;

        for (int i = 0; i < ARRAY_SIZE(sta->txq); i++) {
                struct ieee80211_txq *txq = sta->txq[i];

                if (!txq)
                        continue;

                /* exit early if non-NAN */
                if (txq->vif->type != NL80211_IFTYPE_NAN &&
                    txq->vif->type != NL80211_IFTYPE_NAN_DATA)
                        return;

                if (mac80211_hwsim_nan_txq_transmitting(hw, txq))
                        ieee80211_hwsim_wake_tx_queue(hw, txq);
        }
}

enum hrtimer_restart
mac80211_hwsim_nan_resume_txqs_timer(struct hrtimer *timer)
{
        struct mac80211_hwsim_data *data =
                container_of(timer, struct mac80211_hwsim_data,
                             nan.resume_txqs_timer);

        guard(rcu)();

        /* Wake TX queue for management frames on the NAN device interface */
        if (mac80211_hwsim_nan_txq_transmitting(data->hw,
                                                data->nan.device_vif->txq_mgmt))
                ieee80211_hwsim_wake_tx_queue(data->hw,
                                              data->nan.device_vif->txq_mgmt);

        /* Wake TX queues for all stations */
        ieee80211_iterate_stations_atomic(data->hw,
                                          hwsim_nan_wake_sta_iter,
                                          data->hw);

        return HRTIMER_NORESTART;
}

static void
hwsim_nan_can_mcast_sta_transmit(void *_ctx, struct ieee80211_sta *sta)
{
        struct hwsim_nan_mcast_data_iter_ctx *ctx = _ctx;
        struct ieee80211_txq *txq = sta->txq[0];

        if (!txq || txq->vif != ctx->vif)
                return;

        ctx->n_vif_sta++;
        if (mac80211_hwsim_nan_txq_transmitting(ctx->hw, txq))
                ctx->n_sta_can_tx++;
}

static bool
mac80211_hwsim_nan_mcast_data_transmitting(struct ieee80211_hw *hw,
                                           struct ieee80211_txq *txq)
{
        struct mac80211_hwsim_data *data = hw->priv;
        struct hwsim_nan_mcast_data_iter_ctx ctx = {
                .hw = hw,
                .vif = txq->vif,
                .n_sta_can_tx = 0,
                .n_vif_sta = 0,
        };

        /* Check if all the stations associated with the current
         * interface are available.
         */
        ieee80211_iterate_stations_atomic(data->hw,
                                          hwsim_nan_can_mcast_sta_transmit,
                                          &ctx);

        return ctx.n_vif_sta && ctx.n_sta_can_tx == ctx.n_vif_sta;
}

bool mac80211_hwsim_nan_txq_transmitting(struct ieee80211_hw *hw,
                                         struct ieee80211_txq *txq)
{
        struct mac80211_hwsim_data *data = hw->priv;
        struct ieee80211_sta *nmi_sta;
        struct hwsim_sta_priv *sp;
        bool is_dw_slot;
        u64 tsf;
        u8 slot;

        if (WARN_ON_ONCE(!data->nan.device_vif))
                return true;

        tsf = mac80211_hwsim_get_tsf(hw, data->nan.device_vif);
        slot = hwsim_nan_slot_from_tsf(tsf);

        /* Enforce a maximum channel switch time and guard against TX delays */
        if (slot != hwsim_nan_slot_from_tsf(tsf + NAN_CHAN_SWITCH_TIME_US))
                return false;

        is_dw_slot = mac80211_hwsim_nan_is_dw_slot(data, slot);

        if (!txq->sta) {
                /* Non-STA TXQ: allow management frames during DW */
                if (txq->vif->type == NL80211_IFTYPE_NAN)
                        return is_dw_slot;

                /* Allow multicast data when all the peers are available
                 * on this slot
                 */
                return mac80211_hwsim_nan_mcast_data_transmitting(hw, txq);
        }

        /* STA TXQ: need peer schedule for availability check */
        nmi_sta = rcu_dereference(txq->sta->nmi) ?: txq->sta;
        sp = (void *)nmi_sta->drv_priv;

        /* DW slot: NDI can TX only mgmt but not worth checking,
         * NMI checks peer's committed DW
         */
        if (is_dw_slot) {
                if (txq->vif->type == NL80211_IFTYPE_NAN_DATA)
                        return false;
                return hwsim_nan_peer_present_in_dw(sp, tsf);
        }

        /* FAW slot: verify local schedule and peer availability */
        return hwsim_nan_peer_present_in_faw(sp, data, slot);
}

void mac80211_hwsim_nan_get_tx_chandef(struct ieee80211_hw *hw,
                                       struct cfg80211_chan_def *chandef)
{
        struct mac80211_hwsim_data *data = hw->priv;
        u64 tsf = mac80211_hwsim_get_tsf(data->hw, data->nan.device_vif);
        u8 slot = hwsim_nan_slot_from_tsf(tsf);

        /* DW slots are always 20 MHz */
        if (slot == SLOT_24GHZ_DW) {
                cfg80211_chandef_create(chandef,
                                        ieee80211_get_channel(hw->wiphy, 2437),
                                        NL80211_CHAN_NO_HT);
                return;
        }

        if (slot == SLOT_5GHZ_DW && data->nan.bands & BIT(NL80211_BAND_5GHZ)) {
                cfg80211_chandef_create(chandef,
                                        ieee80211_get_channel(hw->wiphy, 5745),
                                        NL80211_CHAN_NO_HT);
                return;
        }

        /* FAW slot: copy local schedule for this slot */
        scoped_guard(spinlock_bh, &data->nan.state_lock)
                *chandef = data->nan.local_sched[slot];
}

bool mac80211_hwsim_nan_receive(struct ieee80211_hw *hw,
                                struct ieee80211_channel *channel,
                                struct ieee80211_rx_status *rx_status)
{
        struct mac80211_hwsim_data *data = hw->priv;
        u8 slot;

        if (WARN_ON_ONCE(!data->nan.device_vif))
                return false;

        if (data->nan.phase == MAC80211_HWSIM_NAN_PHASE_SCAN)
                return channel->center_freq == 2437;

        if (rx_status->flag & RX_FLAG_MACTIME) {
                slot = hwsim_nan_slot_from_tsf(rx_status->mactime);
        } else {
                u64 tsf;

                /*
                 * This is not perfect, but that should be fine.
                 *
                 * Assume the frame might be a bit early in relation to our
                 * own TSF. This is largely because the TSF sync is going to be
                 * pretty bad when the frame was RXed via NL and the beacon as
                 * well as RX timestamps are not accurate.
                 */
                tsf = mac80211_hwsim_get_tsf(data->hw, data->nan.device_vif);
                slot = hwsim_nan_slot_from_tsf(tsf + 128);
        }

        if (slot == SLOT_24GHZ_DW && channel->center_freq == 2437)
                return true;

        if (slot == SLOT_5GHZ_DW && data->nan.bands & BIT(NL80211_BAND_5GHZ) &&
            channel->center_freq == 5745)
                return true;

        /* Accept frames during FAW slots if chandef is compatible */
        return hwsim_nan_rx_chandef_compatible(data, slot, channel,
                                               rx_status->bw);
}

void mac80211_hwsim_nan_local_sched_changed(struct ieee80211_hw *hw,
                                            struct ieee80211_vif *vif)
{
        struct mac80211_hwsim_data *data = hw->priv;
        struct ieee80211_nan_channel **slots = vif->cfg.nan_sched.schedule;

        if (WARN_ON(vif->type != NL80211_IFTYPE_NAN))
                return;

        spin_lock_bh(&data->nan.state_lock);

        for (int i = 0; i < ARRAY_SIZE(data->nan.local_sched); i++) {
                struct ieee80211_chanctx_conf *chanctx;

                if (!slots[i] || IS_ERR(slots[i])) {
                        memset(&data->nan.local_sched[i], 0,
                               sizeof(data->nan.local_sched[i]));
                        continue;
                }

                chanctx = slots[i]->chanctx_conf;
                if (!chanctx) {
                        memset(&data->nan.local_sched[i], 0,
                               sizeof(data->nan.local_sched[i]));
                        continue;
                }

                data->nan.local_sched[i] = chanctx->def;
        }

        spin_unlock_bh(&data->nan.state_lock);
}

int mac80211_hwsim_nan_peer_sched_changed(struct ieee80211_hw *hw,
                                          struct ieee80211_sta *sta)
{
        struct hwsim_sta_priv *sp = (void *)sta->drv_priv;
        struct ieee80211_nan_peer_sched *sched = sta->nan_sched;

        spin_lock_bh(&sp->nan_sched.lock);

        /* Clear existing schedule */
        sp->nan_sched.committed_dw = 0;
        for (int i = 0; i < CFG80211_NAN_MAX_PEER_MAPS; i++) {
                sp->nan_sched.maps[i].map_id = CFG80211_NAN_INVALID_MAP_ID;
                memset(sp->nan_sched.maps[i].chans, 0,
                       sizeof(sp->nan_sched.maps[i].chans));
        }

        if (!sched)
                goto out;

        sp->nan_sched.committed_dw = sched->committed_dw;

        for (int i = 0; i < CFG80211_NAN_MAX_PEER_MAPS; i++) {
                struct ieee80211_nan_peer_map *map = &sched->maps[i];

                if (map->map_id == CFG80211_NAN_INVALID_MAP_ID)
                        continue;

                sp->nan_sched.maps[i].map_id = map->map_id;

                for (int j = 0; j < CFG80211_NAN_SCHED_NUM_TIME_SLOTS; j++) {
                        struct ieee80211_nan_channel *peer_chan =
                                map->slots[j];

                        if (peer_chan && peer_chan->chanreq.oper.chan)
                                sp->nan_sched.maps[i].chans[j] =
                                        peer_chan->chanreq.oper;
                        else
                                memset(&sp->nan_sched.maps[i].chans[j], 0,
                                       sizeof(sp->nan_sched.maps[i].chans[j]));
                }
        }

out:
        spin_unlock_bh(&sp->nan_sched.lock);
        return 0;
}