]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
wifi: iwlwifi: mld: Add support for NAN multicast data
authorIlan Peer <ilan.peer@intel.com>
Wed, 13 May 2026 05:44:01 +0000 (08:44 +0300)
committerMiri Korenblit <miriam.rachel.korenblit@intel.com>
Tue, 26 May 2026 12:17:12 +0000 (15:17 +0300)
The FW added a new station type to allow NAN multicast
data to be transmitted. The queue of the station is
opened by the firmware when all the NAN Peer NDI stations
associated with the NDI are scheduled at the same time.

Add the corresponding support in iwlmld:

- When a NAN data interface is added, add a NAN multicast
  data station and allocate a queue to it.
- When a NAN data interface is removed, remove the NAN
  multicast station.
- On the Tx path, when a multicast data frame is received
  on a NAN data interface, direct it to the NAN multicast
  data queue.

Signed-off-by: Ilan Peer <ilan.peer@intel.com>
Link: https://patch.msgid.link/20260513084215.1d8e3463717d.I57deaea42bce9afee63f284a57eb8755485e7ea8@changeid
Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
drivers/net/wireless/intel/iwlwifi/fw/api/mac-cfg.h
drivers/net/wireless/intel/iwlwifi/mld/iface.c
drivers/net/wireless/intel/iwlwifi/mld/iface.h
drivers/net/wireless/intel/iwlwifi/mld/mac80211.c
drivers/net/wireless/intel/iwlwifi/mld/nan.c
drivers/net/wireless/intel/iwlwifi/mld/nan.h
drivers/net/wireless/intel/iwlwifi/mld/sta.c
drivers/net/wireless/intel/iwlwifi/mld/sta.h
drivers/net/wireless/intel/iwlwifi/mld/tx.c

index 604281eb29c783343a1bf26dd438af2fa4ce7723..d03a2bda4e688fa5dfb75380ea9a0445a61614b6 100644 (file)
@@ -747,6 +747,8 @@ struct iwl_link_config_cmd {
  *     discovery. No queue is associated with this station.
  * @STATION_TYPE_NAN_MGMT: NAN station used for NAN management frames, e.g.,
  *     SDFs and NAFs.
+ * @STATION_TYPE_NAN_MCAST_DATA: NAN station used for multicast NAN data
+ *     frames.
  * @STATION_TYPE_MAX: maximum number of FW station types
  * @STATION_TYPE_AUX: aux sta. In the FW there is no need for a special type
  *     for the aux sta, so this type is only for driver - internal use.
@@ -759,6 +761,7 @@ enum iwl_fw_sta_type {
        STATION_TYPE_NAN_PEER_NDI,
        STATION_TYPE_NAN_BCAST,
        STATION_TYPE_NAN_MGMT,
+       STATION_TYPE_NAN_MCAST_DATA,
        STATION_TYPE_MAX,
        STATION_TYPE_AUX = STATION_TYPE_MAX /* this doesn't exist in FW */
 }; /* STATION_TYPE_E_VER_1, _VER_2 */
@@ -933,7 +936,8 @@ struct iwl_sta_cfg_cmd_v2 {
  * @mic_prep_pad_delay: MIC prep time padding
  * @mic_compute_pad_delay: MIC compute time padding
  * @nmi_sta_id: for an NDI peer STA, the NMI peer STA ID it relates to
- * @ndi_local_addr: for an NDI peer STA, the local NDI interface MAC address
+ * @ndi_local_addr: for an NDI peer STA or NAN multicast data station,
+ *     the local NDI interface MAC address
  * @reserved: Reserved for alignment
  */
 struct iwl_sta_cfg_cmd {
index f7a0a8efe3741af405855c1e3fe4931f3088fe13..399efeb469f6e7f3e6dd0fc75b80fd87fdd911d6 100644 (file)
@@ -68,6 +68,10 @@ void iwl_mld_cleanup_vif(void *data, u8 *mac, struct ieee80211_vif *vif)
                        iwl_mld_free_internal_sta(mld, &mld_vif->nan.mgmt_sta);
        }
 
+       if (vif->type == NL80211_IFTYPE_NAN_DATA &&
+           mld_vif->nan.mcast_data_sta.sta_id != IWL_INVALID_STA)
+               iwl_mld_free_internal_sta(mld, &mld_vif->nan.mcast_data_sta);
+
        CLEANUP_STRUCT(mld_vif);
 }
 
@@ -534,6 +538,8 @@ iwl_mld_init_vif(struct iwl_mld *mld, struct ieee80211_vif *vif)
 
                iwl_mld_init_internal_sta(&mld_vif->nan.bcast_sta);
                iwl_mld_init_internal_sta(&mld_vif->nan.mgmt_sta);
+       } else if (vif->type == NL80211_IFTYPE_NAN_DATA) {
+               iwl_mld_init_internal_sta(&mld_vif->nan.mcast_data_sta);
        }
 
        iwl_mld_init_internal_sta(&mld_vif->aux_sta);
index 75b6727503d3c3525e57cd14b1cd64ff76c2ce87..c6a7588df1fa57b109fbf2e0d1c7da9195e24d26 100644 (file)
@@ -159,6 +159,7 @@ struct iwl_mld_emlsr {
  *     activities. No queue is associated with it.
  * @nan.mgmt_sta: internal station used for NAN management frames, e.g., SDFs
  *     and NAFs.
+ * @nan.mcast_data_sta: internal station used for multicast NAN Data frames.
  */
 struct iwl_mld_vif {
        /* Add here fields that need clean up on restart */
@@ -187,6 +188,7 @@ struct iwl_mld_vif {
                bool mac_added;
                struct iwl_mld_int_sta bcast_sta;
                struct iwl_mld_int_sta mgmt_sta;
+               struct iwl_mld_int_sta mcast_data_sta;
        } nan;
 
        struct iwl_mld_emlsr emlsr;
index 1f2843af39c15021e99cdd13a3308f5cac4a96d4..b6df09812daefde5dc94632f78ea824c17a30cf8 100644 (file)
@@ -663,8 +663,27 @@ int iwl_mld_mac80211_add_interface(struct ieee80211_hw *hw,
        if (ret)
                return ret;
 
-       if (vif->type == NL80211_IFTYPE_NAN_DATA)
+       if (vif->type == NL80211_IFTYPE_NAN_DATA) {
+               if (WARN_ON(!mld->nan_device_vif)) {
+                       ret = -EINVAL;
+                       goto err;
+               }
+
+               if (iwl_mld_nan_use_nan_stations(mld)) {
+                       struct iwl_mld_vif *mld_vif =
+                               iwl_mld_vif_from_mac80211(vif);
+                       struct iwl_mld_int_sta *sta =
+                               &mld_vif->nan.mcast_data_sta;
+
+                       ret = iwl_mld_add_nan_mcast_data_sta(mld,
+                                                            vif->addr,
+                                                            sta);
+                       if (ret)
+                               goto err;
+               }
+
                return 0;
+       }
 
        /*
         * Add the default link, but not if this is an MLD vif as that implies
@@ -732,10 +751,17 @@ void iwl_mld_mac80211_remove_interface(struct ieee80211_hw *hw,
        if (vif->type == NL80211_IFTYPE_P2P_DEVICE)
                mld->p2p_device_vif = NULL;
 
-       if (vif->type == NL80211_IFTYPE_NAN)
+       if (vif->type == NL80211_IFTYPE_NAN) {
                mld->nan_device_vif = NULL;
-       else if (vif->type != NL80211_IFTYPE_NAN_DATA)
+       } else if (vif->type != NL80211_IFTYPE_NAN_DATA) {
                iwl_mld_remove_link(mld, &vif->bss_conf);
+       } else if (iwl_mld_nan_use_nan_stations(mld)) {
+               struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif);
+               struct iwl_mld_int_sta *sta = &mld_vif->nan.mcast_data_sta;
+
+               if (sta->sta_id != IWL_INVALID_STA)
+                       iwl_mld_remove_nan_mcast_data_sta(mld, sta);
+       }
 
 #ifdef CONFIG_IWLWIFI_DEBUGFS
        debugfs_remove(iwl_mld_vif_from_mac80211(vif)->dbgfs_slink);
index 8f35687c58b1832b5c5119816cb1c99967606038..530ba263c5f04f30f7a293f456de72289ddb4682 100644 (file)
@@ -50,7 +50,7 @@ static int iwl_mld_nan_send_config_cmd(struct iwl_mld *mld,
        return iwl_mld_send_cmd(mld, &hcmd);
 }
 
-static bool iwl_mld_nan_use_nan_stations(struct iwl_mld *mld)
+bool iwl_mld_nan_use_nan_stations(struct iwl_mld *mld)
 {
        /*
         * If the FW supports version 1 of the NAN config command, it means that
@@ -650,6 +650,20 @@ iwl_mld_nan_find_link(struct iwl_mld_vif *mld_vif,
        return NULL;
 }
 
+static void iwl_mld_nan_set_mcast_data_links(struct ieee80211_vif *vif)
+{
+       struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif);
+
+       if (vif->type != NL80211_IFTYPE_NAN_DATA)
+               return;
+
+       /* Note that all errors are handled internally so nothing to do
+        * with the return value (used only to silence compilation warnings)
+        */
+       iwl_mld_update_nan_mcast_data_sta(mld_vif->mld, vif->addr,
+                                         &mld_vif->nan.mcast_data_sta);
+}
+
 void iwl_mld_nan_vif_cfg_changed(struct iwl_mld *mld,
                                 struct ieee80211_vif *vif,
                                 u64 changes)
@@ -807,6 +821,19 @@ void iwl_mld_nan_vif_cfg_changed(struct iwl_mld *mld,
                            mld_sta->sta_type == STATION_TYPE_NAN_PEER_NDI)
                                iwl_mld_add_modify_sta_cmd(mld, &sta->deflink);
                }
+
+               /*
+                * Iterate over all the NAN Data interfaces and update the links
+                * for the internal multicast data station.
+                * In recovery - the station will be added later in
+                * drv_add_interface
+                */
+               if (iwl_mld_nan_use_nan_stations(mld) && !mld->fw_status.in_hw_restart) {
+                       struct ieee80211_vif *iter;
+
+                       for_each_active_interface(iter, mld->hw)
+                               iwl_mld_nan_set_mcast_data_links(iter);
+               }
        }
 
        /* delete unused links */
index 40152dc7d906aac75e55825430ecea7a176a53af..f4e4cd4b4e8d8dd24ca897b2a612f95519cbebdc 100644 (file)
@@ -60,4 +60,6 @@ int iwl_mld_mac802111_nan_peer_sched_changed(struct ieee80211_hw *hw,
 
 int iwl_mld_nan_get_mgmt_queue(struct iwl_mld *mld, struct ieee80211_vif *vif);
 
+bool iwl_mld_nan_use_nan_stations(struct iwl_mld *mld);
+
 #endif /* __iwl_mld_nan_h__ */
index 4c168ad53b0f85c02188cb1dea16ccb6e2b79f75..cd13238ed613d7977aed7fd7398e389535e79bec 100644 (file)
@@ -428,6 +428,10 @@ static int iwl_mld_send_sta_cmd(struct iwl_mld *mld,
 
                len = sizeof(struct iwl_sta_cfg_cmd_v2);
                cmd_v2->link_id = cpu_to_le32(__ffs(le32_to_cpu(cmd->link_mask)));
+       } else if (cmd->station_type ==
+                  cpu_to_le32(STATION_TYPE_NAN_MCAST_DATA)) {
+               if (WARN_ON(!hweight32(le32_to_cpu(cmd->link_mask))))
+                       return -EINVAL;
        } else if (WARN_ON(cmd->station_type != cpu_to_le32(STATION_TYPE_NAN_PEER_NMI) &&
                           cmd->station_type != cpu_to_le32(STATION_TYPE_NAN_PEER_NDI) &&
                           cmd->station_type != cpu_to_le32(STATION_TYPE_NAN_BCAST) &&
@@ -442,6 +446,19 @@ static int iwl_mld_send_sta_cmd(struct iwl_mld *mld,
        return ret;
 }
 
+static u32 iwl_mld_get_nan_link_mask(struct iwl_mld *mld)
+{
+       struct iwl_mld_vif *nan_dev =
+               iwl_mld_vif_from_mac80211(mld->nan_device_vif);
+       struct iwl_mld_nan_link *nan_link;
+       u32 link_mask = 0;
+
+       for_each_mld_nan_valid_link(nan_dev, nan_link)
+               link_mask |= BIT(nan_link->fw_id);
+
+       return link_mask;
+}
+
 int iwl_mld_add_modify_sta_cmd(struct iwl_mld *mld,
                              struct ieee80211_link_sta *link_sta)
 {
@@ -459,21 +476,13 @@ int iwl_mld_add_modify_sta_cmd(struct iwl_mld *mld,
 
        if (mld_sta->sta_type == STATION_TYPE_NAN_PEER_NMI ||
            mld_sta->sta_type == STATION_TYPE_NAN_PEER_NDI) {
-               struct iwl_mld_nan_link *nan_link;
-               struct iwl_mld_vif *nan_dev;
-
-               is_6ghz = false;
-               uora_exists = false;
-
                if (WARN_ON(!mld->nan_device_vif))
                        return -EINVAL;
 
-               nan_dev = iwl_mld_vif_from_mac80211(mld->nan_device_vif);
-
-               link_mask = 0;
+               is_6ghz = false;
+               uora_exists = false;
 
-               for_each_mld_nan_valid_link(nan_dev, nan_link)
-                       link_mask |= BIT(nan_link->fw_id);
+               link_mask = iwl_mld_get_nan_link_mask(mld);
        } else {
                struct ieee80211_bss_conf *link;
                struct iwl_mld_link *mld_link;
@@ -1063,7 +1072,7 @@ static int iwl_mld_send_aux_sta_cmd(struct iwl_mld *mld,
 }
 
 static int
-iwl_mld_add_internal_sta_to_fw(struct iwl_mld *mld,
+iwl_mld_set_internal_sta_to_fw(struct iwl_mld *mld,
                               const struct iwl_mld_int_sta *internal_sta,
                               u32 link_mask, const u8 *addr)
 {
@@ -1085,8 +1094,12 @@ iwl_mld_add_internal_sta_to_fw(struct iwl_mld *mld,
                cmd.mfp = cpu_to_le32(1);
 
        if (addr) {
-               memcpy(cmd.peer_mld_address, addr, ETH_ALEN);
-               memcpy(cmd.peer_link_address, addr, ETH_ALEN);
+               if (internal_sta->sta_type == STATION_TYPE_NAN_MCAST_DATA) {
+                       ether_addr_copy(cmd.ndi_local_addr, addr);
+               } else {
+                       memcpy(cmd.peer_mld_address, addr, ETH_ALEN);
+                       memcpy(cmd.peer_link_address, addr, ETH_ALEN);
+               }
        }
 
        return iwl_mld_send_sta_cmd(mld, &cmd);
@@ -1108,7 +1121,7 @@ static int iwl_mld_add_internal_sta(struct iwl_mld *mld,
 
        internal_sta->sta_type = sta_type;
 
-       ret = iwl_mld_add_internal_sta_to_fw(mld, internal_sta, link_mask,
+       ret = iwl_mld_set_internal_sta_to_fw(mld, internal_sta, link_mask,
                                             addr);
        if (ret)
                goto err;
@@ -1432,6 +1445,51 @@ int iwl_mld_add_nan_mgmt_sta(struct iwl_mld *mld,
                                        0, NULL, IWL_MAX_TID_COUNT, true);
 }
 
+int iwl_mld_add_nan_mcast_data_sta(struct iwl_mld *mld,
+                                  const u8 *ndi_addr,
+                                  struct iwl_mld_int_sta *sta)
+{
+       u32 link_mask = iwl_mld_get_nan_link_mask(mld);
+
+       /* In case that there are no NAN links, nothing to do */
+       if (!link_mask)
+               return 0;
+
+       return iwl_mld_add_internal_sta(mld, sta,
+                                       STATION_TYPE_NAN_MCAST_DATA,
+                                       link_mask, ndi_addr,
+                                       0, true);
+}
+
+int iwl_mld_update_nan_mcast_data_sta(struct iwl_mld *mld,
+                                     const u8 *ndi_addr,
+                                     struct iwl_mld_int_sta *sta)
+{
+       u32 link_mask;
+
+       if (WARN_ON(!mld->nan_device_vif))
+               return -EINVAL;
+
+       /* If the sta doesn't exist, add it */
+       if (sta->sta_id == IWL_INVALID_STA)
+               return iwl_mld_add_nan_mcast_data_sta(mld, ndi_addr, sta);
+
+       /* The station was already added */
+       if (WARN_ON(sta->sta_type != STATION_TYPE_NAN_MCAST_DATA))
+               return -EINVAL;
+
+       link_mask = iwl_mld_get_nan_link_mask(mld);
+       if (link_mask)
+               return iwl_mld_set_internal_sta_to_fw(mld,
+                                                     sta, link_mask,
+                                                     ndi_addr);
+
+       /* If no links are associated with NAN, remove the station */
+       iwl_mld_remove_nan_mcast_data_sta(mld, sta);
+
+       return 0;
+}
+
 void iwl_mld_remove_nan_bcast_sta(struct iwl_mld *mld,
                                  struct iwl_mld_int_sta *sta)
 {
@@ -1443,3 +1501,9 @@ void iwl_mld_remove_nan_mgmt_sta(struct iwl_mld *mld,
 {
        iwl_mld_remove_internal_sta(mld, sta, true, IWL_MAX_TID_COUNT);
 }
+
+void iwl_mld_remove_nan_mcast_data_sta(struct iwl_mld *mld,
+                                      struct iwl_mld_int_sta *sta)
+{
+       iwl_mld_remove_internal_sta(mld, sta, true, 0);
+}
index df859a9e52300f2028d18722c21593bd048ee2a8..98d693235c663c38eba74c0b7b6ac1eef7f0da53 100644 (file)
@@ -283,10 +283,21 @@ int iwl_mld_add_nan_bcast_sta(struct iwl_mld *mld,
 int iwl_mld_add_nan_mgmt_sta(struct iwl_mld *mld,
                             struct iwl_mld_int_sta *sta);
 
+int iwl_mld_add_nan_mcast_data_sta(struct iwl_mld *mld,
+                                  const u8 *ndi_addr,
+                                  struct iwl_mld_int_sta *sta);
+
+int iwl_mld_update_nan_mcast_data_sta(struct iwl_mld *mld,
+                                     const u8 *ndi_addr,
+                                     struct iwl_mld_int_sta *sta);
+
 void iwl_mld_remove_nan_bcast_sta(struct iwl_mld *mld,
                                  struct iwl_mld_int_sta *sta);
 
 void iwl_mld_remove_nan_mgmt_sta(struct iwl_mld *mld,
                                 struct iwl_mld_int_sta *sta);
 
+void iwl_mld_remove_nan_mcast_data_sta(struct iwl_mld *mld,
+                                      struct iwl_mld_int_sta *sta);
+
 #endif /* __iwl_mld_sta_h__ */
index 39e1d959e42c2323f24b71852042c3bccd0765e8..1100f7f1e76f37e7789b81c8928866d219be241b 100644 (file)
@@ -351,7 +351,8 @@ u8 iwl_mld_get_lowest_rate(struct iwl_mld *mld,
        iwl_mld_get_basic_rates_and_band(mld, vif, info, &basic_rates, &band);
 
        if (band >= NUM_NL80211_BANDS) {
-               WARN_ON(vif->type != NL80211_IFTYPE_NAN);
+               WARN_ON(vif->type != NL80211_IFTYPE_NAN &&
+                       vif->type != NL80211_IFTYPE_NAN_DATA);
                return IWL_FIRST_OFDM_RATE;
        }
 
@@ -682,6 +683,14 @@ iwl_mld_get_tx_queue_id(struct iwl_mld *mld, struct ieee80211_txq *txq,
        case NL80211_IFTYPE_NAN:
                WARN_ON(!ieee80211_is_mgmt(fc));
                return iwl_mld_nan_get_mgmt_queue(mld, info->control.vif);
+       case NL80211_IFTYPE_NAN_DATA:
+               WARN_ON(!ieee80211_is_data(fc));
+
+               if (!iwl_mld_nan_use_nan_stations(mld))
+                       break;
+
+               mld_vif = iwl_mld_vif_from_mac80211(info->control.vif);
+               return mld_vif->nan.mcast_data_sta.queue_id;
        default:
                WARN_ONCE(1, "Unsupported vif type\n");
                break;