From: Aaron Katzin Log when the resume flow identifies based on the scratch register that the device was powered off. Signed-off-by: Aaron Katzin Reviewed-by: Emmanuel Grumbach Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/pcie/drv.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/intel/iwlwifi/pcie/drv.c b/drivers/net/wireless/intel/iwlwifi/pcie/drv.c index e0be899b8fca..58a7ae33c3ff 100644 --- a/drivers/net/wireless/intel/iwlwifi/pcie/drv.c +++ b/drivers/net/wireless/intel/iwlwifi/pcie/drv.c @@ -1240,8 +1240,12 @@ static int _iwl_pci_resume(struct device *device, bool restore) u32 scratch = iwl_read32(trans, CSR_FUNC_SCRATCH); if (!(scratch & CSR_FUNC_SCRATCH_POWER_OFF_MASK) || - scratch == ~0U) + scratch == ~0U) { device_was_powered_off = true; + IWL_DEBUG_WOWLAN(trans, + "Scratch 0x%08x indicates device was powered off\n", + scratch); + } } else { /* * bh are re-enabled by iwl_trans_pcie_release_nic_access, -- 2.34.1 From: Johannes Berg The only flags that could reasonably be used here are CMD_WANT_SKB and CMD_ASYNC, CMD_SEND_IN_RFKILL doesn't really make sense and CMD_BLOCK_TXQS just triggers a warning, as does CMD_WANT_SKB | CMD_ASYNC. Clear CMD_WANT_SKB since the response SKB isn't used anyway, and refuse flags other than CMD_ASYNC to avoid the warnings or other issues. Signed-off-by: Johannes Berg Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/fw/debugfs.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/fw/debugfs.c b/drivers/net/wireless/intel/iwlwifi/fw/debugfs.c index ddee7c2deb36..f06978d5b5ee 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/debugfs.c +++ b/drivers/net/wireless/intel/iwlwifi/fw/debugfs.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause /* - * Copyright (C) 2012-2014, 2018-2024 Intel Corporation + * Copyright (C) 2012-2014, 2018-2024, 2026 Intel Corporation * Copyright (C) 2013-2015 Intel Mobile Communications GmbH * Copyright (C) 2016-2017 Intel Deutschland GmbH */ @@ -275,16 +275,19 @@ static ssize_t iwl_dbgfs_send_hcmd_write(struct iwl_fw_runtime *fwrt, char *buf, goto out; } + /* ignore this flag, we cannot use the response */ + hcmd.flags &= ~CMD_WANT_SKB; + /* reject flags other than async, they cannot be used this way */ + if (hcmd.flags & ~CMD_ASYNC) { + ret = -EINVAL; + goto out; + } + if (fwrt->ops && fwrt->ops->send_hcmd) ret = fwrt->ops->send_hcmd(fwrt->ops_ctx, &hcmd); else ret = -EPERM; - if (ret < 0) - goto out; - - if (hcmd.flags & CMD_WANT_SKB) - iwl_free_resp(&hcmd); out: kfree(data); return ret ?: count; -- 2.34.1 From: Johannes Berg Some firmware files can be used for different MACs, for example for sc2/sc2f, yet might have different FSEQ versions. The files will then contain multiple bigger FSEQ TLVs indicating the MAC ID in addition to the version. For now, since we don't parse this, define only the new format. Signed-off-by: Johannes Berg Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/fw/file.h | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/intel/iwlwifi/fw/file.h b/drivers/net/wireless/intel/iwlwifi/fw/file.h index 68ddd99a9f7d..197c88c25f72 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/file.h +++ b/drivers/net/wireless/intel/iwlwifi/fw/file.h @@ -1064,10 +1064,15 @@ struct iwl_fw_dump_exclude { __le32 addr, size; }; -struct iwl_fw_fseq_bin_version { +struct iwl_fw_fseq_bin_version_v1 { __le32 major, minor; }; /* FW_TLV_FSEQ_BIN_VERSION_S */ +struct iwl_fw_fseq_bin_version { + /* rf_id is currently unused and always zero */ + __le32 mac_id, rf_id, major, minor; +}; /* FW_TLV_FSEQ_BIN_VERSION_S */ + static inline size_t _iwl_tlv_array_len(const struct iwl_ucode_tlv *tlv, size_t fixed_size, size_t var_size) { -- 2.34.1 From: Johannes Berg On debugfs reset, set the transport FW state to NO_FW so that the restart won't attempt to send commands, which of course fails because the FW was killed during the error dump. Use iwl_trans_fw_error() now since that's effectively the same as the old code plus setting the state. Fixes: e5d110fec068 ("wifi: iwlwifi: pcie: fix locking on invalid TOP reset") Signed-off-by: Johannes Berg Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/pcie/gen1_2/trans.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/pcie/gen1_2/trans.c b/drivers/net/wireless/intel/iwlwifi/pcie/gen1_2/trans.c index 9f4c99dca195..e3603571bdd9 100644 --- a/drivers/net/wireless/intel/iwlwifi/pcie/gen1_2/trans.c +++ b/drivers/net/wireless/intel/iwlwifi/pcie/gen1_2/trans.c @@ -3201,8 +3201,7 @@ static ssize_t iwl_dbgfs_reset_write(struct file *file, return -EINVAL; trans->request_top_reset = 1; } - iwl_op_mode_nic_error(trans->op_mode, IWL_ERR_TYPE_DEBUGFS); - iwl_trans_schedule_reset(trans, IWL_ERR_TYPE_DEBUGFS); + iwl_trans_fw_error(trans, IWL_ERR_TYPE_DEBUGFS); return count; } -- 2.34.1 From: Avraham Stern The driver supports intalling IGTK on a NAN device interface. When the IGTK is removed, iwl_mld_free_ap_early_key() is called which results in a warning since no links are attached to this interface. The iwl_mld_free_ap_early_key() function should be called for AP or IBSS interfaces only. Check the interface type before calling it. Signed-off-by: Avraham Stern Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/mld/mac80211.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c b/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c index 4315b1b2b36b..41bc47e4e00c 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c @@ -2227,7 +2227,9 @@ static void iwl_mld_set_key_remove(struct iwl_mld *mld, } /* if this key was stored to be added later to the FW - free it here */ - if (!(key->flags & IEEE80211_KEY_FLAG_PAIRWISE)) + if (!(key->flags & IEEE80211_KEY_FLAG_PAIRWISE) && + (vif->type == NL80211_IFTYPE_AP || + vif->type == NL80211_IFTYPE_ADHOC)) iwl_mld_free_ap_early_key(mld, key, mld_vif); /* We already removed it */ -- 2.34.1 From: Avinash Bhatt uAPSD is transitioning to a certification-only feature. The new firmware API version 3 removes advanced uAPSD fields, keeping only basic parameters needed for certification testing. Support the new VER_3 API in the MLD driver while maintaining backward compatibility with VER_1/2. The MVM driver continues using VER_2 only. Remove the obsolete PSM_UAPSD_AP_MISBEHAVING_NOTIFICATION notification from the MLD driver Signed-off-by: Avinash Bhatt Signed-off-by: Miri Korenblit --- .../net/wireless/intel/iwlwifi/fw/api/power.h | 48 ++++- .../net/wireless/intel/iwlwifi/mld/iface.c | 18 -- .../net/wireless/intel/iwlwifi/mld/iface.h | 3 - drivers/net/wireless/intel/iwlwifi/mld/mld.c | 1 - .../net/wireless/intel/iwlwifi/mld/notif.c | 6 - .../net/wireless/intel/iwlwifi/mld/power.c | 177 ++++++++++++++++-- drivers/net/wireless/intel/iwlwifi/mvm/mvm.h | 4 +- .../net/wireless/intel/iwlwifi/mvm/power.c | 14 +- 8 files changed, 214 insertions(+), 57 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/fw/api/power.h b/drivers/net/wireless/intel/iwlwifi/fw/api/power.h index a3f916630df2..115e65ba19f8 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/api/power.h +++ b/drivers/net/wireless/intel/iwlwifi/fw/api/power.h @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ /* - * Copyright (C) 2012-2014, 2018-2025 Intel Corporation + * Copyright (C) 2012-2014, 2018-2026 Intel Corporation * Copyright (C) 2013-2014 Intel Mobile Communications GmbH * Copyright (C) 2015-2017 Intel Deutschland GmbH */ @@ -85,12 +85,13 @@ struct iwl_ltr_config_cmd { * '1' PM could sleep over DTIM till listen Interval. * @POWER_FLAGS_SNOOZE_ENA_MSK: Enable snoozing only if uAPSD is enabled and all * access categories are both delivery and trigger enabled. + * (Not supported since version 3) * @POWER_FLAGS_BT_SCO_ENA: Enable BT SCO coex only if uAPSD and * PBW Snoozing enabled * @POWER_FLAGS_ADVANCE_PM_ENA_MSK: Advanced PM (uAPSD) enable mask * @POWER_FLAGS_LPRX_ENA_MSK: Low Power RX enable. * @POWER_FLAGS_UAPSD_MISBEHAVING_ENA_MSK: AP/GO's uAPSD misbehaving - * detection enablement + * detection enablement (Not supported since version 3) * @POWER_FLAGS_ENABLE_SMPS_MSK: SMPS is allowed for this vif */ enum iwl_power_flags { @@ -175,9 +176,9 @@ struct iwl_device_power_cmd { } __packed; /** - * struct iwl_mac_power_cmd - New power command containing uAPSD support + * struct iwl_mac_power_cmd_v2 - power command V2 containing uAPSD support * MAC_PM_POWER_TABLE = 0xA9 (command, has simple generic response) - * @id_and_color: MAC contex identifier, &enum iwl_ctxt_id_and_color + * @id_and_color: MAC context identifier, &enum iwl_ctxt_id_and_color * @flags: Power table command flags from POWER_FLAGS_* * @keep_alive_seconds: Keep alive period in seconds. Default - 25 sec. * Minimum allowed:- 3 * DTIM. Keep alive period must be @@ -216,7 +217,7 @@ struct iwl_device_power_cmd { * @limited_ps_threshold: (unused) * @reserved: reserved (padding) */ -struct iwl_mac_power_cmd { +struct iwl_mac_power_cmd_v2 { /* CONTEXT_DESC_API_T_VER_1 */ __le32 id_and_color; @@ -242,6 +243,43 @@ struct iwl_mac_power_cmd { u8 reserved; } __packed; /* CLIENT_PM_POWER_TABLE_S_VER_1, VER_2 */ +/** + * struct iwl_mac_power_cmd - power command + * MAC_PM_POWER_TABLE = 0xA9 (command, has simple generic response) + * @id_and_color: MAC context identifier, &enum iwl_ctxt_id_and_color + * @flags: Power table command flags from POWER_FLAGS_* + * @keep_alive_seconds: Keep alive period in seconds. Default - 25 sec. + * Minimum allowed:- 3 * DTIM. Keep alive period must be + * set regardless of power scheme or current power state. + * FW use this value also when PM is disabled. + * @rx_data_timeout: Minimum time (usec) from last Rx packet for AM to + * PSM transition - legacy PM + * @tx_data_timeout: Minimum time (usec) from last Tx packet for AM to + * PSM transition - legacy PM + * @lprx_rssi_threshold: Signal strength up to which LP RX can be enabled. + * Default: 80dbm + * @skip_dtim_periods: Number of DTIM periods to skip if Skip over DTIM flag + * is set. For example, if it is required to skip over + * one DTIM, this value need to be set to 2 (DTIM periods). + * @qndp_tid: TID client shall use for uAPSD QNDP triggers + * @uapsd_ac_flags: Set trigger-enabled and delivery-enabled indication for + * each corresponding AC. + * Use IEEE80211_WMM_IE_STA_QOSINFO_AC* for correct values. + */ +struct iwl_mac_power_cmd { + /* CONTEXT_DESC_API_T_VER_1 */ + __le32 id_and_color; + + __le16 flags; + __le16 keep_alive_seconds; + __le32 rx_data_timeout; + __le32 tx_data_timeout; + u8 lprx_rssi_threshold; + u8 skip_dtim_periods; + u8 qndp_tid; + u8 uapsd_ac_flags; +} __packed; /* CLIENT_PM_POWER_TABLE_S_VER_3 */ + /* * struct iwl_uapsd_misbehaving_ap_notif - FW sends this notification when * associated AP is identified as improperly implementing uAPSD protocol. diff --git a/drivers/net/wireless/intel/iwlwifi/mld/iface.c b/drivers/net/wireless/intel/iwlwifi/mld/iface.c index 5fc3f6729455..4fe57d79daa6 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/iface.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/iface.c @@ -739,24 +739,6 @@ void iwl_mld_handle_probe_resp_data_notif(struct iwl_mld *mld, kfree_rcu(old_data, rcu_head); } -void iwl_mld_handle_uapsd_misbehaving_ap_notif(struct iwl_mld *mld, - struct iwl_rx_packet *pkt) -{ - struct iwl_uapsd_misbehaving_ap_notif *notif = (void *)pkt->data; - struct ieee80211_vif *vif; - - if (IWL_FW_CHECK(mld, notif->mac_id >= ARRAY_SIZE(mld->fw_id_to_vif), - "mac id is invalid: %d\n", notif->mac_id)) - return; - - vif = wiphy_dereference(mld->wiphy, mld->fw_id_to_vif[notif->mac_id]); - - if (WARN_ON(!vif) || ieee80211_vif_is_mld(vif)) - return; - - IWL_WARN(mld, "uapsd misbehaving AP: %pM\n", vif->bss_conf.bssid); -} - void iwl_mld_handle_datapath_monitor_notif(struct iwl_mld *mld, struct iwl_rx_packet *pkt) { diff --git a/drivers/net/wireless/intel/iwlwifi/mld/iface.h b/drivers/net/wireless/intel/iwlwifi/mld/iface.h index 71c794204475..d6fb58e785e5 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/iface.h +++ b/drivers/net/wireless/intel/iwlwifi/mld/iface.h @@ -274,9 +274,6 @@ void iwl_mld_handle_probe_resp_data_notif(struct iwl_mld *mld, void iwl_mld_handle_datapath_monitor_notif(struct iwl_mld *mld, struct iwl_rx_packet *pkt); -void iwl_mld_handle_uapsd_misbehaving_ap_notif(struct iwl_mld *mld, - struct iwl_rx_packet *pkt); - void iwl_mld_reset_cca_40mhz_workaround(struct iwl_mld *mld, struct ieee80211_vif *vif); diff --git a/drivers/net/wireless/intel/iwlwifi/mld/mld.c b/drivers/net/wireless/intel/iwlwifi/mld/mld.c index 9a3b768fce7a..25bab6ab6375 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/mld.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/mld.c @@ -162,7 +162,6 @@ static const struct iwl_hcmd_names iwl_mld_legacy_names[] = { HCMD_NAME(PHY_CONFIGURATION_CMD), HCMD_NAME(SCAN_OFFLOAD_UPDATE_PROFILES_CMD), HCMD_NAME(POWER_TABLE_CMD), - HCMD_NAME(PSM_UAPSD_AP_MISBEHAVING_NOTIFICATION), HCMD_NAME(BEACON_NOTIFICATION), HCMD_NAME(BEACON_TEMPLATE_CMD), HCMD_NAME(TX_ANT_CONFIGURATION_CMD), diff --git a/drivers/net/wireless/intel/iwlwifi/mld/notif.c b/drivers/net/wireless/intel/iwlwifi/mld/notif.c index 4e9f3768c381..1c81152042ab 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/notif.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/notif.c @@ -339,8 +339,6 @@ CMD_VERSIONS(emlsr_mode_notif, CMD_VER_ENTRY(2, iwl_esr_mode_notif)) CMD_VERSIONS(emlsr_trans_fail_notif, CMD_VER_ENTRY(1, iwl_esr_trans_fail_notif)) -CMD_VERSIONS(uapsd_misbehaving_ap_notif, - CMD_VER_ENTRY(1, iwl_uapsd_misbehaving_ap_notif)) CMD_VERSIONS(time_msmt_notif, CMD_VER_ENTRY(1, iwl_time_msmt_notify)) CMD_VERSIONS(time_sync_confirm_notif, @@ -363,8 +361,6 @@ DEFINE_SIMPLE_CANCELLATION(scan_complete, iwl_umac_scan_complete, uid) DEFINE_SIMPLE_CANCELLATION(scan_start, iwl_umac_scan_start, uid) DEFINE_SIMPLE_CANCELLATION(probe_resp_data, iwl_probe_resp_data_notif, mac_id) -DEFINE_SIMPLE_CANCELLATION(uapsd_misbehaving_ap, iwl_uapsd_misbehaving_ap_notif, - mac_id) DEFINE_SIMPLE_CANCELLATION(ftm_resp, iwl_tof_range_rsp_ntfy, request_id) DEFINE_SIMPLE_CANCELLATION(beacon_filter, iwl_beacon_filter_notif, link_id) @@ -455,8 +451,6 @@ const struct iwl_rx_handler iwl_mld_rx_handlers[] = { emlsr_mode_notif, RX_HANDLER_ASYNC) RX_HANDLER_NO_OBJECT(MAC_CONF_GROUP, EMLSR_TRANS_FAIL_NOTIF, emlsr_trans_fail_notif, RX_HANDLER_ASYNC) - RX_HANDLER_OF_VIF(LEGACY_GROUP, PSM_UAPSD_AP_MISBEHAVING_NOTIFICATION, - uapsd_misbehaving_ap_notif) RX_HANDLER_NO_OBJECT(LEGACY_GROUP, WNM_80211V_TIMING_MEASUREMENT_NOTIFICATION, time_msmt_notif, RX_HANDLER_SYNC) diff --git a/drivers/net/wireless/intel/iwlwifi/mld/power.c b/drivers/net/wireless/intel/iwlwifi/mld/power.c index fe71da8b9c89..da065a446f81 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/power.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/power.c @@ -88,10 +88,10 @@ static bool iwl_mld_power_is_radar(struct iwl_mld *mld, return chanctx_conf->def.chan->flags & IEEE80211_CHAN_RADAR; } -static void iwl_mld_power_configure_uapsd(struct iwl_mld *mld, - struct iwl_mld_link *link, - struct iwl_mac_power_cmd *cmd, - bool ps_poll) +static void iwl_mld_power_configure_uapsd_v2(struct iwl_mld *mld, + struct iwl_mld_link *link, + struct iwl_mac_power_cmd_v2 *cmd, + bool ps_poll) { bool tid_found = false; @@ -150,10 +150,54 @@ static void iwl_mld_power_configure_uapsd(struct iwl_mld *mld, cmd->uapsd_max_sp = mld->hw->uapsd_max_sp_len; } +static void iwl_mld_power_configure_uapsd(struct iwl_mld *mld, + struct iwl_mld_link *link, + struct iwl_mac_power_cmd *cmd, + bool ps_poll) +{ + bool tid_found = false; + + /* set advanced pm flag with no uapsd ACs to enable ps-poll */ + if (ps_poll) { + cmd->flags |= cpu_to_le16(POWER_FLAGS_ADVANCE_PM_ENA_MSK); + return; + } + + for (enum ieee80211_ac_numbers ac = IEEE80211_AC_VO; + ac <= IEEE80211_AC_BK; + ac++) { + if (!link->queue_params[ac].uapsd) + continue; + + cmd->flags |= + cpu_to_le16(POWER_FLAGS_ADVANCE_PM_ENA_MSK); + cmd->uapsd_ac_flags |= BIT(ac); + + /* QNDP TID - the highest TID with no admission control */ + if (!tid_found && !link->queue_params[ac].acm) { + tid_found = true; + switch (ac) { + case IEEE80211_AC_VO: + cmd->qndp_tid = 6; + break; + case IEEE80211_AC_VI: + cmd->qndp_tid = 5; + break; + case IEEE80211_AC_BE: + cmd->qndp_tid = 0; + break; + case IEEE80211_AC_BK: + cmd->qndp_tid = 1; + break; + } + } + } +} + static void iwl_mld_power_config_skip_dtim(struct iwl_mld *mld, const struct ieee80211_bss_conf *link_conf, - struct iwl_mac_power_cmd *cmd) + u8 *skip_dtim_periods, __le16 *flags) { unsigned int dtimper_tu; unsigned int dtimper; @@ -171,15 +215,15 @@ iwl_mld_power_config_skip_dtim(struct iwl_mld *mld, /* configure skip over dtim up to 900 TU DTIM interval */ skip = max_t(int, 1, 900 / dtimper_tu); - cmd->skip_dtim_periods = skip; - cmd->flags |= cpu_to_le16(POWER_FLAGS_SKIP_OVER_DTIM_MSK); + *skip_dtim_periods = skip; + *flags |= cpu_to_le16(POWER_FLAGS_SKIP_OVER_DTIM_MSK); } #define POWER_KEEP_ALIVE_PERIOD_SEC 25 -static void iwl_mld_power_build_cmd(struct iwl_mld *mld, - struct ieee80211_vif *vif, - struct iwl_mac_power_cmd *cmd, - bool d3) +static void iwl_mld_power_build_cmd_v2(struct iwl_mld *mld, + struct ieee80211_vif *vif, + struct iwl_mac_power_cmd_v2 *cmd, + bool d3) { int dtimper, bi; int keep_alive; @@ -238,7 +282,9 @@ static void iwl_mld_power_build_cmd(struct iwl_mld *mld, } if (d3) { - iwl_mld_power_config_skip_dtim(mld, link_conf, cmd); + iwl_mld_power_config_skip_dtim(mld, link_conf, + &cmd->skip_dtim_periods, + &cmd->flags); cmd->rx_data_timeout = cpu_to_le32(IWL_MLD_WOWLAN_PS_RX_DATA_TIMEOUT); cmd->tx_data_timeout = @@ -259,6 +305,95 @@ static void iwl_mld_power_build_cmd(struct iwl_mld *mld, * mac80211 will allow uAPSD. Always call iwl_mld_power_configure_uapsd * which will look at what mac80211 is saying. */ +#ifdef CONFIG_IWLWIFI_DEBUGFS + ps_poll = mld_vif->use_ps_poll; +#endif + iwl_mld_power_configure_uapsd_v2(mld, link, cmd, ps_poll); +} + +static void iwl_mld_power_build_cmd(struct iwl_mld *mld, + struct ieee80211_vif *vif, + struct iwl_mac_power_cmd *cmd, + bool d3) +{ + int dtimper, bi; + int keep_alive; + struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif); + struct ieee80211_bss_conf *link_conf = &vif->bss_conf; + struct iwl_mld_link *link = &mld_vif->deflink; + bool ps_poll = false; + __le32 fw_id = cpu_to_le32(mld_vif->fw_id); + + if (ieee80211_vif_is_mld(vif)) { + int link_id; + + if (WARN_ON(!vif->active_links)) + return; + + /* The firmware consumes one single configuration for the vif + * and can't differentiate between links, just pick the lowest + * link_id's configuration and use that. + */ + link_id = __ffs(vif->active_links); + link_conf = link_conf_dereference_check(vif, link_id); + link = iwl_mld_link_dereference_check(mld_vif, link_id); + + if (WARN_ON(!link_conf || !link)) + return; + } + dtimper = link_conf->dtim_period; + bi = link_conf->beacon_int; + + /* Regardless of power management state the driver must set + * keep alive period. FW will use it for sending keep alive NDPs + * immediately after association. Check that keep alive period + * is at least 3 * DTIM + */ + keep_alive = DIV_ROUND_UP(ieee80211_tu_to_usec(3 * dtimper * bi), + USEC_PER_SEC); + keep_alive = max(keep_alive, POWER_KEEP_ALIVE_PERIOD_SEC); + + cmd->id_and_color = fw_id; + cmd->keep_alive_seconds = cpu_to_le16(keep_alive); + + if (iwlmld_mod_params.power_scheme != IWL_POWER_SCHEME_CAM) + cmd->flags |= cpu_to_le16(POWER_FLAGS_POWER_SAVE_ENA_MSK); + + if (vif->cfg.ps && iwl_mld_tdls_sta_count(mld) == 0) { + cmd->flags |= cpu_to_le16(POWER_FLAGS_POWER_MANAGEMENT_ENA_MSK); + cmd->flags |= cpu_to_le16(POWER_FLAGS_ENABLE_SMPS_MSK); + + /* firmware supports LPRX for beacons at rate 1 Mbps or + * 6 Mbps only + */ + if (link_conf->beacon_rate && + (link_conf->beacon_rate->bitrate == 10 || + link_conf->beacon_rate->bitrate == 60)) { + cmd->flags |= cpu_to_le16(POWER_FLAGS_LPRX_ENA_MSK); + cmd->lprx_rssi_threshold = POWER_LPRX_RSSI_THRESHOLD; + } + } + + if (d3) { + iwl_mld_power_config_skip_dtim(mld, link_conf, + &cmd->skip_dtim_periods, + &cmd->flags); + cmd->rx_data_timeout = + cpu_to_le32(IWL_MLD_WOWLAN_PS_RX_DATA_TIMEOUT); + cmd->tx_data_timeout = + cpu_to_le32(IWL_MLD_WOWLAN_PS_TX_DATA_TIMEOUT); + } else if (iwl_mld_vif_low_latency(mld_vif) && vif->p2p) { + cmd->tx_data_timeout = + cpu_to_le32(IWL_MLD_SHORT_PS_TX_DATA_TIMEOUT); + cmd->rx_data_timeout = + cpu_to_le32(IWL_MLD_SHORT_PS_RX_DATA_TIMEOUT); + } else { + cmd->rx_data_timeout = + cpu_to_le32(IWL_MLD_DEFAULT_PS_RX_DATA_TIMEOUT); + cmd->tx_data_timeout = + cpu_to_le32(IWL_MLD_DEFAULT_PS_TX_DATA_TIMEOUT); + } + #ifdef CONFIG_IWLWIFI_DEBUGFS ps_poll = mld_vif->use_ps_poll; #endif @@ -268,11 +403,23 @@ static void iwl_mld_power_build_cmd(struct iwl_mld *mld, int iwl_mld_update_mac_power(struct iwl_mld *mld, struct ieee80211_vif *vif, bool d3) { - struct iwl_mac_power_cmd cmd = {}; + int cmd_ver = iwl_fw_lookup_cmd_ver(mld->fw, MAC_PM_POWER_TABLE, 0); - iwl_mld_power_build_cmd(mld, vif, &cmd, d3); + if (cmd_ver >= 3) { + struct iwl_mac_power_cmd cmd = {}; - return iwl_mld_send_cmd_pdu(mld, MAC_PM_POWER_TABLE, &cmd); + iwl_mld_power_build_cmd(mld, vif, &cmd, d3); + return iwl_mld_send_cmd_with_flags_pdu(mld, + MAC_PM_POWER_TABLE, 0, + &cmd, sizeof(cmd)); + } else { + struct iwl_mac_power_cmd_v2 cmd = {}; + + iwl_mld_power_build_cmd_v2(mld, vif, &cmd, d3); + return iwl_mld_send_cmd_with_flags_pdu(mld, + MAC_PM_POWER_TABLE, 0, + &cmd, sizeof(cmd)); + } } static void diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/mvm.h b/drivers/net/wireless/intel/iwlwifi/mvm/mvm.h index 402ba5dee8b2..be89b84204fb 100644 --- a/drivers/net/wireless/intel/iwlwifi/mvm/mvm.h +++ b/drivers/net/wireless/intel/iwlwifi/mvm/mvm.h @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ /* - * Copyright (C) 2012-2014, 2018-2025 Intel Corporation + * Copyright (C) 2012-2014, 2018-2026 Intel Corporation * Copyright (C) 2013-2015 Intel Mobile Communications GmbH * Copyright (C) 2016-2017 Intel Deutschland GmbH */ @@ -469,7 +469,7 @@ struct iwl_mvm_vif { struct dentry *dbgfs_slink; struct iwl_dbgfs_pm dbgfs_pm; struct iwl_dbgfs_bf dbgfs_bf; - struct iwl_mac_power_cmd mac_pwr_cmd; + struct iwl_mac_power_cmd_v2 mac_pwr_cmd; int dbgfs_quota_min; bool ftm_unprotected; #endif diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/power.c b/drivers/net/wireless/intel/iwlwifi/mvm/power.c index 610de29b7be0..46792c508753 100644 --- a/drivers/net/wireless/intel/iwlwifi/mvm/power.c +++ b/drivers/net/wireless/intel/iwlwifi/mvm/power.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause /* - * Copyright (C) 2012-2014, 2018-2019, 2021-2025 Intel Corporation + * Copyright (C) 2012-2014, 2018-2019, 2021-2026 Intel Corporation * Copyright (C) 2013-2014 Intel Mobile Communications GmbH * Copyright (C) 2015-2017 Intel Deutschland GmbH */ @@ -83,7 +83,7 @@ void iwl_mvm_beacon_filter_set_cqm_params(struct iwl_mvm *mvm, } static void iwl_mvm_power_log(struct iwl_mvm *mvm, - struct iwl_mac_power_cmd *cmd) + struct iwl_mac_power_cmd_v2 *cmd) { IWL_DEBUG_POWER(mvm, "Sending power table command on mac id 0x%X for power level %d, flags = 0x%X\n", @@ -121,7 +121,7 @@ static void iwl_mvm_power_log(struct iwl_mvm *mvm, static void iwl_mvm_power_configure_uapsd(struct iwl_mvm *mvm, struct ieee80211_vif *vif, - struct iwl_mac_power_cmd *cmd) + struct iwl_mac_power_cmd_v2 *cmd) { struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); enum ieee80211_ac_numbers ac; @@ -296,7 +296,7 @@ static bool iwl_mvm_power_is_radar(struct ieee80211_bss_conf *link_conf) static void iwl_mvm_power_config_skip_dtim(struct iwl_mvm *mvm, struct ieee80211_vif *vif, - struct iwl_mac_power_cmd *cmd) + struct iwl_mac_power_cmd_v2 *cmd) { struct ieee80211_bss_conf *link_conf; unsigned int min_link_skip = ~0; @@ -344,7 +344,7 @@ static void iwl_mvm_power_config_skip_dtim(struct iwl_mvm *mvm, static void iwl_mvm_power_build_cmd(struct iwl_mvm *mvm, struct ieee80211_vif *vif, - struct iwl_mac_power_cmd *cmd) + struct iwl_mac_power_cmd_v2 *cmd) { int dtimper, bi; int keep_alive; @@ -466,7 +466,7 @@ static void iwl_mvm_power_build_cmd(struct iwl_mvm *mvm, static int iwl_mvm_power_send_cmd(struct iwl_mvm *mvm, struct ieee80211_vif *vif) { - struct iwl_mac_power_cmd cmd = {}; + struct iwl_mac_power_cmd_v2 cmd = {}; iwl_mvm_power_build_cmd(mvm, vif, &cmd); iwl_mvm_power_log(mvm, &cmd); @@ -717,7 +717,7 @@ int iwl_mvm_power_mac_dbgfs_read(struct iwl_mvm *mvm, int bufsz) { struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); - struct iwl_mac_power_cmd cmd = {}; + struct iwl_mac_power_cmd_v2 cmd = {}; int pos = 0; mutex_lock(&mvm->mutex); -- 2.34.1 From: Daniel Gabay Use IWL_DEBUG_INFO instead of IWL_INFO for logging the DW end notification, as this is a recurring event during NAN operation and should not spam the kernel log. Also fix a coding style issue - missing space after 'if'. Fixes: 9e978d8ebbe9 ("wifi: iwlwifi: mld: Add support for NAN") Signed-off-by: Daniel Gabay Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/mld/nan.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/intel/iwlwifi/mld/nan.c b/drivers/net/wireless/intel/iwlwifi/mld/nan.c index cb7a3e01adb4..264ea7a9a896 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/nan.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/nan.c @@ -269,7 +269,7 @@ void iwl_mld_handle_nan_dw_end_notif(struct iwl_mld *mld, struct wireless_dev *wdev; struct ieee80211_channel *chan; - IWL_INFO(mld, "NAN: DW end: band=%u\n", notif->band); + IWL_DEBUG_INFO(mld, "NAN: DW end: band=%u\n", notif->band); if (IWL_FW_CHECK(mld, !mld_vif, "NAN: DW end without mld_vif\n")) return; -- 2.34.1 From: Daniel Gabay ieee80211_get_channel() can return NULL if the frequency is not registered in the wiphy (e.g. due to regulatory domain restrictions). The returned channel pointer is passed directly to cfg80211_next_nan_dw_notif() which dereferences it unconditionally in both the tracepoint and the netlink message, causing a NULL pointer dereference. Add a NULL check before using the channel pointer. Fixes: 600941464991 ("wifi: iwlwifi: mld: Declare support for NAN capabilities") Signed-off-by: Daniel Gabay Reviewed-by: Ilan Peer Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/mld/nan.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/net/wireless/intel/iwlwifi/mld/nan.c b/drivers/net/wireless/intel/iwlwifi/mld/nan.c index 264ea7a9a896..deb72e401e3c 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/nan.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/nan.c @@ -305,6 +305,9 @@ void iwl_mld_handle_nan_dw_end_notif(struct iwl_mld *mld, return; } + if (WARN_ON_ONCE(!chan)) + return; + wdev = ieee80211_vif_to_wdev(mld->nan_device_vif); cfg80211_next_nan_dw_notif(wdev, chan, GFP_KERNEL); } -- 2.34.1 From: Daniel Gabay iwl_mld_stop_nan() calls iwl_mld_flush_link_sta_txqs() without checking that aux_sta.sta_id is valid. The DW end handler correctly guards this with a WARN_ON check. Add the same defensive check to stop_nan for consistency and to avoid sending a flush command with an invalid sta_id. Fixes: 600941464991 ("wifi: iwlwifi: mld: Declare support for NAN capabilities") Signed-off-by: Daniel Gabay Reviewed-by: Ilan Peer Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/mld/nan.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/intel/iwlwifi/mld/nan.c b/drivers/net/wireless/intel/iwlwifi/mld/nan.c index deb72e401e3c..913f0acf172a 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/nan.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/nan.c @@ -203,7 +203,8 @@ int iwl_mld_stop_nan(struct ieee80211_hw *hw, /* assume that higher layer guarantees that no additional frames are * added before calling this callback */ - iwl_mld_flush_link_sta_txqs(mld, mld_vif->aux_sta.sta_id); + if (!WARN_ON(mld_vif->aux_sta.sta_id == IWL_INVALID_STA)) + iwl_mld_flush_link_sta_txqs(mld, mld_vif->aux_sta.sta_id); iwl_mld_remove_aux_sta(mld, vif); /* cancel based on object type being NAN, as the NAN objects do -- 2.34.1 From: Avinash Bhatt Introduce a three-level, table-driven hysteresis mechanism for deciding when to trigger internal MLO scans based on MCLM Chan Load. Prevents repeated triggers under fluctuating load conditions. Signed-off-by: Avinash Bhatt Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/mld/link.c | 61 +++++++++++++++++++ drivers/net/wireless/intel/iwlwifi/mld/link.h | 15 +++++ drivers/net/wireless/intel/iwlwifi/mld/mlo.c | 29 ++++++++- .../net/wireless/intel/iwlwifi/mld/stats.c | 4 ++ 4 files changed, 106 insertions(+), 3 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/mld/link.c b/drivers/net/wireless/intel/iwlwifi/mld/link.c index 9e40b334ee1f..a8d146edc4bd 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/link.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/link.c @@ -16,6 +16,23 @@ #include "fw/api/context.h" #include "fw/dbg.h" +/** + * struct iwl_mld_link_chan_load_threshold - channel load thresholds + * @high_lim: level up transition thresholds, in percentage + * @low_lim: level down transition thresholds, in percentage + */ +struct iwl_mld_link_chan_load_threshold { + u8 high_lim; + u8 low_lim; +}; + +static const struct iwl_mld_link_chan_load_threshold +link_chan_load_thresh_tbl[] = { + [LINK_CHAN_LOAD_LVL1] = { .high_lim = 45, .low_lim = 40 }, + [LINK_CHAN_LOAD_LVL2] = { .high_lim = 70, .low_lim = 65 }, + [LINK_CHAN_LOAD_LVL3] = { .high_lim = 85, .low_lim = 80 }, +}; + int iwl_mld_send_link_cmd(struct iwl_mld *mld, struct iwl_link_config_cmd *cmd, enum iwl_ctxt_action action) @@ -792,6 +809,50 @@ int iwl_mld_get_chan_load_by_others(struct iwl_mld *mld, return chan_load; } +/* Returns whether internal MLO Scan needs to be triggered */ +bool iwl_mld_chan_load_requires_scan(struct iwl_mld *mld, + struct ieee80211_bss_conf *link_conf, + u32 new_chan_load) +{ + struct iwl_mld_link *mld_link = iwl_mld_link_from_mac80211(link_conf); + enum iwl_mld_link_chan_load_level new_lvl; + bool scan_trig = false; + + if (WARN_ON(!mld_link)) + return false; + + /* For each Level, + * First check if high limit threshold crosses + * If not then, check if low limit threshold crosses + * Set new level based on low limit thresh only if old level + * is not lower than level threshold + */ + for (new_lvl = LINK_CHAN_LOAD_LVL_MAX; + new_lvl > LINK_CHAN_LOAD_LVL_NONE; new_lvl--) { + if (new_chan_load >= + link_chan_load_thresh_tbl[new_lvl].high_lim) + break; + if (new_chan_load >= + link_chan_load_thresh_tbl[new_lvl].low_lim && + mld_link->chan_load_lvl >= new_lvl) + break; + } + + /* Trigger scan only for Level Up Transition */ + if (new_lvl > mld_link->chan_load_lvl) + scan_trig = true; + + IWL_DEBUG_EHT(mld, + "Link %d: chan_load=%d%%, old_lvl=%d, new_lvl=%d, scan_trig=%d\n", + link_conf->link_id, new_chan_load, + mld_link->chan_load_lvl, new_lvl, scan_trig); + + /* Update computed new level */ + mld_link->chan_load_lvl = new_lvl; + + return scan_trig; +} + static unsigned int iwl_mld_get_default_chan_load(struct ieee80211_bss_conf *link_conf) { diff --git a/drivers/net/wireless/intel/iwlwifi/mld/link.h b/drivers/net/wireless/intel/iwlwifi/mld/link.h index 2b3e6b55367f..4527f054ce92 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/link.h +++ b/drivers/net/wireless/intel/iwlwifi/mld/link.h @@ -10,6 +10,14 @@ #include "mld.h" #include "sta.h" +enum iwl_mld_link_chan_load_level { + LINK_CHAN_LOAD_LVL_NONE, + LINK_CHAN_LOAD_LVL1, + LINK_CHAN_LOAD_LVL2, + LINK_CHAN_LOAD_LVL3, + LINK_CHAN_LOAD_LVL_MAX = LINK_CHAN_LOAD_LVL3 +}; + /** * struct iwl_probe_resp_data - data for NoA/CSA updates * @rcu_head: used for freeing the data on update @@ -50,6 +58,8 @@ struct iwl_probe_resp_data { * @silent_deactivation: next deactivation needs to be silent. * @probe_resp_data: data from FW notification to store NOA related data to be * inserted into probe response. + * @chan_load_lvl: current channel load level for a link, computed based on + * channel load by others on a link. */ struct iwl_mld_link { struct rcu_head rcu_head; @@ -63,6 +73,7 @@ struct iwl_mld_link { bool he_ru_2mhz_block; struct ieee80211_key_conf *tx_igtk; struct ieee80211_key_conf __rcu *bigtks[2]; + enum iwl_mld_link_chan_load_level chan_load_lvl; ); /* And here fields that survive a fw restart */ struct iwl_mld_int_sta bcast_sta; @@ -135,6 +146,10 @@ int iwl_mld_get_chan_load_by_others(struct iwl_mld *mld, struct ieee80211_bss_conf *link_conf, bool expect_active_link); +bool iwl_mld_chan_load_requires_scan(struct iwl_mld *mld, + struct ieee80211_bss_conf *link_conf, + u32 new_chan_load); + void iwl_mld_handle_beacon_filter_notif(struct iwl_mld *mld, struct iwl_rx_packet *pkt); diff --git a/drivers/net/wireless/intel/iwlwifi/mld/mlo.c b/drivers/net/wireless/intel/iwlwifi/mld/mlo.c index 8227ccb31d60..2a3b2c883fc4 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/mlo.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/mlo.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause /* - * Copyright (C) 2024-2025 Intel Corporation + * Copyright (C) 2024-2026 Intel Corporation */ #include "mlo.h" #include "phy.h" @@ -1081,8 +1081,13 @@ static void iwl_mld_chan_load_update_iter(void *_data, u8 *mac, container_of((const void *)phy, struct ieee80211_chanctx_conf, drv_priv); struct iwl_mld *mld = iwl_mld_vif_from_mac80211(vif)->mld; - struct ieee80211_bss_conf *prim_link; + u32 new_chan_load = phy->avg_channel_load_not_by_us; + struct ieee80211_bss_conf *prim_link, *link_conf; unsigned int prim_link_id; + int link_id; + + if (!ieee80211_vif_is_mld(vif) || hweight16(vif->valid_links) <= 1) + return; prim_link_id = iwl_mld_get_primary_link(vif); prim_link = link_conf_dereference_protected(vif, prim_link_id); @@ -1090,6 +1095,25 @@ static void iwl_mld_chan_load_update_iter(void *_data, u8 *mac, if (WARN_ON(!prim_link)) return; + /* Evaluate MLO Internal Scan for high chan load beyond thresholds */ + for_each_vif_active_link(vif, link_conf, link_id) { + if (rcu_access_pointer(link_conf->chanctx_conf) != chanctx) + continue; + + if (iwl_mld_chan_load_requires_scan(mld, + link_conf, + new_chan_load)) { + /* When EMLSR is active, only trigger scan based on + * primary link + */ + if (iwl_mld_emlsr_active(vif) && link_conf != prim_link) + continue; + + iwl_mld_int_mlo_scan(mld, vif); + return; + } + } + if (chanctx != rcu_access_pointer(prim_link->chanctx_conf)) return; @@ -1107,7 +1131,6 @@ static void iwl_mld_chan_load_update_iter(void *_data, u8 *mac, prim_link_id); } else { u32 old_chan_load = data->prev_chan_load_not_by_us; - u32 new_chan_load = phy->avg_channel_load_not_by_us; u32 min_thresh = iwl_mld_get_min_chan_load_thresh(chanctx); #define THRESHOLD_CROSSED(threshold) \ diff --git a/drivers/net/wireless/intel/iwlwifi/mld/stats.c b/drivers/net/wireless/intel/iwlwifi/mld/stats.c index 6e826797f637..b93e0f8ab5fb 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/stats.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/stats.c @@ -515,6 +515,10 @@ static void iwl_mld_fill_chanctx_stats(struct ieee80211_hw *hw, (old_load >> 1); } + IWL_DEBUG_EHT(phy->mld, + "PHY %d: load_by_us=%u%% load_not_by_us=%u%%\n", + phy->fw_id, phy->channel_load_by_us, new_load); + iwl_mld_emlsr_check_chan_load(hw, phy, old_load); } -- 2.34.1 From: Emmanuel Grumbach Add support for a new RSC notification that arrives on DATA queues. The same RSC handling previously done in the WOWLAN flow is now done through this notification, with backward compatibility maintained. Signed-off-by: Emmanuel Grumbach Signed-off-by: Miri Korenblit --- .../wireless/intel/iwlwifi/fw/api/datapath.h | 9 +- drivers/net/wireless/intel/iwlwifi/mld/d3.c | 148 ++++++++++++++++-- drivers/net/wireless/intel/iwlwifi/mld/d3.h | 6 +- .../net/wireless/intel/iwlwifi/mld/iface.c | 4 +- drivers/net/wireless/intel/iwlwifi/mld/mld.c | 1 + .../net/wireless/intel/iwlwifi/mld/notif.c | 6 + drivers/net/wireless/intel/iwlwifi/mld/rx.c | 22 +++ drivers/net/wireless/intel/iwlwifi/mld/rx.h | 5 +- 8 files changed, 183 insertions(+), 18 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/fw/api/datapath.h b/drivers/net/wireless/intel/iwlwifi/fw/api/datapath.h index 06370c161fe4..e494e5b18d22 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/api/datapath.h +++ b/drivers/net/wireless/intel/iwlwifi/fw/api/datapath.h @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ /* - * Copyright (C) 2024-2025 Intel Corporation + * Copyright (C) 2024-2026 Intel Corporation * Copyright (C) 2012-2014, 2018-2022 Intel Corporation * Copyright (C) 2013-2015 Intel Mobile Communications GmbH * Copyright (C) 2016-2017 Intel Deutschland GmbH @@ -91,6 +91,13 @@ enum iwl_data_path_subcmd_ids { */ SEC_KEY_CMD = 0x18, + /** + * @RSC_NOTIF: notification to update each Rx queue with the RSC. This + * notification is sent after resume and uses + * &struct iwl_wowlan_all_rsc_tsc_v5. + */ + RSC_NOTIF = 0xF1, + /** * @ESR_MODE_NOTIF: notification to recommend/force a wanted esr mode, * uses &struct iwl_esr_mode_notif or &struct iwl_esr_mode_notif_v1 diff --git a/drivers/net/wireless/intel/iwlwifi/mld/d3.c b/drivers/net/wireless/intel/iwlwifi/mld/d3.c index c44f02f225ce..ca4222a9a6ff 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/d3.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/d3.c @@ -43,6 +43,12 @@ struct iwl_mld_resume_key_iter_data { struct iwl_mld_wowlan_status *wowlan_status; }; +struct iwl_mld_rsc_resume_iter_data { + struct iwl_mld *mld; + const struct iwl_wowlan_all_rsc_tsc_v5 *notif; + int queue; +}; + struct iwl_mld_suspend_key_iter_data { struct iwl_wowlan_rsc_tsc_params_cmd *rsc; bool have_rsc; @@ -282,7 +288,8 @@ static void iwl_mld_convert_gtk_resume_data(struct iwl_mld *mld, struct iwl_mld_wowlan_status *wowlan_status, const struct iwl_wowlan_gtk_status *gtk_data, - const struct iwl_wowlan_all_rsc_tsc_v5 *sc) + const struct iwl_wowlan_all_rsc_tsc_v5 *sc, + int rsc_notif_ver) { int status_idx = 0; @@ -305,14 +312,18 @@ iwl_mld_convert_gtk_resume_data(struct iwl_mld *mld, wowlan_status->gtk[status_idx].id = wowlan_status->gtk[status_idx].flags & IWL_WOWLAN_GTK_IDX_MASK; - /* The rsc for both gtk keys are stored in gtk[0]->sc->mcast_rsc - * The gtk ids can be any two numbers between 0 and 3, - * the id_map maps between the key id and the index in sc->mcast - */ - rsc_idx = - sc->mcast_key_id_map[wowlan_status->gtk[status_idx].id]; - iwl_mld_convert_gtk_resume_seq(&wowlan_status->gtk[status_idx], - sc, rsc_idx); + /* If RSC_NOTIF is not supported */ + if (rsc_notif_ver == IWL_FW_CMD_VER_UNKNOWN) { + /* The rsc for both gtk keys are stored in + * gtk[0]->sc->mcast_rsc. The gtk ids can be any two + * numbers between 0 and 3, the id_map maps between the + * key id and the index in sc->mcast + */ + rsc_idx = + sc->mcast_key_id_map[wowlan_status->gtk[status_idx].id]; + iwl_mld_convert_gtk_resume_seq(&wowlan_status->gtk[status_idx], + sc, rsc_idx); + } if (key_status == IWL_WOWLAN_STATUS_NEW_KEY) { memcpy(wowlan_status->gtk[status_idx].key, @@ -598,6 +609,10 @@ iwl_mld_handle_wowlan_info_notif(struct iwl_mld *mld, PROT_OFFLOAD_GROUP, WOWLAN_INFO_NOTIFICATION, IWL_FW_CMD_VER_UNKNOWN); + int rsc_notif_ver = iwl_fw_lookup_notif_ver(mld->fw, + DATA_PATH_GROUP, + RSC_NOTIF, + IWL_FW_CMD_VER_UNKNOWN); if (wowlan_info_ver == 5) { /* v5 format - validate before conversion */ @@ -642,8 +657,10 @@ iwl_mld_handle_wowlan_info_notif(struct iwl_mld *mld, return true; iwl_mld_convert_gtk_resume_data(mld, wowlan_status, notif->gtk, - ¬if->gtk[0].sc); - iwl_mld_convert_ptk_resume_seq(mld, wowlan_status, ¬if->gtk[0].sc); + ¬if->gtk[0].sc, rsc_notif_ver); + if (rsc_notif_ver == IWL_FW_CMD_VER_UNKNOWN) + iwl_mld_convert_ptk_resume_seq(mld, wowlan_status, + ¬if->gtk[0].sc); /* only one igtk is passed by FW */ iwl_mld_convert_igtk_resume_data(wowlan_status, ¬if->igtk[0]); iwl_mld_convert_bigtk_resume_data(wowlan_status, notif->bigtk); @@ -902,8 +919,14 @@ iwl_mld_resume_keys_iter(struct ieee80211_hw *hw, struct iwl_mld_resume_key_iter_data *data = _data; struct iwl_mld_wowlan_status *wowlan_status = data->wowlan_status; u8 status_idx; - - if (key->keyidx >= 0 && key->keyidx <= 3) { + int rsc_notif_ver = iwl_fw_lookup_notif_ver(data->mld->fw, + DATA_PATH_GROUP, + RSC_NOTIF, + IWL_FW_CMD_VER_UNKNOWN); + + /* If RSC_NOTIF is not supported */ + if (rsc_notif_ver == IWL_FW_CMD_VER_UNKNOWN && + key->keyidx >= 0 && key->keyidx <= 3) { /* PTK */ if (sta) { iwl_mld_update_ptk_rx_seq(data->mld, wowlan_status, @@ -932,6 +955,105 @@ iwl_mld_resume_keys_iter(struct ieee80211_hw *hw, } } +static void +iwl_mld_rsc_update_key_iter(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta, + struct ieee80211_key_conf *key, + void *_data) +{ + struct iwl_mld_rsc_resume_iter_data *data = _data; + struct ieee80211_key_seq seq; + + if (key->keyidx > 3) + return; + + if (sta) { + /* PTK */ + BUILD_BUG_ON(ARRAY_SIZE(data->notif->ucast_rsc) != + IWL_MAX_TID_COUNT); + + if (key->cipher == WLAN_CIPHER_SUITE_TKIP) { + /* TKIP: just update key sequences */ + for (int tid = 0; tid < IWL_MAX_TID_COUNT; tid++) { + iwl_mld_le64_to_tkip_seq(data->notif->ucast_rsc[tid], + &seq); + ieee80211_set_key_rx_seq(key, tid, &seq); + } + } else { + struct iwl_mld_sta *mld_sta = iwl_mld_sta_from_mac80211(sta); + struct iwl_mld_ptk_pn *mld_ptk_pn = + rcu_dereference_wiphy(data->mld->wiphy, + mld_sta->ptk_pn[key->keyidx]); + + if (WARN_ON(!mld_ptk_pn)) + return; + + if (WARN_ON(data->queue >= + data->mld->trans->info.num_rxqs)) + return; + + for (int tid = 0; tid < IWL_MAX_TID_COUNT; tid++) { + iwl_mld_le64_to_aes_seq(data->notif->ucast_rsc[tid], + &seq); + ieee80211_set_key_rx_seq(key, tid, &seq); + memcpy(mld_ptk_pn->q[data->queue].pn[tid], + seq.ccmp.pn, + IEEE80211_CCMP_PN_LEN); + } + } + + IWL_DEBUG_WOWLAN(data->mld, + "Updated PTK RSC for key %d on queue %d\n", + key->keyidx, data->queue); + } else { + /* GTK */ + int rsc_idx = data->notif->mcast_key_id_map[key->keyidx]; + + if (rsc_idx == IWL_MCAST_KEY_MAP_INVALID) + return; + + if (IWL_FW_CHECK(data->mld, + rsc_idx >= ARRAY_SIZE(data->notif->mcast_rsc), + "Invalid mcast key mapping: %d for key %d\n", + rsc_idx, key->keyidx)) + return; + + for (int tid = 0; tid < IWL_MAX_TID_COUNT; tid++) { + __le64 rsc = + data->notif->mcast_rsc[rsc_idx][tid]; + + if (key->cipher == WLAN_CIPHER_SUITE_TKIP) + iwl_mld_le64_to_tkip_seq(rsc, &seq); + else + iwl_mld_le64_to_aes_seq(rsc, &seq); + ieee80211_set_key_rx_seq(key, tid, &seq); + } + + IWL_DEBUG_WOWLAN(data->mld, + "Updated GTK %d RSC (rsc_idx %d) on queue %d\n", + key->keyidx, rsc_idx, data->queue); + } +} + +void +iwl_mld_process_rsc_notification(struct iwl_mld *mld, + struct ieee80211_vif *vif, + const struct iwl_wowlan_all_rsc_tsc_v5 *notif, + int queue) +{ + struct iwl_mld_rsc_resume_iter_data iter_data = { + .mld = mld, + .notif = notif, + .queue = queue, + }; + + /* Iterate through all active keys and update RSC */ + ieee80211_iter_keys_rcu(mld->hw, vif, + iwl_mld_rsc_update_key_iter, + &iter_data); +} + static void iwl_mld_add_mcast_rekey(struct ieee80211_vif *vif, struct iwl_mld *mld, diff --git a/drivers/net/wireless/intel/iwlwifi/mld/d3.h b/drivers/net/wireless/intel/iwlwifi/mld/d3.h index 618d6fb3c796..c2e8ba877042 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/d3.h +++ b/drivers/net/wireless/intel/iwlwifi/mld/d3.h @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ /* - * Copyright (C) 2024 Intel Corporation + * Copyright (C) 2024, 2026 Intel Corporation */ #ifndef __iwl_mld_d3_h__ #define __iwl_mld_d3_h__ @@ -42,6 +42,10 @@ int iwl_mld_wowlan_resume(struct iwl_mld *mld); void iwl_mld_set_rekey_data(struct ieee80211_hw *hw, struct ieee80211_vif *vif, struct cfg80211_gtk_rekey_data *data); +void iwl_mld_process_rsc_notification(struct iwl_mld *mld, + struct ieee80211_vif *vif, + const struct iwl_wowlan_all_rsc_tsc_v5 *notif, + int queue); #if IS_ENABLED(CONFIG_IPV6) void iwl_mld_ipv6_addr_change(struct ieee80211_hw *hw, struct ieee80211_vif *vif, diff --git a/drivers/net/wireless/intel/iwlwifi/mld/iface.c b/drivers/net/wireless/intel/iwlwifi/mld/iface.c index 4fe57d79daa6..6f2590f9a69b 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/iface.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/iface.c @@ -853,6 +853,6 @@ struct ieee80211_vif *iwl_mld_get_bss_vif(struct iwl_mld *mld) fw_id = __ffs(fw_id_bitmap); - return wiphy_dereference(mld->wiphy, - mld->fw_id_to_vif[fw_id]); + return rcu_dereference_wiphy(mld->wiphy, + mld->fw_id_to_vif[fw_id]); } diff --git a/drivers/net/wireless/intel/iwlwifi/mld/mld.c b/drivers/net/wireless/intel/iwlwifi/mld/mld.c index 25bab6ab6375..3caa76b9b2cb 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/mld.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/mld.c @@ -260,6 +260,7 @@ static const struct iwl_hcmd_names iwl_mld_data_path_names[] = { HCMD_NAME(RX_BAID_ALLOCATION_CONFIG_CMD), HCMD_NAME(SCD_QUEUE_CONFIG_CMD), HCMD_NAME(SEC_KEY_CMD), + HCMD_NAME(RSC_NOTIF), HCMD_NAME(ESR_MODE_NOTIF), HCMD_NAME(MONITOR_NOTIF), HCMD_NAME(TLC_MNG_UPDATE_NOTIF), diff --git a/drivers/net/wireless/intel/iwlwifi/mld/notif.c b/drivers/net/wireless/intel/iwlwifi/mld/notif.c index 1c81152042ab..14834395de3a 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/notif.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/notif.c @@ -599,6 +599,9 @@ void iwl_mld_rx(struct iwl_op_mode *op_mode, struct napi_struct *napi, else if (unlikely(cmd_id == WIDE_ID(DATA_PATH_GROUP, RX_QUEUES_NOTIFICATION))) iwl_mld_handle_rx_queues_sync_notif(mld, napi, pkt, 0); + else if (unlikely(cmd_id == WIDE_ID(DATA_PATH_GROUP, + RSC_NOTIF))) + iwl_mld_handle_rsc_notif(mld, pkt, 0); else if (cmd_id == WIDE_ID(DATA_PATH_GROUP, PHY_AIR_SNIFFER_NOTIF)) iwl_mld_handle_phy_air_sniffer_notif(mld, napi, pkt); else @@ -622,6 +625,9 @@ void iwl_mld_rx_rss(struct iwl_op_mode *op_mode, struct napi_struct *napi, iwl_mld_handle_rx_queues_sync_notif(mld, napi, pkt, queue); else if (unlikely(cmd_id == WIDE_ID(LEGACY_GROUP, FRAME_RELEASE))) iwl_mld_handle_frame_release_notif(mld, napi, pkt, queue); + else if (unlikely(cmd_id == WIDE_ID(DATA_PATH_GROUP, + RSC_NOTIF))) + iwl_mld_handle_rsc_notif(mld, pkt, queue); } void iwl_mld_delete_handlers(struct iwl_mld *mld, const u16 *cmds, int n_cmds) diff --git a/drivers/net/wireless/intel/iwlwifi/mld/rx.c b/drivers/net/wireless/intel/iwlwifi/mld/rx.c index 01603dc07f0a..7e023fe51e3b 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/rx.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/rx.c @@ -2260,6 +2260,28 @@ void iwl_mld_handle_rx_queues_sync_notif(struct iwl_mld *mld, wake_up(&mld->rxq_sync.waitq); } +void iwl_mld_handle_rsc_notif(struct iwl_mld *mld, + struct iwl_rx_packet *pkt, int queue) +{ + const struct iwl_wowlan_all_rsc_tsc_v5 *notif = (void *)pkt->data; + u32 len = iwl_rx_packet_payload_len(pkt); + struct ieee80211_vif *bss_vif; + + if (IWL_FW_CHECK(mld, len != sizeof(*notif), + "invalid notification size %u (%zu)\n", + len, sizeof(*notif))) + return; + + /* for the bss lookup and updating the keys' pn */ + guard(rcu)(); + + bss_vif = iwl_mld_get_bss_vif(mld); + if (WARN_ON(!bss_vif)) + return; + + iwl_mld_process_rsc_notification(mld, bss_vif, notif, queue); +} + static void iwl_mld_no_data_rx(struct iwl_mld *mld, struct napi_struct *napi, struct iwl_rx_phy_air_sniffer_ntfy *ntfy) diff --git a/drivers/net/wireless/intel/iwlwifi/mld/rx.h b/drivers/net/wireless/intel/iwlwifi/mld/rx.h index 09dddbd40f55..f7ef70e69143 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/rx.h +++ b/drivers/net/wireless/intel/iwlwifi/mld/rx.h @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ /* - * Copyright (C) 2024-2025 Intel Corporation + * Copyright (C) 2024-2026 Intel Corporation */ #ifndef __iwl_mld_rx_h__ #define __iwl_mld_rx_h__ @@ -61,6 +61,9 @@ void iwl_mld_handle_rx_queues_sync_notif(struct iwl_mld *mld, struct napi_struct *napi, struct iwl_rx_packet *pkt, int queue); +void iwl_mld_handle_rsc_notif(struct iwl_mld *mld, + struct iwl_rx_packet *pkt, int queue); + void iwl_mld_pass_packet_to_mac80211(struct iwl_mld *mld, struct napi_struct *napi, struct sk_buff *skb, int queue, -- 2.34.1 From: Avinash Bhatt For 6 GHz duplicated beacons, the RSSI is measured only on the 20 MHz primary channel while the actual beacon energy spans the full operational bandwidth. This leads to underestimated link quality. Detect duplicated beacons and apply bandwidth-based RSSI adjustments. Signed-off-by: Avinash Bhatt Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/mld/link.c | 67 ++++++++++++++++++- 1 file changed, 64 insertions(+), 3 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/mld/link.c b/drivers/net/wireless/intel/iwlwifi/mld/link.c index a8d146edc4bd..c07a0d6b0bd4 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/link.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/link.c @@ -894,6 +894,60 @@ iwl_mld_get_avail_chan_load(struct iwl_mld *mld, return MAX_CHAN_LOAD - iwl_mld_get_chan_load(mld, link_conf); } +static s8 +iwl_mld_get_dup_beacon_rssi_adjust(struct iwl_mld *mld, + struct ieee80211_bss_conf *link_conf) +{ + const struct ieee80211_he_6ghz_oper *he_6ghz_oper; + const struct cfg80211_bss_ies *beacon_ies; + const struct element *elem; + + /* Duplicated beacon feature is only specific to 6 GHz */ + if (WARN_ONCE(link_conf->chanreq.oper.chan->band != NL80211_BAND_6GHZ, + "Unexpected band %d\n", + link_conf->chanreq.oper.chan->band)) + return 0; + + lockdep_assert_wiphy(mld->wiphy); + + beacon_ies = wiphy_dereference(mld->wiphy, link_conf->bss->beacon_ies); + if (!beacon_ies) + return 0; + + elem = cfg80211_find_ext_elem(WLAN_EID_EXT_HE_OPERATION, + beacon_ies->data, beacon_ies->len); + if (!elem || + elem->datalen < sizeof(struct ieee80211_he_operation) + 1 || + elem->datalen < ieee80211_he_oper_size(&elem->data[1])) + return 0; + + he_6ghz_oper = ieee80211_he_6ghz_oper((const void *)&elem->data[1]); + if (!he_6ghz_oper) + return 0; + + if (!(he_6ghz_oper->control & IEEE80211_HE_6GHZ_OPER_CTRL_DUP_BEACON)) + return 0; + + /* Apply adjustment based on operational bandwidth */ + switch (link_conf->chanreq.oper.width) { + case NL80211_CHAN_WIDTH_20: + case NL80211_CHAN_WIDTH_20_NOHT: + return 0; + case NL80211_CHAN_WIDTH_40: + return 3; + case NL80211_CHAN_WIDTH_80: + return 6; + case NL80211_CHAN_WIDTH_160: + return 9; + case NL80211_CHAN_WIDTH_320: + return 12; + default: + WARN_ONCE(1, "Unexpected channel width: %d\n", + link_conf->chanreq.oper.width); + return 0; + } +} + /* This function calculates the grade of a link. Returns 0 in error case */ unsigned int iwl_mld_get_link_grade(struct iwl_mld *mld, struct ieee80211_bss_conf *link_conf) @@ -903,7 +957,7 @@ unsigned int iwl_mld_get_link_grade(struct iwl_mld *mld, s32 link_rssi; unsigned int grade = MAX_GRADE; - if (WARN_ON_ONCE(!link_conf)) + if (WARN_ON_ONCE(!link_conf || !link_conf->bss)) return 0; band = link_conf->chanreq.oper.chan->band; @@ -918,8 +972,15 @@ unsigned int iwl_mld_get_link_grade(struct iwl_mld *mld, * For 6 GHz the RSSI of the beacons is lower than * the RSSI of the data. */ - if (band == NL80211_BAND_6GHZ && link_rssi) - link_rssi += 4; + if (band == NL80211_BAND_6GHZ && link_rssi) { + s8 rssi_adj_6g = + iwl_mld_get_dup_beacon_rssi_adjust(mld, link_conf); + + if (!rssi_adj_6g) + rssi_adj_6g = 4; + + link_rssi += rssi_adj_6g; + } rssi_idx = band == NL80211_BAND_2GHZ ? 0 : 1; -- 2.34.1 From: Avraham Stern Version 2 of the NAN schedule config command adds support for deferred schedule update and passing the availability attribute blob to firmware. Add support for the new command version. Currently the new functionality is not supported. Signed-off-by: Avraham Stern Signed-off-by: Miri Korenblit --- .../wireless/intel/iwlwifi/fw/api/mac-cfg.h | 40 ++++++++++++++++++- drivers/net/wireless/intel/iwlwifi/mld/nan.c | 22 ++++++++-- 2 files changed, 57 insertions(+), 5 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/fw/api/mac-cfg.h b/drivers/net/wireless/intel/iwlwifi/fw/api/mac-cfg.h index 75b477319096..dd850aeebdec 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/api/mac-cfg.h +++ b/drivers/net/wireless/intel/iwlwifi/fw/api/mac-cfg.h @@ -1253,6 +1253,27 @@ struct iwl_nan_config_cmd { u8 beacon_data[]; } __packed; /* NAN_CONFIG_CMD_API_S_VER_1 */ +/** + * struct iwl_nan_schedule_cmd_v1 - NAN schedule command + * @channels: per channel information + * @channels.availability_map: bitmap of slots this channel is advertising + * availability on, will be ULW'ed out if no link/inactive link is + * referenced by the link ID below + * @channels.channel_entry: NAN channel entry descriptor + * @channels.link_id: FW link ID, or %0xFF for unset + * @channels.reserved: (reserved) + */ +struct iwl_nan_schedule_cmd_v1 { + struct { + __le32 availability_map; + u8 channel_entry[6]; + u8 link_id; + u8 reserved; + } __packed channels[NUM_PHY_CTX]; +} __packed; /* NAN_SCHEDULE_CMD_API_S_VER_1 */ + +#define IWL_MAX_AVAILABILITY_ATTR_LEN 54 + /** * struct iwl_nan_schedule_cmd - NAN schedule command * @channels: per channel information @@ -1262,6 +1283,14 @@ struct iwl_nan_config_cmd { * @channels.channel_entry: NAN channel entry descriptor * @channels.link_id: FW link ID, or %0xFF for unset * @channels.reserved: (reserved) + * @avail_attr: NAN availability attribute information + * @avail_attr.attr_len: length of the availability attribute + * @avail_attr.reserved: reserved + * @avail_attr.attr: the availability attribute including the attribute header + * @deferred: true if the firmware should defer applying the schedule until + * notifying all peers. For a deferred schedule update, the firmware should + * send a notification to the driver after the new schedule is applied. + * @reserved: reserved */ struct iwl_nan_schedule_cmd { struct { @@ -1270,7 +1299,16 @@ struct iwl_nan_schedule_cmd { u8 link_id; u8 reserved; } __packed channels[NUM_PHY_CTX]; -} __packed; /* NAN_SCHEDULE_CMD_API_S_VER_1 */ + + struct { + u8 attr_len; + u8 reserved; + u8 attr[IWL_MAX_AVAILABILITY_ATTR_LEN]; + } __packed avail_attr; + + u8 deferred; + u8 reserved[3]; +} __packed; /* NAN_SCHEDULE_CMD_API_S_VER_2 */ /** * struct iwl_nan_peer_cmd - NAN peer command diff --git a/drivers/net/wireless/intel/iwlwifi/mld/nan.c b/drivers/net/wireless/intel/iwlwifi/mld/nan.c index 913f0acf172a..53a0c3b9bb33 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/nan.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/nan.c @@ -546,11 +546,27 @@ void iwl_mld_nan_vif_cfg_changed(struct iwl_mld *mld, bool added_links = false; bool empty_schedule = true; int ret, i; + u16 cmd_size; + u32 cmd_id = WIDE_ID(MAC_CONF_GROUP, NAN_SCHEDULE_CMD); + u8 version = iwl_fw_lookup_cmd_ver(mld->fw, cmd_id, 0); if (!(changes & BSS_CHANGED_NAN_LOCAL_SCHED)) return; - for (i = 0; i < ARRAY_SIZE(sched_cfg->channels); i++) { + switch (version) { + case 1: + cmd_size = sizeof(struct iwl_nan_schedule_cmd_v1); + break; + case 2: + cmd_size = sizeof(struct iwl_nan_schedule_cmd); + break; + default: + IWL_ERR(mld, "NAN: unsupported NAN schedule cmd version %d\n", + version); + return; + } + + for (i = 0; i < ARRAY_SIZE(sched_cfg->channels); i++) { if (!sched_cfg->channels[i].chanreq.oper.chan) continue; empty_schedule = false; @@ -647,9 +663,7 @@ void iwl_mld_nan_vif_cfg_changed(struct iwl_mld *mld, cmd.channels[chan_idx].availability_map |= cpu_to_le32(BIT(i)); } - ret = iwl_mld_send_cmd_pdu(mld, - WIDE_ID(MAC_CONF_GROUP, NAN_SCHEDULE_CMD), - &cmd); + ret = iwl_mld_send_cmd_pdu(mld, cmd_id, &cmd, cmd_size); if (ret) IWL_ERR(mld, "NAN: failed to update schedule (%d)\n", ret); -- 2.34.1 From: Ilan Peer The following limitations arise from the current FW support for NAN: - While NAN synchronization and discovery beacons are sent internally by the firmware, the BIGTK is configured to the auxiliary station associated with the NAN operation. Thus, the beacons are transmitted unprotected. - The auxiliary station cannot be configured with support for management frame protection as this is not supported by the firmware. Thus, there is no way to protect the SDFs and the NAFs. To overcome the above limitations the firmware introduced the following new station types: - NAN broadcast station: Used for NAN synchronization and discovery. i.e., used for beacon transmissions. A BIGTK can be configured to this station and thus beacons can be transmitted with protection. - NAN management station: Used for sending SDFs and NAFs. This station can be configured with support for management frame protection etc. Modify the iwlmld logic to support the older and the newer firmware designs. As no Tx queue is needed for the NAN broadcast station, modify the internal station support to allow adding/removing a station without a queue. In addition, since no links are associated with these stations, modify the internal station support to allow adding a station without a link mask. Signed-off-by: Ilan Peer Signed-off-by: Miri Korenblit --- .../wireless/intel/iwlwifi/fw/api/mac-cfg.h | 15 ++- .../net/wireless/intel/iwlwifi/mld/iface.c | 8 ++ .../net/wireless/intel/iwlwifi/mld/iface.h | 6 + drivers/net/wireless/intel/iwlwifi/mld/nan.c | 123 +++++++++++++++--- drivers/net/wireless/intel/iwlwifi/mld/nan.h | 2 + drivers/net/wireless/intel/iwlwifi/mld/sta.c | 69 +++++++--- drivers/net/wireless/intel/iwlwifi/mld/sta.h | 13 ++ drivers/net/wireless/intel/iwlwifi/mld/tx.c | 5 +- 8 files changed, 200 insertions(+), 41 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/fw/api/mac-cfg.h b/drivers/net/wireless/intel/iwlwifi/fw/api/mac-cfg.h index dd850aeebdec..1df81df5830e 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/api/mac-cfg.h +++ b/drivers/net/wireless/intel/iwlwifi/fw/api/mac-cfg.h @@ -735,6 +735,10 @@ struct iwl_link_config_cmd { * @STATION_TYPE_NAN_PEER_NDI: NAN data peer station type. A station * of this type can have any number of links (even none) set in the * link_mask. (Supported since version 3.) + * @STATION_TYPE_NAN_BCAST: NAN station used for synchronization and + * 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_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. @@ -745,6 +749,8 @@ enum iwl_fw_sta_type { STATION_TYPE_MCAST, STATION_TYPE_NAN_PEER_NMI, STATION_TYPE_NAN_PEER_NDI, + STATION_TYPE_NAN_BCAST, + STATION_TYPE_NAN_MGMT, STATION_TYPE_MAX, STATION_TYPE_AUX = STATION_TYPE_MAX /* this doesn't exist in FW */ }; /* STATION_TYPE_E_VER_1, _VER_2 */ @@ -882,7 +888,9 @@ struct iwl_sta_cfg_cmd_v2 { * ( STA_CONFIG_CMD = 0xA ) * * @sta_id: index of station in uCode's station table - * @link_mask: bitmap of link FW IDs used with this STA + * @link_mask: bitmap of link FW IDs used with this STA. Should be set to 0 + * for STATION_TYPE_NAN_BCAST and STATION_TYPE_NAN_MGMT as they are not + * associated with any link added by the driver. * @peer_mld_address: the peers mld address * @reserved_for_peer_mld_address: reserved * @peer_link_address: the address of the link that is used to communicate @@ -1213,7 +1221,8 @@ enum iwl_nan_flags { * @discovery_beacon_interval: discovery beacon interval in TUs * @cluster_id: lower last two bytes of the cluster ID, in case the local * device starts a cluster - * @sta_id: station ID of the NAN station + * @sta_id: station ID of the NAN station. Used only in version 1, in version 2 + * it is reserved. * @hb_channel: channel for 5 GHz if the device supports operation on 5 GHz. * Valid values are 44 and 149, which correspond to the 5 GHz channel, and * 0 which means that NAN operation on the 5 GHz band is disabled. @@ -1251,7 +1260,7 @@ struct iwl_nan_config_cmd { __le32 nan_attr_len; __le32 nan_vendor_elems_len; u8 beacon_data[]; -} __packed; /* NAN_CONFIG_CMD_API_S_VER_1 */ +} __packed; /* NAN_CONFIG_CMD_API_S_VER_1, NAN_CONFIG_CMD_API_S_VER_2 */ /** * struct iwl_nan_schedule_cmd_v1 - NAN schedule command diff --git a/drivers/net/wireless/intel/iwlwifi/mld/iface.c b/drivers/net/wireless/intel/iwlwifi/mld/iface.c index 6f2590f9a69b..2a270d689de8 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/iface.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/iface.c @@ -61,6 +61,11 @@ void iwl_mld_cleanup_vif(void *data, u8 *mac, struct ieee80211_vif *vif) /* Clean up NAN links */ for (int i = 0; i < ARRAY_SIZE(mld_vif->nan.links); i++) iwl_mld_cleanup_nan_link(&mld_vif->nan.links[i]); + + if (mld_vif->nan.bcast_sta.sta_id != IWL_INVALID_STA) + iwl_mld_free_internal_sta(mld, &mld_vif->nan.bcast_sta); + if (mld_vif->nan.mgmt_sta.sta_id != IWL_INVALID_STA) + iwl_mld_free_internal_sta(mld, &mld_vif->nan.mgmt_sta); } CLEANUP_STRUCT(mld_vif); @@ -526,6 +531,9 @@ iwl_mld_init_vif(struct iwl_mld *mld, struct ieee80211_vif *vif) memset(&mld_vif->nan.links[i], 0, sizeof(mld_vif->nan.links[i])); mld_vif->nan.links[i].fw_id = FW_CTXT_ID_INVALID; } + + iwl_mld_init_internal_sta(&mld_vif->nan.bcast_sta); + iwl_mld_init_internal_sta(&mld_vif->nan.mgmt_sta); } iwl_mld_init_internal_sta(&mld_vif->aux_sta); diff --git a/drivers/net/wireless/intel/iwlwifi/mld/iface.h b/drivers/net/wireless/intel/iwlwifi/mld/iface.h index d6fb58e785e5..75b6727503d3 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/iface.h +++ b/drivers/net/wireless/intel/iwlwifi/mld/iface.h @@ -155,6 +155,10 @@ struct iwl_mld_emlsr { * @nan: NAN parameters * @nan.links: NAN links for FW (indexed by FW link ID) * @nan.mac_added: track whether or not the MAC was added to FW + * @nan.bcast_sta: internal station used for NAN synchronization and discovery + * activities. No queue is associated with it. + * @nan.mgmt_sta: internal station used for NAN management frames, e.g., SDFs + * and NAFs. */ struct iwl_mld_vif { /* Add here fields that need clean up on restart */ @@ -181,6 +185,8 @@ struct iwl_mld_vif { /* use only with wiphy protection */ struct iwl_mld_nan_link links[IWL_FW_MAX_LINKS]; bool mac_added; + struct iwl_mld_int_sta bcast_sta; + struct iwl_mld_int_sta mgmt_sta; } nan; struct iwl_mld_emlsr emlsr; diff --git a/drivers/net/wireless/intel/iwlwifi/mld/nan.c b/drivers/net/wireless/intel/iwlwifi/mld/nan.c index 53a0c3b9bb33..351c726be51f 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/nan.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/nan.c @@ -50,6 +50,98 @@ 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) +{ + /* + * If the FW supports version 1 of the NAN config command, it means that + * it needs to receive the station ID of the auxiliary station in the + * NAN configuration command. Otherwise, use the NAN dedicated station + * types. + */ + return iwl_fw_lookup_cmd_ver(mld->fw, + WIDE_ID(MAC_CONF_GROUP, + NAN_CFG_CMD), 1) != 1; +} + +static const struct iwl_mld_int_sta * +iwl_mld_nan_get_mgmt_sta(struct iwl_mld *mld, struct ieee80211_vif *vif) +{ + struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif); + const struct iwl_mld_int_sta *sta; + + if (iwl_mld_nan_use_nan_stations(mld)) + sta = &mld_vif->nan.mgmt_sta; + else + sta = &mld_vif->aux_sta; + + if (WARN_ON(sta->sta_id == IWL_INVALID_STA)) + return NULL; + + return sta; +} + +int iwl_mld_nan_get_mgmt_queue(struct iwl_mld *mld, struct ieee80211_vif *vif) +{ + const struct iwl_mld_int_sta *sta = iwl_mld_nan_get_mgmt_sta(mld, vif); + + if (!sta) + return IWL_MLD_INVALID_QUEUE; + + return sta->queue_id; +} + +static void iwl_mld_nan_flush(struct iwl_mld *mld, struct ieee80211_vif *vif) +{ + const struct iwl_mld_int_sta *sta = iwl_mld_nan_get_mgmt_sta(mld, vif); + + if (!sta) + return; + + if (WARN_ON(sta->queue_id == IWL_MLD_INVALID_QUEUE)) + return; + + IWL_DEBUG_INFO(mld, "NAN: flush queues for sta=%u\n", + sta->sta_id); + + iwl_mld_flush_link_sta_txqs(mld, sta->sta_id); +} + +static void iwl_mld_nan_remove_stations(struct iwl_mld *mld, + struct ieee80211_vif *vif) +{ + struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif); + + iwl_mld_nan_flush(mld, vif); + + if (!iwl_mld_nan_use_nan_stations(mld)) { + iwl_mld_remove_aux_sta(mld, vif); + return; + } + + iwl_mld_remove_nan_bcast_sta(mld, &mld_vif->nan.bcast_sta); + iwl_mld_remove_nan_mgmt_sta(mld, &mld_vif->nan.mgmt_sta); +} + +static int iwl_mld_nan_add_stations(struct iwl_mld *mld, + struct ieee80211_vif *vif) +{ + struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif); + int ret; + + if (!iwl_mld_nan_use_nan_stations(mld)) + return iwl_mld_add_aux_sta(mld, &mld_vif->aux_sta); + + ret = iwl_mld_add_nan_bcast_sta(mld, &mld_vif->nan.bcast_sta); + if (ret) + return ret; + + ret = iwl_mld_add_nan_mgmt_sta(mld, &mld_vif->nan.mgmt_sta); + if (ret) + iwl_mld_remove_nan_bcast_sta(mld, &mld_vif->nan.bcast_sta); + + return ret; +} + static int iwl_mld_nan_config(struct iwl_mld *mld, struct ieee80211_vif *vif, struct cfg80211_nan_conf *conf, @@ -126,7 +218,12 @@ static int iwl_mld_nan_config(struct iwl_mld *mld, conf->vendor_elems_len); } - cmd.sta_id = mld_vif->aux_sta.sta_id; + /* FW needs to know about the station ID only with version 1 of the + * NAN configuration command + */ + if (!iwl_mld_nan_use_nan_stations(mld)) + cmd.sta_id = mld_vif->aux_sta.sta_id; + return iwl_mld_nan_send_config_cmd(mld, &cmd, data, conf->extra_nan_attrs_len + conf->vendor_elems_len); @@ -136,8 +233,6 @@ int iwl_mld_start_nan(struct ieee80211_hw *hw, struct ieee80211_vif *vif, struct cfg80211_nan_conf *conf) { struct iwl_mld *mld = IWL_MAC80211_GET_MLD(hw); - struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif); - struct iwl_mld_int_sta *aux_sta = &mld_vif->aux_sta; int ret; IWL_DEBUG_MAC80211(mld, "NAN: start: bands=0x%x\n", conf->bands); @@ -146,19 +241,20 @@ int iwl_mld_start_nan(struct ieee80211_hw *hw, struct ieee80211_vif *vif, if (ret) return ret; - ret = iwl_mld_add_aux_sta(mld, aux_sta); + ret = iwl_mld_nan_add_stations(mld, vif); if (ret) goto unblock_emlsr; ret = iwl_mld_nan_config(mld, vif, conf, FW_CTXT_ACTION_ADD); if (ret) { IWL_ERR(mld, "Failed to start NAN. ret=%d\n", ret); - goto remove_aux; + goto remove_stas; } + return 0; -remove_aux: - iwl_mld_remove_aux_sta(mld, vif); +remove_stas: + iwl_mld_nan_remove_stations(mld, vif); unblock_emlsr: iwl_mld_update_emlsr_block(mld, false, IWL_MLD_EMLSR_BLOCKED_NAN); @@ -186,7 +282,6 @@ int iwl_mld_stop_nan(struct ieee80211_hw *hw, struct ieee80211_vif *vif) { struct iwl_mld *mld = IWL_MAC80211_GET_MLD(hw); - struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif); struct iwl_nan_config_cmd cmd = { .action = cpu_to_le32(FW_CTXT_ACTION_REMOVE), }; @@ -203,9 +298,7 @@ int iwl_mld_stop_nan(struct ieee80211_hw *hw, /* assume that higher layer guarantees that no additional frames are * added before calling this callback */ - if (!WARN_ON(mld_vif->aux_sta.sta_id == IWL_INVALID_STA)) - iwl_mld_flush_link_sta_txqs(mld, mld_vif->aux_sta.sta_id); - iwl_mld_remove_aux_sta(mld, vif); + iwl_mld_nan_remove_stations(mld, vif); /* cancel based on object type being NAN, as the NAN objects do * not have a unique identifier associated with them @@ -279,13 +372,7 @@ void iwl_mld_handle_nan_dw_end_notif(struct iwl_mld *mld, "NAN: DW end without NAN started\n")) return; - if (WARN_ON(mld_vif->aux_sta.sta_id == IWL_INVALID_STA)) - return; - - IWL_DEBUG_INFO(mld, "NAN: flush queues for aux sta=%u\n", - mld_vif->aux_sta.sta_id); - - iwl_mld_flush_link_sta_txqs(mld, mld_vif->aux_sta.sta_id); + iwl_mld_nan_flush(mld, mld->nan_device_vif); /* TODO: currently the notification specified the band on which the DW * ended. Need to change that to the actual channel on which the next DW diff --git a/drivers/net/wireless/intel/iwlwifi/mld/nan.h b/drivers/net/wireless/intel/iwlwifi/mld/nan.h index 80e18c4ddb33..caa98dbb4a75 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/nan.h +++ b/drivers/net/wireless/intel/iwlwifi/mld/nan.h @@ -53,4 +53,6 @@ void iwl_mld_nan_vif_cfg_changed(struct iwl_mld *mld, int iwl_mld_mac802111_nan_peer_sched_changed(struct ieee80211_hw *hw, struct ieee80211_sta *sta); +int iwl_mld_nan_get_mgmt_queue(struct iwl_mld *mld, struct ieee80211_vif *vif); + #endif /* __iwl_mld_nan_h__ */ diff --git a/drivers/net/wireless/intel/iwlwifi/mld/sta.c b/drivers/net/wireless/intel/iwlwifi/mld/sta.c index f794f80b0fdd..1fae5a6ba8d4 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/sta.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/sta.c @@ -430,6 +430,8 @@ static int iwl_mld_send_sta_cmd(struct iwl_mld *mld, cmd_v2->link_id = cpu_to_le32(__ffs(le32_to_cpu(cmd->link_mask))); } 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) && + cmd->station_type != cpu_to_le32(STATION_TYPE_NAN_MGMT) && hweight32(le32_to_cpu(cmd->link_mask)) != 1)) { return -EINVAL; } @@ -1063,8 +1065,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, const struct iwl_mld_int_sta *internal_sta, - u8 fw_link_id, - const u8 *addr) + u32 link_mask, const u8 *addr) { struct iwl_sta_cfg_cmd cmd = {}; @@ -1072,7 +1073,7 @@ iwl_mld_add_internal_sta_to_fw(struct iwl_mld *mld, return iwl_mld_send_aux_sta_cmd(mld, internal_sta); cmd.sta_id = cpu_to_le32((u8)internal_sta->sta_id); - cmd.link_mask = cpu_to_le32(BIT(fw_link_id)); + cmd.link_mask = cpu_to_le32(link_mask); cmd.station_type = cpu_to_le32(internal_sta->sta_type); /* FW doesn't allow to add a IGTK/BIGTK if the sta isn't marked as MFP. @@ -1094,7 +1095,8 @@ iwl_mld_add_internal_sta_to_fw(struct iwl_mld *mld, static int iwl_mld_add_internal_sta(struct iwl_mld *mld, struct iwl_mld_int_sta *internal_sta, enum iwl_fw_sta_type sta_type, - u8 fw_link_id, const u8 *addr, u8 tid) + u32 link_mask, const u8 *addr, + u8 tid, bool add_txq) { int ret, queue_id; @@ -1106,11 +1108,14 @@ 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, fw_link_id, + ret = iwl_mld_add_internal_sta_to_fw(mld, internal_sta, link_mask, addr); if (ret) goto err; + if (!add_txq) + return 0; + queue_id = iwl_mld_allocate_internal_txq(mld, internal_sta, tid); if (queue_id < 0) { iwl_mld_rm_sta_from_fw(mld, internal_sta->sta_id); @@ -1145,8 +1150,8 @@ int iwl_mld_add_bcast_sta(struct iwl_mld *mld, return iwl_mld_add_internal_sta(mld, &mld_link->bcast_sta, STATION_TYPE_BCAST_MGMT, - mld_link->fw_id, addr, - IWL_MGMT_TID); + BIT(mld_link->fw_id), addr, + IWL_MGMT_TID, true); } int iwl_mld_add_mcast_sta(struct iwl_mld *mld, @@ -1165,14 +1170,16 @@ int iwl_mld_add_mcast_sta(struct iwl_mld *mld, return iwl_mld_add_internal_sta(mld, &mld_link->mcast_sta, STATION_TYPE_MCAST, - mld_link->fw_id, mcast_addr, 0); + BIT(mld_link->fw_id), mcast_addr, + 0, true); } int iwl_mld_add_aux_sta(struct iwl_mld *mld, struct iwl_mld_int_sta *internal_sta) { return iwl_mld_add_internal_sta(mld, internal_sta, STATION_TYPE_AUX, - 0, NULL, IWL_MAX_TID_COUNT); + 0, NULL, IWL_MAX_TID_COUNT, + true); } int iwl_mld_add_mon_sta(struct iwl_mld *mld, @@ -1189,23 +1196,25 @@ int iwl_mld_add_mon_sta(struct iwl_mld *mld, return iwl_mld_add_internal_sta(mld, &mld_link->mon_sta, STATION_TYPE_BCAST_MGMT, - mld_link->fw_id, NULL, - IWL_MAX_TID_COUNT); + BIT(mld_link->fw_id), NULL, + IWL_MAX_TID_COUNT, + true); } static void iwl_mld_remove_internal_sta(struct iwl_mld *mld, struct iwl_mld_int_sta *internal_sta, bool flush, u8 tid) { - if (WARN_ON_ONCE(internal_sta->sta_id == IWL_INVALID_STA || - internal_sta->queue_id == IWL_MLD_INVALID_QUEUE)) + if (WARN_ON_ONCE(internal_sta->sta_id == IWL_INVALID_STA)) return; - if (flush) + if (flush && !WARN_ON_ONCE(internal_sta->queue_id == + IWL_MLD_INVALID_QUEUE)) iwl_mld_flush_link_sta_txqs(mld, internal_sta->sta_id); - iwl_mld_free_txq(mld, BIT(internal_sta->sta_id), - tid, internal_sta->queue_id); + if (internal_sta->queue_id != IWL_MLD_INVALID_QUEUE) + iwl_mld_free_txq(mld, BIT(internal_sta->sta_id), + tid, internal_sta->queue_id); iwl_mld_rm_sta_from_fw(mld, internal_sta->sta_id); @@ -1406,3 +1415,31 @@ int iwl_mld_update_link_stas(struct iwl_mld *mld, return ret; } + +int iwl_mld_add_nan_bcast_sta(struct iwl_mld *mld, + struct iwl_mld_int_sta *sta) +{ + const u8 bcast_addr[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; + + return iwl_mld_add_internal_sta(mld, sta, STATION_TYPE_NAN_BCAST, + 0, bcast_addr, 0, false); +} + +int iwl_mld_add_nan_mgmt_sta(struct iwl_mld *mld, + struct iwl_mld_int_sta *sta) +{ + return iwl_mld_add_internal_sta(mld, sta, STATION_TYPE_NAN_MGMT, + 0, NULL, IWL_MAX_TID_COUNT, true); +} + +void iwl_mld_remove_nan_bcast_sta(struct iwl_mld *mld, + struct iwl_mld_int_sta *sta) +{ + iwl_mld_remove_internal_sta(mld, sta, false, 0); +} + +void iwl_mld_remove_nan_mgmt_sta(struct iwl_mld *mld, + struct iwl_mld_int_sta *sta) +{ + iwl_mld_remove_internal_sta(mld, sta, true, IWL_MAX_TID_COUNT); +} diff --git a/drivers/net/wireless/intel/iwlwifi/mld/sta.h b/drivers/net/wireless/intel/iwlwifi/mld/sta.h index dff14ff0a5af..df859a9e5230 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/sta.h +++ b/drivers/net/wireless/intel/iwlwifi/mld/sta.h @@ -276,4 +276,17 @@ int iwl_mld_update_link_stas(struct iwl_mld *mld, struct ieee80211_vif *vif, struct ieee80211_sta *sta, u16 old_links, u16 new_links); + +int iwl_mld_add_nan_bcast_sta(struct iwl_mld *mld, + struct iwl_mld_int_sta *sta); + +int iwl_mld_add_nan_mgmt_sta(struct iwl_mld *mld, + 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); + #endif /* __iwl_mld_sta_h__ */ diff --git a/drivers/net/wireless/intel/iwlwifi/mld/tx.c b/drivers/net/wireless/intel/iwlwifi/mld/tx.c index 636d24633e57..39e1d959e42c 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/tx.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/tx.c @@ -680,11 +680,8 @@ iwl_mld_get_tx_queue_id(struct iwl_mld *mld, struct ieee80211_txq *txq, WARN_ON(!ieee80211_is_mgmt(fc)); return mld_vif->aux_sta.queue_id; case NL80211_IFTYPE_NAN: - mld_vif = iwl_mld_vif_from_mac80211(info->control.vif); - WARN_ON(!ieee80211_is_mgmt(fc)); - - return mld_vif->aux_sta.queue_id; + return iwl_mld_nan_get_mgmt_queue(mld, info->control.vif); default: WARN_ONCE(1, "Unsupported vif type\n"); break; -- 2.34.1 From: Avinash Bhatt Add a KUnit test suite for iwl_mld_chan_load_requires_scan, covering level-up, level-down, and no-change transitions. The test directly sets channel-load values, validating scan-trigger decisions and updated load levels Signed-off-by: Avinash Bhatt Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/mld/link.c | 1 + .../wireless/intel/iwlwifi/mld/tests/Makefile | 1 + .../iwlwifi/mld/tests/chan_load_thresh.c | 139 ++++++++++++++++++ 3 files changed, 141 insertions(+) create mode 100644 drivers/net/wireless/intel/iwlwifi/mld/tests/chan_load_thresh.c diff --git a/drivers/net/wireless/intel/iwlwifi/mld/link.c b/drivers/net/wireless/intel/iwlwifi/mld/link.c index c07a0d6b0bd4..805f2e2eac38 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/link.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/link.c @@ -852,6 +852,7 @@ bool iwl_mld_chan_load_requires_scan(struct iwl_mld *mld, return scan_trig; } +EXPORT_SYMBOL_IF_IWLWIFI_KUNIT(iwl_mld_chan_load_requires_scan); static unsigned int iwl_mld_get_default_chan_load(struct ieee80211_bss_conf *link_conf) diff --git a/drivers/net/wireless/intel/iwlwifi/mld/tests/Makefile b/drivers/net/wireless/intel/iwlwifi/mld/tests/Makefile index 36317feb923b..efa61638b8ee 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/tests/Makefile +++ b/drivers/net/wireless/intel/iwlwifi/mld/tests/Makefile @@ -1,5 +1,6 @@ # SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause iwlmld-tests-y += module.o hcmd.o utils.o link.o rx.o agg.o link-selection.o +iwlmld-tests-y += chan_load_thresh.o ccflags-y += -I$(src)/../ obj-$(CONFIG_IWLWIFI_KUNIT_TESTS) += iwlmld-tests.o diff --git a/drivers/net/wireless/intel/iwlwifi/mld/tests/chan_load_thresh.c b/drivers/net/wireless/intel/iwlwifi/mld/tests/chan_load_thresh.c new file mode 100644 index 000000000000..87e29e09949b --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/mld/tests/chan_load_thresh.c @@ -0,0 +1,139 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * KUnit tests for channel helper functions + * + * Copyright (C) 2026 Intel Corporation + */ +#include +#include "mld.h" +#include "link.h" +#include "iface.h" +#include "utils.h" + +MODULE_IMPORT_NS("EXPORTED_FOR_KUNIT_TESTING"); + +struct test_chan_load_case { + const char *desc; + u32 load; + enum iwl_mld_link_chan_load_level old_lvl; + enum iwl_mld_link_chan_load_level expected_lvl; + bool expected_scan_trig; +}; + +static const struct test_chan_load_case test_chan_load_thresh_cases[] = { + /* Level-up transitions */ + { + .desc = "Transition NONE->NONE", + .load = 20, + .old_lvl = LINK_CHAN_LOAD_LVL_NONE, + .expected_lvl = LINK_CHAN_LOAD_LVL_NONE, + .expected_scan_trig = false, + }, + { + .desc = "Transition NONE->LVL1", + .load = 50, + .old_lvl = LINK_CHAN_LOAD_LVL_NONE, + .expected_lvl = LINK_CHAN_LOAD_LVL1, + .expected_scan_trig = true, + }, + { + .desc = "Transition LVL1->LVL2", + .load = 75, + .old_lvl = LINK_CHAN_LOAD_LVL1, + .expected_lvl = LINK_CHAN_LOAD_LVL2, + .expected_scan_trig = true, + }, + { + .desc = "Transition LVL2->LVL3", + .load = 90, + .old_lvl = LINK_CHAN_LOAD_LVL2, + .expected_lvl = LINK_CHAN_LOAD_LVL3, + .expected_scan_trig = true, + }, + + /* Level-down transitions */ + { + .desc = "Transition LVL1->NONE", + .load = 30, + .old_lvl = LINK_CHAN_LOAD_LVL1, + .expected_lvl = LINK_CHAN_LOAD_LVL_NONE, + .expected_scan_trig = false, + }, + { + .desc = "Transition LVL2->LVL1", + .load = 60, + .old_lvl = LINK_CHAN_LOAD_LVL2, + .expected_lvl = LINK_CHAN_LOAD_LVL1, + .expected_scan_trig = false, + }, + { + .desc = "Transition LVL3->LVL2", + .load = 70, + .old_lvl = LINK_CHAN_LOAD_LVL3, + .expected_lvl = LINK_CHAN_LOAD_LVL2, + .expected_scan_trig = false, + }, + + /* No change */ + { + .desc = "Transition LVL2->LVL2", + .load = 72, + .old_lvl = LINK_CHAN_LOAD_LVL2, + .expected_lvl = LINK_CHAN_LOAD_LVL2, + .expected_scan_trig = false, + }, +}; + +KUNIT_ARRAY_PARAM_DESC(test_chan_load_thresh_cases, + test_chan_load_thresh_cases, desc); + +static void test_chan_load_thresholds(struct kunit *test) +{ + const struct test_chan_load_case *tc = test->param_value; + struct iwl_mld *mld = test->priv; + struct ieee80211_vif *vif; + struct iwl_mld_vif *mld_vif; + struct ieee80211_bss_conf *link_conf; + struct iwl_mld_link *mld_link; + struct iwl_mld_kunit_link assoc_link = { + .id = 0, + .chandef = &chandef_6ghz_160mhz, + }; + bool scan_trig; + u32 chan_load; + + /* Setup associated non-MLO station */ + vif = iwlmld_kunit_setup_non_mlo_assoc(&assoc_link); + mld_vif = iwl_mld_vif_from_mac80211(vif); + + link_conf = &vif->bss_conf; + mld_link = &mld_vif->deflink; + + chan_load = tc->load; + mld_link->chan_load_lvl = tc->old_lvl; + + /* Execute function under test */ + wiphy_lock(mld->wiphy); + scan_trig = iwl_mld_chan_load_requires_scan(mld, link_conf, chan_load); + wiphy_unlock(mld->wiphy); + + /* Check return value */ + KUNIT_EXPECT_EQ(test, tc->expected_scan_trig, scan_trig); + + /* Check updated channel-load level */ + KUNIT_EXPECT_EQ(test, tc->expected_lvl, mld_link->chan_load_lvl); +} + +static struct kunit_case chan_load_thresh_test_cases[] = { + KUNIT_CASE_PARAM(test_chan_load_thresholds, + test_chan_load_thresh_cases_gen_params), + {} +}; + +static struct kunit_suite chan_load_thresh_test_suite = { + .name = "iwl_mld_chan_load_threshold_tests", + .init = iwlmld_kunit_test_init, + .test_cases = chan_load_thresh_test_cases, +}; + +kunit_test_suite(chan_load_thresh_test_suite); -- 2.34.1