]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
wifi: mac80211: support NAN stations
authorMiri Korenblit <miriam.rachel.korenblit@intel.com>
Thu, 26 Mar 2026 10:14:38 +0000 (12:14 +0200)
committerJohannes Berg <johannes.berg@intel.com>
Tue, 7 Apr 2026 13:36:03 +0000 (15:36 +0200)
Add support for both NMI and NDI stations.

The NDI station will be linked to the NMI station of the NAN peer for
which the NDI station is added.

A peer can choose to reuse its NMI address as the NDI address.
Since different keys might be in use for NAN management and for data
frames, we will have 2 different stations, even if they'll have the same
address.

Even though there are no links in NAN, sta->deflink will still be used
to store the one set of capabilities and SMPS mode.

Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
Link: https://patch.msgid.link/20260326121156.9fdd37b8e755.I7a7bd6e8e751cab49c329419485839afd209cfc6@changeid
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
include/net/mac80211.h
net/mac80211/cfg.c
net/mac80211/he.c
net/mac80211/ht.c
net/mac80211/iface.c
net/mac80211/sta_info.c
net/mac80211/sta_info.h
net/mac80211/util.c
net/mac80211/vht.c

index cc23f99f8318afbd00b55e6266d5d96ffd5a5b8d..b190d9035182d985cb8776aa26783fb01717fee6 100644 (file)
@@ -2534,11 +2534,15 @@ struct ieee80211_sta_aggregates {
  * @uhr_cap: UHR capabilities of this STA
  * @s1g_cap: S1G capabilities of this STA
  * @agg: per-link data for multi-link aggregation
- * @bandwidth: current bandwidth the station can receive with
+ * @bandwidth: current bandwidth the station can receive with.
+ *     This is the minimum between the peer's capabilities and our own
+ *     operating channel width; Invalid for NAN since that is operating on
+ *     multiple channels.
  * @rx_nss: in HT/VHT, the maximum number of spatial streams the
  *     station can receive at the moment, changed by operating mode
  *     notifications and capabilities. The value is only valid after
- *     the station moves to associated state.
+ *     the station moves to associated state. Invalid for NAN since it
+ *     operates on multiple configurations of rx_nss.
  * @txpwr: the station tx power configuration
  *
  */
@@ -2620,6 +2624,7 @@ struct ieee80211_link_sta {
  * @valid_links: bitmap of valid links, or 0 for non-MLO
  * @spp_amsdu: indicates whether the STA uses SPP A-MSDU or not.
  * @epp_peer: indicates that the peer is an EPP peer.
+ * @nmi: For NDI stations, pointer to the NMI station of the peer.
  */
 struct ieee80211_sta {
        u8 addr[ETH_ALEN] __aligned(2);
@@ -2648,6 +2653,8 @@ struct ieee80211_sta {
        struct ieee80211_link_sta deflink;
        struct ieee80211_link_sta __rcu *link[IEEE80211_MLD_MAX_NUM_LINKS];
 
+       struct ieee80211_sta __rcu *nmi;
+
        /* must be last */
        u8 drv_priv[] __aligned(sizeof(void *));
 };
@@ -2881,6 +2888,8 @@ struct ieee80211_txq {
  *     station has a unique address, i.e. each station entry can be identified
  *     by just its MAC address; this prevents, for example, the same station
  *     from connecting to two virtual AP interfaces at the same time.
+ *     Note that this doesn't apply for NAN, in which the peer's NMI address
+ *     can be equal to its NDI address.
  *
  * @IEEE80211_HW_SUPPORTS_REORDERING_BUFFER: Hardware (or driver) manages the
  *     reordering buffer internally, guaranteeing mac80211 receives frames in
index 5bc867a568111f9778cb3161753b2fa65df86b65..607f3bd3f8f8c59b356d86bed87d6d7458a3de23 100644 (file)
@@ -2077,7 +2077,7 @@ static int sta_link_apply_parameters(struct ieee80211_local *local,
                                     enum sta_link_apply_mode mode,
                                     struct link_station_parameters *params)
 {
-       struct ieee80211_supported_band *sband;
+       struct ieee80211_supported_band *sband = NULL;
        struct ieee80211_sub_if_data *sdata = sta->sdata;
        u32 link_id = params->link_id < 0 ? 0 : params->link_id;
        struct ieee80211_link_data *link =
@@ -2085,6 +2085,9 @@ static int sta_link_apply_parameters(struct ieee80211_local *local,
        struct link_sta_info *link_sta =
                rcu_dereference_protected(sta->link[link_id],
                                          lockdep_is_held(&local->hw.wiphy->mtx));
+       const struct ieee80211_sta_ht_cap *own_ht_cap;
+       const struct ieee80211_sta_vht_cap *own_vht_cap;
+       const struct ieee80211_sta_he_cap *own_he_cap;
        bool changes = params->link_mac ||
                       params->txpwr_set ||
                       params->supported_rates_len ||
@@ -2114,10 +2117,27 @@ static int sta_link_apply_parameters(struct ieee80211_local *local,
        if (!link || !link_sta)
                return -EINVAL;
 
-       sband = ieee80211_get_link_sband(link);
-       if (!sband)
+       /*
+        * We should not have any changes in NDI station, its capabilities are
+        * copied from the NMI sta
+        */
+       if (WARN_ON(sdata->vif.type == NL80211_IFTYPE_NAN_DATA))
                return -EINVAL;
 
+       if (sdata->vif.type == NL80211_IFTYPE_NAN) {
+               own_ht_cap = &local->hw.wiphy->nan_capa.phy.ht;
+               own_vht_cap = &local->hw.wiphy->nan_capa.phy.vht;
+               own_he_cap = &local->hw.wiphy->nan_capa.phy.he;
+       } else {
+               sband = ieee80211_get_link_sband(link);
+               if (!sband)
+                       return -EINVAL;
+
+               own_ht_cap = &sband->ht_cap;
+               own_vht_cap = &sband->vht_cap;
+               own_he_cap = ieee80211_get_he_iftype_cap_vif(sband, &sdata->vif);
+       }
+
        if (params->link_mac) {
                if (mode == STA_LINK_MODE_NEW) {
                        memcpy(link_sta->addr, params->link_mac, ETH_ALEN);
@@ -2139,6 +2159,27 @@ static int sta_link_apply_parameters(struct ieee80211_local *local,
                        return ret;
        }
 
+       if (sdata->vif.type == NL80211_IFTYPE_NAN) {
+               static const u8 all_ofdm_rates[] = {
+                       0x0c, 0x12, 0x18, 0x24, 0x30, 0x48, 0x60, 0x6c
+               };
+
+               /* Set the same supported_rates for all bands */
+               for (int i = 0; i < NUM_NL80211_BANDS; i++) {
+                       struct ieee80211_supported_band *tmp =
+                               sdata->local->hw.wiphy->bands[i];
+
+                       if ((i != NL80211_BAND_2GHZ && i != NL80211_BAND_5GHZ) ||
+                           !tmp)
+                               continue;
+
+                       if (!ieee80211_parse_bitrates(tmp, all_ofdm_rates,
+                                                     sizeof(all_ofdm_rates),
+                                                     &link_sta->pub->supp_rates[i]))
+                               return -EINVAL;
+               }
+       }
+
        if (params->supported_rates &&
            params->supported_rates_len &&
            !ieee80211_parse_bitrates(sband, params->supported_rates,
@@ -2147,22 +2188,24 @@ static int sta_link_apply_parameters(struct ieee80211_local *local,
                return -EINVAL;
 
        if (params->ht_capa)
-               ieee80211_ht_cap_ie_to_sta_ht_cap(sdata, &sband->ht_cap,
+               ieee80211_ht_cap_ie_to_sta_ht_cap(sdata, own_ht_cap,
                                                  params->ht_capa, link_sta);
 
        /* VHT can override some HT caps such as the A-MSDU max length */
        if (params->vht_capa)
                ieee80211_vht_cap_ie_to_sta_vht_cap(sdata, sband,
-                                                   &sband->vht_cap,
+                                                   own_vht_cap,
                                                    params->vht_capa, NULL,
                                                    link_sta);
 
        if (params->he_capa)
-               ieee80211_he_cap_ie_to_sta_he_cap(sdata, sband,
-                                                 (void *)params->he_capa,
-                                                 params->he_capa_len,
-                                                 (void *)params->he_6ghz_capa,
-                                                 link_sta);
+               _ieee80211_he_cap_ie_to_sta_he_cap(sdata,
+                                                  own_he_cap,
+                                                  (void *)params->he_capa,
+                                                  params->he_capa_len,
+                                                  (sband && sband->band == NL80211_BAND_6GHZ) ?
+                                                  (void *)params->he_6ghz_capa : NULL,
+                                                  link_sta);
 
        if (params->he_capa && params->eht_capa)
                ieee80211_eht_cap_ie_to_sta_eht_cap(sdata, sband,
@@ -2349,6 +2392,32 @@ static int sta_apply_parameters(struct ieee80211_local *local,
        if (params->airtime_weight)
                sta->airtime_weight = params->airtime_weight;
 
+       if (params->nmi_mac) {
+               struct ieee80211_sub_if_data *nmi =
+                       rcu_dereference_wiphy(local->hw.wiphy,
+                                             sdata->u.nan_data.nmi);
+               struct sta_info *nmi_sta;
+
+               if (WARN_ON(!nmi))
+                       return -EINVAL;
+
+               nmi_sta = sta_info_get(nmi, params->nmi_mac);
+               if (!nmi_sta)
+                       return -ENOENT;
+               rcu_assign_pointer(sta->sta.nmi, &nmi_sta->sta);
+
+               /* For NAN_DATA stations, copy capabilities from the NMI station */
+               if (!nmi_sta->deflink.pub->ht_cap.ht_supported)
+                       return -EINVAL;
+
+               sta->deflink.pub->ht_cap = nmi_sta->deflink.pub->ht_cap;
+               sta->deflink.pub->vht_cap = nmi_sta->deflink.pub->vht_cap;
+               sta->deflink.pub->he_cap = nmi_sta->deflink.pub->he_cap;
+               memcpy(&sta->deflink.pub->supp_rates,
+                      &nmi_sta->deflink.pub->supp_rates,
+                      sizeof(sta->deflink.pub->supp_rates));
+       }
+
        /* set the STA state after all sta info from usermode has been set */
        if (test_sta_flag(sta, WLAN_STA_TDLS_PEER) ||
            set & BIT(NL80211_STA_FLAG_ASSOCIATED)) {
@@ -2494,6 +2563,12 @@ static int ieee80211_change_station(struct wiphy *wiphy,
                else
                        statype = CFG80211_STA_AP_CLIENT_UNASSOC;
                break;
+       case NL80211_IFTYPE_NAN:
+               statype = CFG80211_STA_NAN_MGMT;
+               break;
+       case NL80211_IFTYPE_NAN_DATA:
+               statype = CFG80211_STA_NAN_DATA;
+               break;
        default:
                return -EOPNOTSUPP;
        }
@@ -2532,6 +2607,14 @@ static int ieee80211_change_station(struct wiphy *wiphy,
                }
        }
 
+       /* NAN capabilties should not change */
+       if (statype == CFG80211_STA_NAN_DATA &&
+           sta->deflink.pub->ht_cap.ht_supported &&
+           (params->link_sta_params.ht_capa ||
+            params->link_sta_params.vht_capa ||
+            params->link_sta_params.he_capa))
+               return -EINVAL;
+
        err = sta_apply_parameters(local, sta, params);
        if (err)
                return err;
index 93e0342cff4fab5df3ff547c5d7a8d3e43c9453e..a3e16a5bec2246615b3b13ccc4cdad455039721c 100644 (file)
@@ -127,6 +127,10 @@ _ieee80211_he_cap_ie_to_sta_he_cap(struct ieee80211_sub_if_data *sdata,
        if (!he_cap_ie || !own_he_cap_ptr || !own_he_cap_ptr->has_he)
                return;
 
+       /* NDI station are using the capabilities from the NMI station */
+       if (WARN_ON_ONCE(sdata->vif.type == NL80211_IFTYPE_NAN_DATA))
+               return;
+
        own_he_cap = *own_he_cap_ptr;
 
        /* Make sure size is OK */
@@ -156,7 +160,8 @@ _ieee80211_he_cap_ie_to_sta_he_cap(struct ieee80211_sub_if_data *sdata,
        he_cap->has_he = true;
 
        link_sta->cur_max_bandwidth = ieee80211_sta_cap_rx_bw(link_sta);
-       link_sta->pub->bandwidth = ieee80211_sta_cur_vht_bw(link_sta);
+       if (sdata->vif.type != NL80211_IFTYPE_NAN)
+               link_sta->pub->bandwidth = ieee80211_sta_cur_vht_bw(link_sta);
 
        if (he_6ghz_capa)
                ieee80211_update_from_he_6ghz_capa(he_6ghz_capa, link_sta);
index 410e2354f33aa609812f35eac33f57403c38d846..97719298e038a2b5ea7b44aa34cf457bbfea80df 100644 (file)
@@ -154,6 +154,10 @@ bool ieee80211_ht_cap_ie_to_sta_ht_cap(struct ieee80211_sub_if_data *sdata,
        if (!ht_cap_ie || !own_cap_ptr->ht_supported)
                goto apply;
 
+       /* NDI station are using the capabilities from the NMI station */
+       if (WARN_ON_ONCE(sdata->vif.type == NL80211_IFTYPE_NAN_DATA))
+               return 0;
+
        ht_cap.ht_supported = true;
 
        own_cap = *own_cap_ptr;
@@ -254,10 +258,17 @@ bool ieee80211_ht_cap_ie_to_sta_ht_cap(struct ieee80211_sub_if_data *sdata,
 
        rcu_read_lock();
        link_conf = rcu_dereference(sdata->vif.link_conf[link_sta->link_id]);
-       if (WARN_ON(!link_conf))
+       if (WARN_ON(!link_conf)) {
                width = NL80211_CHAN_WIDTH_20_NOHT;
-       else
+       } else if (sdata->vif.type == NL80211_IFTYPE_NAN ||
+                  sdata->vif.type == NL80211_IFTYPE_NAN_DATA) {
+               /* In NAN, link_sta->bandwidth is invalid since NAN operates on
+                * multiple channels. Just take the maximum.
+                */
+               width = NL80211_CHAN_WIDTH_320;
+       } else {
                width = link_conf->chanreq.oper.width;
+       }
 
        switch (width) {
        default:
@@ -285,7 +296,9 @@ bool ieee80211_ht_cap_ie_to_sta_ht_cap(struct ieee80211_sub_if_data *sdata,
                                IEEE80211_STA_RX_BW_40 : IEEE80211_STA_RX_BW_20;
 
        if (sta->sdata->vif.type == NL80211_IFTYPE_AP ||
-           sta->sdata->vif.type == NL80211_IFTYPE_AP_VLAN) {
+           sta->sdata->vif.type == NL80211_IFTYPE_AP_VLAN ||
+           sta->sdata->vif.type == NL80211_IFTYPE_NAN ||
+           sta->sdata->vif.type == NL80211_IFTYPE_NAN_DATA) {
                enum ieee80211_smps_mode smps_mode;
 
                switch ((ht_cap.cap & IEEE80211_HT_CAP_SM_PS)
index 507c5e016ec8ecf8df9c9eac53b84839f7ede2df..f1ab85ff326def08e0d4e1673145c4944d95ea7c 100644 (file)
@@ -535,12 +535,14 @@ static void ieee80211_do_stop(struct ieee80211_sub_if_data *sdata, bool going_do
         * (because if we remove a STA after ops->remove_interface()
         * the driver will have removed the vif info already!)
         *
-        * For AP_VLANs stations may exist since there's nothing else that
-        * would have removed them, but in other modes there shouldn't
-        * be any stations.
+        * For AP_VLANs, NAN and NAN_DATA stations may exist since there's
+        * nothing else that would have removed them, but in other modes there
+        * shouldn't be any stations.
         */
        flushed = sta_info_flush(sdata, -1);
-       WARN_ON_ONCE(sdata->vif.type != NL80211_IFTYPE_AP_VLAN && flushed > 0);
+       WARN_ON_ONCE(sdata->vif.type != NL80211_IFTYPE_AP_VLAN &&
+                    sdata->vif.type != NL80211_IFTYPE_NAN &&
+                    sdata->vif.type != NL80211_IFTYPE_NAN_DATA && flushed > 0);
 
        /* don't count this interface for allmulti while it is down */
        if (sdata->flags & IEEE80211_SDATA_ALLMULTI)
index 7923ee9eafab4e9fdb3fa99657fc107ad64767a3..580cd7a68beb59ad2dfc91407843f5b822d4b560 100644 (file)
@@ -795,6 +795,7 @@ struct sta_info *sta_info_alloc_with_link(struct ieee80211_sub_if_data *sdata,
 static int sta_info_insert_check(struct sta_info *sta)
 {
        struct ieee80211_sub_if_data *sdata = sta->sdata;
+       struct ieee80211_sta *same_addr_sta;
 
        lockdep_assert_wiphy(sdata->local->hw.wiphy);
 
@@ -810,13 +811,18 @@ static int sta_info_insert_check(struct sta_info *sta)
                    !is_valid_ether_addr(sta->sta.addr)))
                return -EINVAL;
 
+       if (!ieee80211_hw_check(&sdata->local->hw, NEEDS_UNIQUE_STA_ADDR))
+               return 0;
+
        /* The RCU read lock is required by rhashtable due to
         * asynchronous resize/rehash.  We also require the mutex
         * for correctness.
         */
        rcu_read_lock();
-       if (ieee80211_hw_check(&sdata->local->hw, NEEDS_UNIQUE_STA_ADDR) &&
-           ieee80211_find_sta_by_ifaddr(&sdata->local->hw, sta->addr, NULL)) {
+       same_addr_sta = ieee80211_find_sta_by_ifaddr(&sdata->local->hw,
+                                                    sta->addr, NULL);
+       /* For NAN, a peer can re-use */
+       if (same_addr_sta && same_addr_sta != rcu_access_pointer(sta->sta.nmi)) {
                rcu_read_unlock();
                return -ENOTUNIQ;
        }
@@ -1294,6 +1300,17 @@ static int __must_check __sta_info_destroy_part1(struct sta_info *sta)
 
        lockdep_assert_wiphy(local->hw.wiphy);
 
+       if (sdata->vif.type == NL80211_IFTYPE_NAN) {
+               struct sta_info *sta_iter, *tmp;
+
+               /* Remove all NDI stations associated with this NMI STA */
+               list_for_each_entry_safe(sta_iter, tmp, &local->sta_list, list) {
+                       if (rcu_access_pointer(sta_iter->sta.nmi) != &sta->sta)
+                               continue;
+                       sta_info_destroy_addr(sta_iter->sdata, sta_iter->addr);
+               }
+       }
+
        /*
         * Before removing the station from the driver and
         * rate control, it might still start new aggregation
index 58ccbea7f6f6090b032618385c9127ef3fe41971..3e5d003bd31f183460a3daf1d6e45862e50a78db 100644 (file)
@@ -505,7 +505,8 @@ struct ieee80211_fragment_cache {
  * @status_stats.ack_signal_filled: last ACK signal validity
  * @status_stats.avg_ack_signal: average ACK signal
  * @cur_max_bandwidth: maximum bandwidth to use for TX to the station,
- *     taken from HT/VHT capabilities or VHT operating mode notification
+ *     taken from HT/VHT capabilities or VHT operating mode notification.
+ *     Invalid for NAN since that is operating on multiple bands.
  * @rx_omi_bw_rx: RX OMI bandwidth restriction to apply for RX
  * @rx_omi_bw_tx: RX OMI bandwidth restriction to apply for TX
  * @rx_omi_bw_staging: RX OMI bandwidth restriction to apply later
index 925a09246ad912961250debc002603eae88f6520..a352f73a7ec4ec9528a729f2c820a7374d5ef2ac 100644 (file)
@@ -1782,6 +1782,7 @@ static int ieee80211_reconfig_nan(struct ieee80211_sub_if_data *sdata)
 {
        struct ieee80211_local *local = sdata->local;
        struct ieee80211_sub_if_data *ndi_sdata;
+       struct sta_info *sta;
        int res;
 
        res = drv_start_nan(local, sdata, &sdata->u.nan.conf);
@@ -1802,6 +1803,42 @@ static int ieee80211_reconfig_nan(struct ieee80211_sub_if_data *sdata)
                }
        }
 
+       /* Add NMI stations (stations on the NAN interface) */
+       list_for_each_entry(sta, &local->sta_list, list) {
+               enum ieee80211_sta_state state;
+
+               if (!sta->uploaded || sta->sdata != sdata)
+                       continue;
+
+               for (state = IEEE80211_STA_NOTEXIST; state < sta->sta_state;
+                    state++) {
+                       res = drv_sta_state(local, sdata, sta, state,
+                                           state + 1);
+                       if (WARN_ON(res))
+                               return res;
+               }
+       }
+
+       /* Add NDI stations (stations on NAN_DATA interfaces) */
+       list_for_each_entry(sta, &local->sta_list, list) {
+               enum ieee80211_sta_state state;
+
+               if (!sta->uploaded ||
+                   sta->sdata->vif.type != NL80211_IFTYPE_NAN_DATA)
+                       continue;
+
+               if (WARN_ON(!sta->sta.nmi))
+                       continue;
+
+               for (state = IEEE80211_STA_NOTEXIST; state < sta->sta_state;
+                    state++) {
+                       res = drv_sta_state(local, sta->sdata, sta, state,
+                                           state + 1);
+                       if (WARN_ON(res))
+                               return res;
+               }
+       }
+
        return 0;
 }
 
@@ -2060,6 +2097,10 @@ int ieee80211_reconfig(struct ieee80211_local *local)
                case NL80211_IFTYPE_AP_VLAN:
                case NL80211_IFTYPE_MONITOR:
                        break;
+               case NL80211_IFTYPE_NAN:
+               case NL80211_IFTYPE_NAN_DATA:
+                       /* NAN stations are handled later */
+                       break;
                case NL80211_IFTYPE_ADHOC:
                        if (sdata->vif.cfg.ibss_joined)
                                WARN_ON(drv_join_ibss(local, sdata));
index a6570781740adadd703f94e6142169c9cc8d4026..f3bb5a561a38a673216db7b2190d55471259dabd 100644 (file)
@@ -133,6 +133,10 @@ ieee80211_vht_cap_ie_to_sta_vht_cap(struct ieee80211_sub_if_data *sdata,
        if (!vht_cap_ie || !own_vht_cap->vht_supported)
                return;
 
+       /* NDI station are using the capabilities from the NMI station */
+       if (WARN_ON_ONCE(sdata->vif.type == NL80211_IFTYPE_NAN_DATA))
+               return;
+
        if (sband) {
                /* Allow VHT if at least one channel on the sband supports 80 MHz */
                bool have_80mhz = false;
@@ -320,7 +324,8 @@ ieee80211_vht_cap_ie_to_sta_vht_cap(struct ieee80211_sub_if_data *sdata,
                                IEEE80211_STA_RX_BW_160;
        }
 
-       link_sta->pub->bandwidth = ieee80211_sta_cur_vht_bw(link_sta);
+       if (sdata->vif.type != NL80211_IFTYPE_NAN)
+               link_sta->pub->bandwidth = ieee80211_sta_cur_vht_bw(link_sta);
 
        /*
         * Work around the Cisco 9115 FW 17.3 bug by taking the min of
@@ -373,6 +378,10 @@ __ieee80211_sta_cap_rx_bw(struct link_sta_info *link_sta,
                } else {
                        struct ieee80211_bss_conf *link_conf;
 
+                       if (WARN_ON_ONCE(sdata->vif.type == NL80211_IFTYPE_NAN_DATA ||
+                                        sdata->vif.type == NL80211_IFTYPE_NAN))
+                               return IEEE80211_STA_RX_BW_20;
+
                        rcu_read_lock();
                        link_conf = rcu_dereference(sdata->vif.link_conf[link_id]);
                        band = link_conf->chanreq.oper.chan->band;
@@ -518,6 +527,11 @@ _ieee80211_sta_cur_vht_bw(struct link_sta_info *link_sta,
        } else {
                struct ieee80211_bss_conf *link_conf;
 
+               /* NAN operates on multiple channels so a chandef must be given */
+               if (WARN_ON_ONCE(sta->sdata->vif.type == NL80211_IFTYPE_NAN ||
+                                sta->sdata->vif.type == NL80211_IFTYPE_NAN_DATA))
+                       return IEEE80211_STA_RX_BW_20;
+
                rcu_read_lock();
                link_conf = rcu_dereference(sta->sdata->vif.link_conf[link_sta->link_id]);
                if (WARN_ON_ONCE(!link_conf)) {