From b2d98a6cee3ae02a681f8cae01161ff803001e69 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Wed, 30 Apr 2025 15:23:12 +0300 Subject: [PATCH 01/16] wifi: iwlwifi: avoid scheduling restart during restart When a restart is in progress, it can be async due to the next worker being scheduled in mac80211 (restart work) or the driver itself (reprobe). Avoid scheduling another restart during this period. Signed-off-by: Johannes Berg Signed-off-by: Miri Korenblit Link: https://patch.msgid.link/20250430151952.db428503ec3c.I3f2be3d72a9d40cfb4e697cdab1cd2866a9262bc@changeid --- drivers/net/wireless/intel/iwlwifi/iwl-trans.c | 2 ++ drivers/net/wireless/intel/iwlwifi/iwl-trans.h | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-trans.c b/drivers/net/wireless/intel/iwlwifi/iwl-trans.c index c1607b6d0759..75d70021ee03 100644 --- a/drivers/net/wireless/intel/iwlwifi/iwl-trans.c +++ b/drivers/net/wireless/intel/iwlwifi/iwl-trans.c @@ -393,6 +393,8 @@ int iwl_trans_start_hw(struct iwl_trans *trans) { might_sleep(); + clear_bit(STATUS_TRANS_RESET_IN_PROGRESS, &trans->status); + return iwl_trans_pcie_start_hw(trans); } IWL_EXPORT_SYMBOL(iwl_trans_start_hw); diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-trans.h b/drivers/net/wireless/intel/iwlwifi/iwl-trans.h index b9dc1b8794ce..8fae7f59a73e 100644 --- a/drivers/net/wireless/intel/iwlwifi/iwl-trans.h +++ b/drivers/net/wireless/intel/iwlwifi/iwl-trans.h @@ -304,6 +304,8 @@ enum iwl_d3_status { * via iwl_trans_finish_sw_reset() * @STATUS_RESET_PENDING: reset worker was scheduled, but didn't dump * the firmware state yet + * @STATUS_TRANS_RESET_IN_PROGRESS: reset is still in progress, don't + * attempt another reset yet */ enum iwl_trans_status { STATUS_SYNC_HCMD_ACTIVE, @@ -317,6 +319,7 @@ enum iwl_trans_status { STATUS_SUPPRESS_CMD_ERROR_ONCE, STATUS_IN_SW_RESET, STATUS_RESET_PENDING, + STATUS_TRANS_RESET_IN_PROGRESS, }; static inline int @@ -1152,6 +1155,9 @@ static inline void iwl_trans_schedule_reset(struct iwl_trans *trans, { if (test_bit(STATUS_TRANS_DEAD, &trans->status)) return; + /* clear this on device init, not cleared on any unbind/reprobe */ + if (test_and_set_bit(STATUS_TRANS_RESET_IN_PROGRESS, &trans->status)) + return; trans->restart.mode.type = type; trans->restart.mode.context = IWL_ERR_CONTEXT_WORKER; -- 2.51.0 From 822c7bd5ef25017883f99e120f2145320ddb8750 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Wed, 30 Apr 2025 15:23:14 +0300 Subject: [PATCH 02/16] wifi: iwlwifi: implement TOP reset follower For the upcoming SC hardware, a new reset mode "(silent) TOP reset" will be available. When BT initiates that reset, it'll negotiate with the WiFi firmware which makes it appear to the driver as the reset interrupt. To distinguish it from all the other reasons for the reset interrupt, there's (now) a status field in CSR 0x110. Implement the part of TOP reset where we react to BT doing it. This requires disambiguating the interrupt, depending on the state of the device, since we can even get TOP reset from BT while waiting for the reset handshake. If TOP reset is done by BT while we're not trying to do reset anyway, then simply reprobe, since we cannot keep the state of the device as it's being reset, after waiting the needed 180ms to let the device reset/settle. Signed-off-by: Johannes Berg Signed-off-by: Miri Korenblit Link: https://patch.msgid.link/20250430151952.fb86bfbdca40.Ibe40bf54003e3f8929b671324a395e76eb64a4d8@changeid --- drivers/net/wireless/intel/iwlwifi/iwl-csr.h | 7 +++ .../net/wireless/intel/iwlwifi/iwl-op-mode.h | 7 ++- .../net/wireless/intel/iwlwifi/iwl-trans.c | 56 ++++++++++------- .../net/wireless/intel/iwlwifi/iwl-trans.h | 3 + drivers/net/wireless/intel/iwlwifi/pcie/rx.c | 60 ++++++++++++++++--- 5 files changed, 103 insertions(+), 30 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-csr.h b/drivers/net/wireless/intel/iwlwifi/iwl-csr.h index f259747e21b3..54765f585b9a 100644 --- a/drivers/net/wireless/intel/iwlwifi/iwl-csr.h +++ b/drivers/net/wireless/intel/iwlwifi/iwl-csr.h @@ -107,6 +107,13 @@ /* GIO Chicken Bits (PCI Express bus link power management) */ #define CSR_GIO_CHICKEN_BITS (CSR_BASE+0x100) +#define CSR_IPC_STATE (CSR_BASE + 0x110) +#define CSR_IPC_STATE_RESET 0x00000030 +#define CSR_IPC_STATE_RESET_NONE 0 +#define CSR_IPC_STATE_RESET_SW_READY 1 +#define CSR_IPC_STATE_RESET_TOP_READY 2 +#define CSR_IPC_STATE_RESET_TOP_FOLLOWER 3 + #define CSR_IPC_SLEEP_CONTROL (CSR_BASE + 0x114) #define CSR_IPC_SLEEP_CONTROL_SUSPEND 0x3 #define CSR_IPC_SLEEP_CONTROL_RESUME 0 diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-op-mode.h b/drivers/net/wireless/intel/iwlwifi/iwl-op-mode.h index 34eca1a568ea..6bccb30c0981 100644 --- a/drivers/net/wireless/intel/iwlwifi/iwl-op-mode.h +++ b/drivers/net/wireless/intel/iwlwifi/iwl-op-mode.h @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ /* - * Copyright (C) 2005-2014, 2018-2021, 2024 Intel Corporation + * Copyright (C) 2005-2014, 2018-2021, 2024-2025 Intel Corporation * Copyright (C) 2013-2014 Intel Mobile Communications GmbH * Copyright (C) 2015 Intel Deutschland GmbH */ @@ -52,12 +52,14 @@ struct iwl_cfg; * any debug collection must happen synchronously as * the device will be shut down * @IWL_ERR_TYPE_CMD_QUEUE_FULL: command queue was full + * @IWL_ERR_TYPE_TOP_RESET_BY_BT: TOP reset initiated by BT */ enum iwl_fw_error_type { IWL_ERR_TYPE_IRQ, IWL_ERR_TYPE_NMI_FORCED, IWL_ERR_TYPE_RESET_HS_TIMEOUT, IWL_ERR_TYPE_CMD_QUEUE_FULL, + IWL_ERR_TYPE_TOP_RESET_BY_BT, }; /** @@ -242,6 +244,9 @@ static inline void iwl_op_mode_dump_error(struct iwl_op_mode *op_mode, { might_sleep(); + if (WARN_ON(mode->type == IWL_ERR_TYPE_TOP_RESET_BY_BT)) + return; + if (op_mode->ops->dump_error) op_mode->ops->dump_error(op_mode, mode); } diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-trans.c b/drivers/net/wireless/intel/iwlwifi/iwl-trans.c index 75d70021ee03..71baf09d2f04 100644 --- a/drivers/net/wireless/intel/iwlwifi/iwl-trans.c +++ b/drivers/net/wireless/intel/iwlwifi/iwl-trans.c @@ -81,14 +81,14 @@ void iwl_trans_free_restart_list(void) struct iwl_trans_reprobe { struct device *dev; - struct work_struct work; + struct delayed_work work; }; static void iwl_trans_reprobe_wk(struct work_struct *wk) { struct iwl_trans_reprobe *reprobe; - reprobe = container_of(wk, typeof(*reprobe), work); + reprobe = container_of(wk, typeof(*reprobe), work.work); if (device_reprobe(reprobe->dev)) dev_err(reprobe->dev, "reprobe failed!\n"); @@ -97,6 +97,31 @@ static void iwl_trans_reprobe_wk(struct work_struct *wk) module_put(THIS_MODULE); } +static void iwl_trans_schedule_reprobe(struct iwl_trans *trans, + unsigned int delay_ms) +{ + struct iwl_trans_reprobe *reprobe; + + /* + * get a module reference to avoid doing this while unloading + * anyway and to avoid scheduling a work with code that's + * being removed. + */ + if (!try_module_get(THIS_MODULE)) { + IWL_ERR(trans, "Module is being unloaded - abort\n"); + return; + } + + reprobe = kzalloc(sizeof(*reprobe), GFP_KERNEL); + if (!reprobe) { + module_put(THIS_MODULE); + return; + } + reprobe->dev = get_device(trans->dev); + INIT_DELAYED_WORK(&reprobe->work, iwl_trans_reprobe_wk); + schedule_delayed_work(&reprobe->work, msecs_to_jiffies(delay_ms)); +} + #define IWL_TRANS_RESET_OK_TIME 7 /* seconds */ static enum iwl_reset_mode @@ -136,14 +161,20 @@ iwl_trans_determine_restart_mode(struct iwl_trans *trans) return max(at_least, escalation_list[index]); } +#define IWL_TRANS_TOP_FOLLOWER_WAIT 180 /* ms */ + #define IWL_TRANS_RESET_DELAY (HZ * 60) static void iwl_trans_restart_wk(struct work_struct *wk) { struct iwl_trans *trans = container_of(wk, typeof(*trans), restart.wk); - struct iwl_trans_reprobe *reprobe; enum iwl_reset_mode mode; + if (trans->restart.mode.type == IWL_ERR_TYPE_TOP_RESET_BY_BT) { + iwl_trans_schedule_reprobe(trans, IWL_TRANS_TOP_FOLLOWER_WAIT); + return; + } + if (!trans->op_mode) return; @@ -179,24 +210,7 @@ static void iwl_trans_restart_wk(struct work_struct *wk) case IWL_RESET_MODE_REPROBE: IWL_ERR(trans, "Device error - reprobe!\n"); - /* - * get a module reference to avoid doing this while unloading - * anyway and to avoid scheduling a work with code that's - * being removed. - */ - if (!try_module_get(THIS_MODULE)) { - IWL_ERR(trans, "Module is being unloaded - abort\n"); - return; - } - - reprobe = kzalloc(sizeof(*reprobe), GFP_KERNEL); - if (!reprobe) { - module_put(THIS_MODULE); - return; - } - reprobe->dev = get_device(trans->dev); - INIT_WORK(&reprobe->work, iwl_trans_reprobe_wk); - schedule_work(&reprobe->work); + iwl_trans_schedule_reprobe(trans, 0); break; default: iwl_trans_pcie_reset(trans, mode); diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-trans.h b/drivers/net/wireless/intel/iwlwifi/iwl-trans.h index 8fae7f59a73e..91abab56faba 100644 --- a/drivers/net/wireless/intel/iwlwifi/iwl-trans.h +++ b/drivers/net/wireless/intel/iwlwifi/iwl-trans.h @@ -1195,6 +1195,9 @@ static inline void iwl_trans_opmode_sw_reset(struct iwl_trans *trans, set_bit(STATUS_IN_SW_RESET, &trans->status); + if (WARN_ON(type == IWL_ERR_TYPE_TOP_RESET_BY_BT)) + return; + if (!trans->op_mode->ops->sw_reset || !trans->op_mode->ops->sw_reset(trans->op_mode, type)) clear_bit(STATUS_IN_SW_RESET, &trans->status); diff --git a/drivers/net/wireless/intel/iwlwifi/pcie/rx.c b/drivers/net/wireless/intel/iwlwifi/pcie/rx.c index 337324eea1a1..bbeecb621593 100644 --- a/drivers/net/wireless/intel/iwlwifi/pcie/rx.c +++ b/drivers/net/wireless/intel/iwlwifi/pcie/rx.c @@ -13,6 +13,7 @@ #include "internal.h" #include "iwl-op-mode.h" #include "iwl-context-info-gen3.h" +#include "fw/dbg.h" /****************************************************************************** * @@ -1828,6 +1829,54 @@ void iwl_pcie_handle_rfkill_irq(struct iwl_trans *trans, bool from_irq) } } +static void iwl_trans_pcie_handle_reset_interrupt(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + u32 state; + + if (trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_SC) { + u32 val = iwl_read32(trans, CSR_IPC_STATE); + + state = u32_get_bits(val, CSR_IPC_STATE_RESET); + IWL_DEBUG_ISR(trans, "IPC state = 0x%x/%d\n", val, state); + } else { + state = CSR_IPC_STATE_RESET_SW_READY; + } + + switch (state) { + case CSR_IPC_STATE_RESET_SW_READY: + if (trans_pcie->fw_reset_state == FW_RESET_REQUESTED) { + IWL_DEBUG_ISR(trans, "Reset flow completed\n"); + trans_pcie->fw_reset_state = FW_RESET_OK; + wake_up(&trans_pcie->fw_reset_waitq); + break; + } + fallthrough; + case CSR_IPC_STATE_RESET_TOP_READY: + /* FIXME: handle this case when requesting TOP reset */ + fallthrough; + case CSR_IPC_STATE_RESET_NONE: + IWL_FW_CHECK_FAILED(trans, + "Invalid reset interrupt (state=%d)!\n", + state); + break; + case CSR_IPC_STATE_RESET_TOP_FOLLOWER: + if (trans_pcie->fw_reset_state == FW_RESET_REQUESTED) { + /* if we were in reset, wake that up */ + IWL_INFO(trans, + "TOP reset from BT while doing reset\n"); + trans_pcie->fw_reset_state = FW_RESET_OK; + wake_up(&trans_pcie->fw_reset_waitq); + } else { + IWL_INFO(trans, "TOP reset from BT\n"); + trans->state = IWL_TRANS_NO_FW; + iwl_trans_schedule_reset(trans, + IWL_ERR_TYPE_TOP_RESET_BY_BT); + } + break; + } +} + irqreturn_t iwl_pcie_irq_handler(int irq, void *dev_id) { struct iwl_trans *trans = dev_id; @@ -1948,10 +1997,8 @@ irqreturn_t iwl_pcie_irq_handler(int irq, void *dev_id) } if (inta & CSR_INT_BIT_RESET_DONE) { - IWL_DEBUG_ISR(trans, "Reset flow completed\n"); - trans_pcie->fw_reset_state = FW_RESET_OK; + iwl_trans_pcie_handle_reset_interrupt(trans); handled |= CSR_INT_BIT_RESET_DONE; - wake_up(&trans_pcie->fw_reset_waitq); } /* Safely ignore these bits for debug checks below */ @@ -2400,11 +2447,8 @@ irqreturn_t iwl_pcie_irq_msix_handler(int irq, void *dev_id) iwl_pcie_irq_handle_error(trans); } - if (inta_hw & MSIX_HW_INT_CAUSES_REG_RESET_DONE) { - IWL_DEBUG_ISR(trans, "Reset flow completed\n"); - trans_pcie->fw_reset_state = FW_RESET_OK; - wake_up(&trans_pcie->fw_reset_waitq); - } + if (inta_hw & MSIX_HW_INT_CAUSES_REG_RESET_DONE) + iwl_trans_pcie_handle_reset_interrupt(trans); if (!polling) iwl_pcie_clear_irq(trans, entry->entry); -- 2.51.0 From 37808a3788fdf47af11ffa2f4f4033f3b1e0c9f2 Mon Sep 17 00:00:00 2001 From: Somashekhar Puttagangaiah Date: Wed, 30 Apr 2025 15:23:15 +0300 Subject: [PATCH 03/16] wifi: iwlwifi: mld: allow EMLSR with 2.4 GHz when BT is ON When BT is ON, EMLSR with one of the links operating on 2.4 GHz is allowed only if it meets following conditions. In this patch: 1. during link selection, when BT is ON, allow emlsr only if BT pentalty is < 7%. 2. exit EMLSR if BT is turned ON and one of the links is operating on 2.4 GHz with BT penalty > 7% Signed-off-by: Somashekhar Puttagangaiah Signed-off-by: Miri Korenblit Link: https://patch.msgid.link/20250430151952.d30212ec3901.I48e3d5bd6b0b8583f98057c38d2ee30fff5abd8a@changeid --- drivers/net/wireless/intel/iwlwifi/mld/coex.c | 8 +-- drivers/net/wireless/intel/iwlwifi/mld/mld.h | 4 +- drivers/net/wireless/intel/iwlwifi/mld/mlo.c | 69 ++++++++++++++++--- .../intel/iwlwifi/mld/tests/link-selection.c | 6 ++ 4 files changed, 69 insertions(+), 18 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/mld/coex.c b/drivers/net/wireless/intel/iwlwifi/mld/coex.c index 5f262bd43f21..32c727b3b391 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/coex.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/coex.c @@ -24,17 +24,13 @@ int iwl_mld_send_bt_init_conf(struct iwl_mld *mld) void iwl_mld_handle_bt_coex_notif(struct iwl_mld *mld, struct iwl_rx_packet *pkt) { - const struct iwl_bt_coex_profile_notif *notif = (void *)pkt->data; + const struct iwl_bt_coex_profile_notif *notif = (const void *)pkt->data; const struct iwl_bt_coex_profile_notif zero_notif = {}; /* zeroed structure means that BT is OFF */ bool bt_is_active = memcmp(notif, &zero_notif, sizeof(*notif)); - if (bt_is_active == mld->bt_is_active) - return; - + mld->last_bt_notif = *notif; IWL_DEBUG_INFO(mld, "BT was turned %s\n", bt_is_active ? "ON" : "OFF"); - mld->bt_is_active = bt_is_active; - iwl_mld_emlsr_check_bt(mld); } diff --git a/drivers/net/wireless/intel/iwlwifi/mld/mld.h b/drivers/net/wireless/intel/iwlwifi/mld/mld.h index a4a16da6ebf3..7007a43bca4d 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/mld.h +++ b/drivers/net/wireless/intel/iwlwifi/mld/mld.h @@ -126,7 +126,6 @@ * cleanup using iwl_mld_free_internal_sta * @netdetect: indicates the FW is in suspend mode with netdetect configured * @p2p_device_vif: points to the p2p device vif if exists - * @bt_is_active: indicates that BT is active * @dev: pointer to device struct. For printing purposes * @trans: pointer to the transport layer * @cfg: pointer to the device configuration @@ -184,6 +183,7 @@ * @ptp_data: data of the PTP clock * @time_sync: time sync data. * @ftm_initiator: FTM initiator data + * @last_bt_notif: last received BT Coex notif */ struct iwl_mld { /* Add here fields that need clean up on restart */ @@ -207,7 +207,7 @@ struct iwl_mld { bool netdetect; #endif /* CONFIG_PM_SLEEP */ struct ieee80211_vif *p2p_device_vif; - bool bt_is_active; + struct iwl_bt_coex_profile_notif last_bt_notif; ); struct ieee80211_link_sta __rcu *fw_id_to_link_sta[IWL_STATION_COUNT_MAX]; /* And here fields that survive a fw restart */ diff --git a/drivers/net/wireless/intel/iwlwifi/mld/mlo.c b/drivers/net/wireless/intel/iwlwifi/mld/mlo.c index 91f3a48d0c4b..c84b39794c8f 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/mlo.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/mlo.c @@ -636,6 +636,40 @@ s8 iwl_mld_get_emlsr_rssi_thresh(struct iwl_mld *mld, #undef RSSI_THRESHOLD } +#define IWL_MLD_BT_COEX_DISABLE_EMLSR_RSSI_THRESH -69 +#define IWL_MLD_BT_COEX_ENABLE_EMLSR_RSSI_THRESH -63 +#define IWL_MLD_BT_COEX_WIFI_LOSS_THRESH 7 + +static bool +iwl_mld_bt_allows_emlsr(struct iwl_mld *mld, struct ieee80211_bss_conf *link, + bool check_entry) +{ + int bt_penalty, rssi_thresh; + s32 link_rssi; + + if (WARN_ON_ONCE(!link->bss)) + return false; + + link_rssi = MBM_TO_DBM(link->bss->signal); + rssi_thresh = check_entry ? + IWL_MLD_BT_COEX_ENABLE_EMLSR_RSSI_THRESH : + IWL_MLD_BT_COEX_DISABLE_EMLSR_RSSI_THRESH; + /* No valid RSSI - force to take low rssi */ + if (!link_rssi) + link_rssi = rssi_thresh - 1; + + if (link_rssi > rssi_thresh) + bt_penalty = max(mld->last_bt_notif.wifi_loss_mid_high_rssi[PHY_BAND_24][0], + mld->last_bt_notif.wifi_loss_mid_high_rssi[PHY_BAND_24][1]); + else + bt_penalty = max(mld->last_bt_notif.wifi_loss_low_rssi[PHY_BAND_24][0], + mld->last_bt_notif.wifi_loss_low_rssi[PHY_BAND_24][1]); + + IWL_DEBUG_EHT(mld, "BT penalty for link-id %0X is %d\n", + link->link_id, bt_penalty); + return bt_penalty < IWL_MLD_BT_COEX_WIFI_LOSS_THRESH; +} + static u32 iwl_mld_emlsr_disallowed_with_link(struct iwl_mld *mld, struct ieee80211_vif *vif, @@ -650,7 +684,8 @@ iwl_mld_emlsr_disallowed_with_link(struct iwl_mld *mld, if (WARN_ON_ONCE(!conf)) return IWL_MLD_EMLSR_EXIT_INVALID; - if (link->chandef->chan->band == NL80211_BAND_2GHZ && mld->bt_is_active) + if (link->chandef->chan->band == NL80211_BAND_2GHZ && + !iwl_mld_bt_allows_emlsr(mld, conf, true)) ret |= IWL_MLD_EMLSR_EXIT_BT_COEX; if (link->signal < @@ -985,27 +1020,41 @@ static void iwl_mld_emlsr_check_bt_iter(void *_data, u8 *mac, struct ieee80211_vif *vif) { struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif); + const struct iwl_bt_coex_profile_notif zero_notif = {}; struct iwl_mld *mld = mld_vif->mld; struct ieee80211_bss_conf *link; unsigned int link_id; + const struct iwl_bt_coex_profile_notif *notif = &mld->last_bt_notif; - if (!mld->bt_is_active) { - iwl_mld_retry_emlsr(mld, vif); + if (!iwl_mld_vif_has_emlsr_cap(vif)) return; - } - /* BT is turned ON but we are not in EMLSR, nothing to do */ - if (!iwl_mld_emlsr_active(vif)) + /* zeroed structure means that BT is OFF */ + if (!memcmp(notif, &zero_notif, sizeof(*notif))) { + iwl_mld_retry_emlsr(mld, vif); return; - - /* In EMLSR and BT is turned ON */ + } for_each_vif_active_link(vif, link, link_id) { + bool emlsr_active, emlsr_allowed; + if (WARN_ON(!link->chanreq.oper.chan)) continue; - if (link->chanreq.oper.chan->band == NL80211_BAND_2GHZ) { - iwl_mld_exit_emlsr(mld, vif, IWL_MLD_EMLSR_EXIT_BT_COEX, + if (link->chanreq.oper.chan->band != NL80211_BAND_2GHZ) + continue; + + emlsr_active = iwl_mld_emlsr_active(vif); + emlsr_allowed = iwl_mld_bt_allows_emlsr(mld, link, + !emlsr_active); + if (emlsr_allowed && !emlsr_active) { + iwl_mld_retry_emlsr(mld, vif); + return; + } + + if (!emlsr_allowed && emlsr_active) { + iwl_mld_exit_emlsr(mld, vif, + IWL_MLD_EMLSR_EXIT_BT_COEX, iwl_mld_get_primary_link(vif)); return; } diff --git a/drivers/net/wireless/intel/iwlwifi/mld/tests/link-selection.c b/drivers/net/wireless/intel/iwlwifi/mld/tests/link-selection.c index 766c24db3613..94a037bec1fa 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/tests/link-selection.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/tests/link-selection.c @@ -287,6 +287,7 @@ static void test_iwl_mld_link_pair_allows_emlsr(struct kunit *test) const struct link_pair_case *params = test->param_value; struct iwl_mld *mld = test->priv; struct ieee80211_vif *vif; + struct ieee80211_bss_conf *link; /* link A is the primary and link B is the secondary */ struct iwl_mld_link_sel_data a = { .chandef = params->chandef_a, @@ -310,6 +311,11 @@ static void test_iwl_mld_link_pair_allows_emlsr(struct kunit *test) wiphy_lock(mld->wiphy); + link = wiphy_dereference(mld->wiphy, vif->link_conf[a.link_id]); + KUNIT_ALLOC_AND_ASSERT(test, link->bss); + link = wiphy_dereference(mld->wiphy, vif->link_conf[b.link_id]); + KUNIT_ALLOC_AND_ASSERT(test, link->bss); + /* Simulate channel load */ if (params->primary_link_active) { struct iwl_mld_phy *phy = -- 2.51.0 From f9151f16e140b9c43f076579146679408af6f442 Mon Sep 17 00:00:00 2001 From: Miri Korenblit Date: Wed, 30 Apr 2025 15:23:16 +0300 Subject: [PATCH 04/16] wifi: iwlwifi: mld: check for NULL before referencing a pointer Errors can happen, and it is better not to risk with a NULL pointer dereference. Make sure that the links-to-remove pointers are not NULL before dereferencing it. Signed-off-by: Miri Korenblit Reviewed-by: Johannes Berg Link: https://patch.msgid.link/20250430151952.408652d45cda.I1bb72836dab17895a2e39910e4493d667db0fa80@changeid --- drivers/net/wireless/intel/iwlwifi/mld/mac80211.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c b/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c index 03ef9b33c2d2..284599abf8c6 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c @@ -2450,7 +2450,7 @@ iwl_mld_change_vif_links(struct ieee80211_hw *hw, added |= BIT(0); for (int i = 0; i < IEEE80211_MLD_MAX_NUM_LINKS; i++) { - if (removed & BIT(i)) + if (removed & BIT(i) && !WARN_ON(!old[i])) iwl_mld_remove_link(mld, old[i]); } -- 2.51.0 From f5f6b9d9a4959d19fefb4c48ef66de25571f22c3 Mon Sep 17 00:00:00 2001 From: Miri Korenblit Date: Wed, 30 Apr 2025 15:23:17 +0300 Subject: [PATCH 05/16] wifi: iwlwifi: mld: don't return an error if the FW is dead If iwl_mld_change_vif_links failed to add the requested link(s) because the FW is dead (error before recovery), there is no point in returning an error value, as the reconfig will re-add the link(s) after the FW is started. Return 0 in that case, and WARN in the others. Signed-off-by: Miri Korenblit Reviewed-by: Johannes Berg Link: https://patch.msgid.link/20250430151952.c549c72b1f37.I445bf723e9befc9541b4abd0ec7c72db8f1ff177@changeid --- drivers/net/wireless/intel/iwlwifi/mld/mac80211.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c b/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c index 284599abf8c6..6710dcacecd3 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c @@ -2457,8 +2457,10 @@ iwl_mld_change_vif_links(struct ieee80211_hw *hw, for (int i = 0; i < IEEE80211_MLD_MAX_NUM_LINKS; i++) { if (added & BIT(i)) { link_conf = link_conf_dereference_protected(vif, i); - if (WARN_ON(!link_conf)) - return -EINVAL; + if (!link_conf) { + err = -EINVAL; + goto remove_added_links; + } err = iwl_mld_add_link(mld, link_conf); if (err) @@ -2493,7 +2495,11 @@ remove_added_links: iwl_mld_remove_link(mld, link_conf); } - return err; + if (WARN_ON(!iwl_mld_error_before_recovery(mld))) + return err; + + /* reconfig will fix us anyway */ + return 0; } static int iwl_mld_change_sta_links(struct ieee80211_hw *hw, -- 2.51.0 From b8ca16d1d4498f9c1923905f9ba8b929b9294bea Mon Sep 17 00:00:00 2001 From: Pagadala Yesu Anjaneyulu Date: Wed, 30 Apr 2025 15:23:18 +0300 Subject: [PATCH 06/16] wifi: iwlwifi: rename ppag_ver to ppag_bios_rev ppag_ver variable is holding PPAG BIOS revision but name misleading as PPAG command API version, So rename to ppag_bios_rev. This will improve code readability but doesn't alter behaviour. Signed-off-by: Pagadala Yesu Anjaneyulu Signed-off-by: Miri Korenblit Link: https://patch.msgid.link/20250430151952.be51850947df.If81d88cd2d38299faf1e2a3db5efbc021650f907@changeid --- drivers/net/wireless/intel/iwlwifi/fw/acpi.c | 4 ++-- drivers/net/wireless/intel/iwlwifi/fw/regulatory.c | 8 ++++---- drivers/net/wireless/intel/iwlwifi/fw/regulatory.h | 6 +++--- drivers/net/wireless/intel/iwlwifi/fw/runtime.h | 3 ++- drivers/net/wireless/intel/iwlwifi/fw/uefi.c | 4 ++-- 5 files changed, 13 insertions(+), 12 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/fw/acpi.c b/drivers/net/wireless/intel/iwlwifi/fw/acpi.c index dfd5365995b6..68b461297174 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/acpi.c +++ b/drivers/net/wireless/intel/iwlwifi/fw/acpi.c @@ -882,7 +882,7 @@ int iwl_acpi_get_ppag_table(struct iwl_fw_runtime *fwrt) goto out_free; read_table: - fwrt->ppag_ver = tbl_rev; + fwrt->ppag_bios_rev = tbl_rev; flags = &wifi_pkg->package.elements[1]; if (flags->type != ACPI_TYPE_INTEGER) { @@ -891,7 +891,7 @@ read_table: } fwrt->ppag_flags = iwl_bios_get_ppag_flags(flags->integer.value, - fwrt->ppag_ver); + fwrt->ppag_bios_rev); /* * read, verify gain values and save them into the PPAG table. diff --git a/drivers/net/wireless/intel/iwlwifi/fw/regulatory.c b/drivers/net/wireless/intel/iwlwifi/fw/regulatory.c index 1444b628214b..bb8ddfad8621 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/regulatory.c +++ b/drivers/net/wireless/intel/iwlwifi/fw/regulatory.c @@ -349,17 +349,17 @@ int iwl_fill_ppag_table(struct iwl_fw_runtime *fwrt, num_sub_bands = IWL_NUM_SUB_BANDS_V1; gain = cmd->v1.gain[0]; *cmd_size = sizeof(cmd->v1); - if (fwrt->ppag_ver >= 1) { + if (fwrt->ppag_bios_rev >= 1) { /* in this case FW supports revision 0 */ IWL_DEBUG_RADIO(fwrt, "PPAG table rev is %d, send truncated table\n", - fwrt->ppag_ver); + fwrt->ppag_bios_rev); } } else if (cmd_ver >= 2 && cmd_ver <= 6) { num_sub_bands = IWL_NUM_SUB_BANDS_V2; gain = cmd->v2.gain[0]; *cmd_size = sizeof(cmd->v2); - if (fwrt->ppag_ver == 0) { + if (fwrt->ppag_bios_rev == 0) { /* in this case FW supports revisions 1,2 or 3 */ IWL_DEBUG_RADIO(fwrt, "PPAG table rev is 0, send padded table\n"); @@ -382,7 +382,7 @@ int iwl_fill_ppag_table(struct iwl_fw_runtime *fwrt, if ((cmd_ver == 1 && !fw_has_capa(&fwrt->fw->ucode_capa, IWL_UCODE_TLV_CAPA_PPAG_CHINA_BIOS_SUPPORT)) || - (cmd_ver == 2 && fwrt->ppag_ver >= 2)) { + (cmd_ver == 2 && fwrt->ppag_bios_rev >= 2)) { cmd->v1.flags &= cpu_to_le32(IWL_PPAG_ETSI_MASK); IWL_DEBUG_RADIO(fwrt, "masking ppag China bit\n"); } else { diff --git a/drivers/net/wireless/intel/iwlwifi/fw/regulatory.h b/drivers/net/wireless/intel/iwlwifi/fw/regulatory.h index 46d19101b741..641b4463e898 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/regulatory.h +++ b/drivers/net/wireless/intel/iwlwifi/fw/regulatory.h @@ -224,10 +224,10 @@ int iwl_bios_get_dsm(struct iwl_fw_runtime *fwrt, enum iwl_dsm_funcs func, u32 *value); static inline u32 iwl_bios_get_ppag_flags(const u32 ppag_modes, - const u8 ppag_ver) + const u8 ppag_bios_rev) { - return ppag_modes & (ppag_ver < 3 ? IWL_PPAG_ETSI_CHINA_MASK : - IWL_PPAG_REV3_MASK); + return ppag_modes & (ppag_bios_rev < 3 ? IWL_PPAG_ETSI_CHINA_MASK : + IWL_PPAG_REV3_MASK); } bool iwl_puncturing_is_allowed_in_bios(u32 puncturing, u16 mcc); diff --git a/drivers/net/wireless/intel/iwlwifi/fw/runtime.h b/drivers/net/wireless/intel/iwlwifi/fw/runtime.h index a56b96003650..8ff85a243075 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/runtime.h +++ b/drivers/net/wireless/intel/iwlwifi/fw/runtime.h @@ -111,6 +111,7 @@ struct iwl_txf_iter_data { * @sar_profiles: sar profiles as read from WRDS/EWRD BIOS tables * @geo_profiles: geographic profiles as read from WGDS BIOS table * @phy_filters: specific phy filters as read from WPFC BIOS table + * @ppag_bios_rev: PPAG BIOS revision */ struct iwl_fw_runtime { struct iwl_trans *trans; @@ -179,7 +180,7 @@ struct iwl_fw_runtime { bool geo_enabled; struct iwl_ppag_chain ppag_chains[IWL_NUM_CHAIN_LIMITS]; u32 ppag_flags; - u8 ppag_ver; + u8 ppag_bios_rev; struct iwl_sar_offset_mapping_cmd sgom_table; bool sgom_enabled; struct iwl_mcc_allowed_ap_type_cmd uats_table; diff --git a/drivers/net/wireless/intel/iwlwifi/fw/uefi.c b/drivers/net/wireless/intel/iwlwifi/fw/uefi.c index b0f4c217b2a5..74b7984e8ebe 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/uefi.c +++ b/drivers/net/wireless/intel/iwlwifi/fw/uefi.c @@ -557,9 +557,9 @@ int iwl_uefi_get_ppag_table(struct iwl_fw_runtime *fwrt) goto out; } - fwrt->ppag_ver = data->revision; + fwrt->ppag_bios_rev = data->revision; fwrt->ppag_flags = iwl_bios_get_ppag_flags(data->ppag_modes, - fwrt->ppag_ver); + fwrt->ppag_bios_rev); BUILD_BUG_ON(sizeof(fwrt->ppag_chains) != sizeof(data->ppag_chains)); memcpy(&fwrt->ppag_chains, &data->ppag_chains, -- 2.51.0 From d807f9e8f4a34e878bbc476261ea466435c26cb5 Mon Sep 17 00:00:00 2001 From: Pagadala Yesu Anjaneyulu Date: Wed, 30 Apr 2025 15:23:19 +0300 Subject: [PATCH 07/16] wifi: iwlwifi: fw: support reading PPAG BIOS table revision 4 PPAG revision 4 data has more supported bits than supported in version 6 of PPAG command. So, adjust by masking accordingly to firmware acceptance. Signed-off-by: Pagadala Yesu Anjaneyulu Signed-off-by: Miri Korenblit Link: https://patch.msgid.link/20250430151952.f1cb31f9f6ab.I56d6887866f76fd02b2cd298e3de1189c6d3890e@changeid --- drivers/net/wireless/intel/iwlwifi/fw/acpi.c | 4 ++-- drivers/net/wireless/intel/iwlwifi/fw/api/power.h | 9 +++++++++ drivers/net/wireless/intel/iwlwifi/fw/regulatory.c | 4 +++- drivers/net/wireless/intel/iwlwifi/fw/regulatory.h | 4 ++++ drivers/net/wireless/intel/iwlwifi/fw/uefi.h | 2 +- 5 files changed, 19 insertions(+), 4 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/fw/acpi.c b/drivers/net/wireless/intel/iwlwifi/fw/acpi.c index 68b461297174..a1c949d15d94 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/acpi.c +++ b/drivers/net/wireless/intel/iwlwifi/fw/acpi.c @@ -847,12 +847,12 @@ int iwl_acpi_get_ppag_table(struct iwl_fw_runtime *fwrt) if (IS_ERR(data)) return PTR_ERR(data); - /* try to read ppag table rev 3, 2 or 1 (all have the same data size) */ + /* try to read ppag table rev 1 to 4 (all have the same data size) */ wifi_pkg = iwl_acpi_get_wifi_pkg(fwrt->dev, data, ACPI_PPAG_WIFI_DATA_SIZE_V2, &tbl_rev); if (!IS_ERR(wifi_pkg)) { - if (tbl_rev >= 1 && tbl_rev <= 3) { + if (tbl_rev >= 1 && tbl_rev <= 4) { num_sub_bands = IWL_NUM_SUB_BANDS_V2; IWL_DEBUG_RADIO(fwrt, "Reading PPAG table (tbl_rev=%d)\n", diff --git a/drivers/net/wireless/intel/iwlwifi/fw/api/power.h b/drivers/net/wireless/intel/iwlwifi/fw/api/power.h index 1238c23ac48b..1c09c30055b1 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/api/power.h +++ b/drivers/net/wireless/intel/iwlwifi/fw/api/power.h @@ -658,6 +658,15 @@ union iwl_ppag_table_cmd { IWL_PPAG_ETSI_LPI_UHB_MASK | \ IWL_PPAG_USA_LPI_UHB_MASK) +#define IWL_PPAG_CMD_V6_MASK (IWL_PPAG_CMD_V5_MASK | \ + IWL_PPAG_ETSI_VLP_UHB_MASK | \ + IWL_PPAG_ETSI_SP_UHB_MASK | \ + IWL_PPAG_USA_VLP_UHB_MASK | \ + IWL_PPAG_USA_SP_UHB_MASK | \ + IWL_PPAG_CANADA_LPI_UHB_MASK | \ + IWL_PPAG_CANADA_VLP_UHB_MASK | \ + IWL_PPAG_CANADA_SP_UHB_MASK) + #define MCC_TO_SAR_OFFSET_TABLE_ROW_SIZE 26 #define MCC_TO_SAR_OFFSET_TABLE_COL_SIZE 13 diff --git a/drivers/net/wireless/intel/iwlwifi/fw/regulatory.c b/drivers/net/wireless/intel/iwlwifi/fw/regulatory.c index bb8ddfad8621..4508de2508d7 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/regulatory.c +++ b/drivers/net/wireless/intel/iwlwifi/fw/regulatory.c @@ -374,7 +374,9 @@ int iwl_fill_ppag_table(struct iwl_fw_runtime *fwrt, "PPAG MODE bits were read from bios: %d\n", le32_to_cpu(cmd->v1.flags)); - if (cmd_ver == 5) + if (cmd_ver == 6) + cmd->v1.flags &= cpu_to_le32(IWL_PPAG_CMD_V6_MASK); + else if (cmd_ver == 5) cmd->v1.flags &= cpu_to_le32(IWL_PPAG_CMD_V5_MASK); else if (cmd_ver < 5) cmd->v1.flags &= cpu_to_le32(IWL_PPAG_CMD_V4_MASK); diff --git a/drivers/net/wireless/intel/iwlwifi/fw/regulatory.h b/drivers/net/wireless/intel/iwlwifi/fw/regulatory.h index 641b4463e898..bd5c0a27718c 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/regulatory.h +++ b/drivers/net/wireless/intel/iwlwifi/fw/regulatory.h @@ -226,6 +226,10 @@ int iwl_bios_get_dsm(struct iwl_fw_runtime *fwrt, enum iwl_dsm_funcs func, static inline u32 iwl_bios_get_ppag_flags(const u32 ppag_modes, const u8 ppag_bios_rev) { + /* For revision 4 and above driver is pipe */ + if (ppag_bios_rev >= 4) + return ppag_modes; + return ppag_modes & (ppag_bios_rev < 3 ? IWL_PPAG_ETSI_CHINA_MASK : IWL_PPAG_REV3_MASK); } diff --git a/drivers/net/wireless/intel/iwlwifi/fw/uefi.h b/drivers/net/wireless/intel/iwlwifi/fw/uefi.h index 37d2ced03ce1..ace36cb081e6 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/uefi.h +++ b/drivers/net/wireless/intel/iwlwifi/fw/uefi.h @@ -34,7 +34,7 @@ #define IWL_UEFI_EWRD_REVISION 2 #define IWL_UEFI_WGDS_REVISION 3 #define IWL_UEFI_MIN_PPAG_REV 1 -#define IWL_UEFI_MAX_PPAG_REV 3 +#define IWL_UEFI_MAX_PPAG_REV 4 #define IWL_UEFI_MIN_WTAS_REVISION 1 #define IWL_UEFI_MAX_WTAS_REVISION 2 #define IWL_UEFI_SPLC_REVISION 0 -- 2.51.0 From ad883b79b2b60006c92213d45546211267408c15 Mon Sep 17 00:00:00 2001 From: Pagadala Yesu Anjaneyulu Date: Wed, 30 Apr 2025 15:23:20 +0300 Subject: [PATCH 08/16] wifi: iwlwifi: fw: support PPAG command version 7 PPAG command version 7 will send exact data read from BIOS to firmware without filtering/altering BIOS data. This enables the driver to become purely a pipe for this feature. Signed-off-by: Pagadala Yesu Anjaneyulu Signed-off-by: Miri Korenblit Link: https://patch.msgid.link/20250430151952.88ed7b2cadef.Iad78f6cec617d1f111b704352795dde81af71a99@changeid --- drivers/net/wireless/intel/iwlwifi/fw/acpi.c | 1 + .../net/wireless/intel/iwlwifi/fw/api/power.h | 23 ++++++++++++++----- .../wireless/intel/iwlwifi/fw/regulatory.c | 22 ++++++++++++------ .../net/wireless/intel/iwlwifi/fw/runtime.h | 2 ++ drivers/net/wireless/intel/iwlwifi/fw/uefi.c | 1 + 5 files changed, 36 insertions(+), 13 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/fw/acpi.c b/drivers/net/wireless/intel/iwlwifi/fw/acpi.c index a1c949d15d94..bee7d92293b8 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/acpi.c +++ b/drivers/net/wireless/intel/iwlwifi/fw/acpi.c @@ -912,6 +912,7 @@ read_table: } } + fwrt->ppag_bios_source = BIOS_SOURCE_ACPI; ret = 0; out_free: diff --git a/drivers/net/wireless/intel/iwlwifi/fw/api/power.h b/drivers/net/wireless/intel/iwlwifi/fw/api/power.h index 1c09c30055b1..23140205ccb9 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/api/power.h +++ b/drivers/net/wireless/intel/iwlwifi/fw/api/power.h @@ -7,6 +7,8 @@ #ifndef __iwl_fw_api_power_h__ #define __iwl_fw_api_power_h__ +#include "nvm-reg.h" + /* Power Management Commands, Responses, Notifications */ /** @@ -629,28 +631,37 @@ enum iwl_ppag_flags { /** * union iwl_ppag_table_cmd - union for all versions of PPAG command - * @v1: version 1 - * @v2: version 2 - * version 3, 4, 5 and 6 are the same structure as v2, + * @v1: command version 1 structure. + * @v2: command version from 2 to 6 are same structure as v2. * but has a different format of the flags bitmap + * @v3: command version 7 structure. * @v1.flags: values from &enum iwl_ppag_flags * @v1.gain: table of antenna gain values per chain and sub-band * @v1.reserved: reserved * @v2.flags: values from &enum iwl_ppag_flags * @v2.gain: table of antenna gain values per chain and sub-band - * @v2.reserved: reserved + * @v3.ppag_config_info: see @struct bios_value_u32 + * @v3.gain: table of antenna gain values per chain and sub-band + * @v3.reserved: reserved */ union iwl_ppag_table_cmd { struct { __le32 flags; s8 gain[IWL_NUM_CHAIN_LIMITS][IWL_NUM_SUB_BANDS_V1]; s8 reserved[2]; - } v1; + } __packed v1; /* PER_PLAT_ANTENNA_GAIN_CMD_API_S_VER_1 */ struct { __le32 flags; s8 gain[IWL_NUM_CHAIN_LIMITS][IWL_NUM_SUB_BANDS_V2]; s8 reserved[2]; - } v2; + } __packed v2; /* PER_PLAT_ANTENNA_GAIN_CMD_API_S_VER_2, VER3, VER4, + * VER5, VER6 + */ + struct { + struct bios_value_u32 ppag_config_info; + s8 gain[IWL_NUM_CHAIN_LIMITS][IWL_NUM_SUB_BANDS_V2]; + s8 reserved[2]; + } __packed v3; /* PER_PLAT_ANTENNA_GAIN_CMD_API_S_VER_7 */ } __packed; #define IWL_PPAG_CMD_V4_MASK (IWL_PPAG_ETSI_MASK | IWL_PPAG_CHINA_MASK) diff --git a/drivers/net/wireless/intel/iwlwifi/fw/regulatory.c b/drivers/net/wireless/intel/iwlwifi/fw/regulatory.c index 4508de2508d7..4038de2df75e 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/regulatory.c +++ b/drivers/net/wireless/intel/iwlwifi/fw/regulatory.c @@ -339,16 +339,12 @@ int iwl_fill_ppag_table(struct iwl_fw_runtime *fwrt, return -EINVAL; } - /* The 'flags' field is the same in v1 and in v2 so we can just - * use v1 to access it. - */ - cmd->v1.flags = cpu_to_le32(fwrt->ppag_flags); - IWL_DEBUG_RADIO(fwrt, "PPAG cmd ver is %d\n", cmd_ver); if (cmd_ver == 1) { num_sub_bands = IWL_NUM_SUB_BANDS_V1; gain = cmd->v1.gain[0]; *cmd_size = sizeof(cmd->v1); + cmd->v1.flags = cpu_to_le32(fwrt->ppag_flags); if (fwrt->ppag_bios_rev >= 1) { /* in this case FW supports revision 0 */ IWL_DEBUG_RADIO(fwrt, @@ -359,11 +355,19 @@ int iwl_fill_ppag_table(struct iwl_fw_runtime *fwrt, num_sub_bands = IWL_NUM_SUB_BANDS_V2; gain = cmd->v2.gain[0]; *cmd_size = sizeof(cmd->v2); + cmd->v2.flags = cpu_to_le32(fwrt->ppag_flags); if (fwrt->ppag_bios_rev == 0) { /* in this case FW supports revisions 1,2 or 3 */ IWL_DEBUG_RADIO(fwrt, "PPAG table rev is 0, send padded table\n"); } + } else if (cmd_ver == 7) { + num_sub_bands = IWL_NUM_SUB_BANDS_V2; + gain = cmd->v3.gain[0]; + *cmd_size = sizeof(cmd->v3); + cmd->v3.ppag_config_info.table_source = fwrt->ppag_bios_source; + cmd->v3.ppag_config_info.table_revision = fwrt->ppag_bios_rev; + cmd->v3.ppag_config_info.value = cpu_to_le32(fwrt->ppag_flags); } else { IWL_DEBUG_RADIO(fwrt, "Unsupported PPAG command version\n"); return -EINVAL; @@ -372,7 +376,7 @@ int iwl_fill_ppag_table(struct iwl_fw_runtime *fwrt, /* ppag mode */ IWL_DEBUG_RADIO(fwrt, "PPAG MODE bits were read from bios: %d\n", - le32_to_cpu(cmd->v1.flags)); + fwrt->ppag_flags); if (cmd_ver == 6) cmd->v1.flags &= cpu_to_le32(IWL_PPAG_CMD_V6_MASK); @@ -391,9 +395,13 @@ int iwl_fill_ppag_table(struct iwl_fw_runtime *fwrt, IWL_DEBUG_RADIO(fwrt, "isn't masking ppag China bit\n"); } + /* The 'flags' field is the same in v1 and v2 so we can just + * use v1 to access it. + */ IWL_DEBUG_RADIO(fwrt, "PPAG MODE bits going to be sent: %d\n", - le32_to_cpu(cmd->v1.flags)); + (cmd_ver < 7) ? le32_to_cpu(cmd->v1.flags) : + le32_to_cpu(cmd->v3.ppag_config_info.value)); for (i = 0; i < IWL_NUM_CHAIN_LIMITS; i++) { for (j = 0; j < num_sub_bands; j++) { diff --git a/drivers/net/wireless/intel/iwlwifi/fw/runtime.h b/drivers/net/wireless/intel/iwlwifi/fw/runtime.h index 8ff85a243075..0444a736c2b2 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/runtime.h +++ b/drivers/net/wireless/intel/iwlwifi/fw/runtime.h @@ -112,6 +112,7 @@ struct iwl_txf_iter_data { * @geo_profiles: geographic profiles as read from WGDS BIOS table * @phy_filters: specific phy filters as read from WPFC BIOS table * @ppag_bios_rev: PPAG BIOS revision + * @ppag_bios_source: see &enum bios_source */ struct iwl_fw_runtime { struct iwl_trans *trans; @@ -181,6 +182,7 @@ struct iwl_fw_runtime { struct iwl_ppag_chain ppag_chains[IWL_NUM_CHAIN_LIMITS]; u32 ppag_flags; u8 ppag_bios_rev; + u8 ppag_bios_source; struct iwl_sar_offset_mapping_cmd sgom_table; bool sgom_enabled; struct iwl_mcc_allowed_ap_type_cmd uats_table; diff --git a/drivers/net/wireless/intel/iwlwifi/fw/uefi.c b/drivers/net/wireless/intel/iwlwifi/fw/uefi.c index 74b7984e8ebe..d31492035089 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/uefi.c +++ b/drivers/net/wireless/intel/iwlwifi/fw/uefi.c @@ -564,6 +564,7 @@ int iwl_uefi_get_ppag_table(struct iwl_fw_runtime *fwrt) BUILD_BUG_ON(sizeof(fwrt->ppag_chains) != sizeof(data->ppag_chains)); memcpy(&fwrt->ppag_chains, &data->ppag_chains, sizeof(data->ppag_chains)); + fwrt->ppag_bios_source = BIOS_SOURCE_UEFI; out: kfree(data); return ret; -- 2.51.0 From f7cc80b871eeffa17a4b280e2820d081c651a308 Mon Sep 17 00:00:00 2001 From: Somashekhar Puttagangaiah Date: Wed, 30 Apr 2025 15:57:15 +0300 Subject: [PATCH 09/16] wifi: iwlwifi: mld: add kunit test for emlsr with bt on add kunit test to validate entering and exiting emlsr scenarios when the bt is on with certain penalty on wifi. Signed-off-by: Somashekhar Puttagangaiah Signed-off-by: Miri Korenblit Link: https://patch.msgid.link/20250430155443.6621494fa412.If89b4b2dab308d825ca02284dd8e0030675d7af5@changeid --- drivers/net/wireless/intel/iwlwifi/mld/mlo.c | 4 +- drivers/net/wireless/intel/iwlwifi/mld/mlo.h | 4 + .../wireless/intel/iwlwifi/mld/tests/Makefile | 2 +- .../intel/iwlwifi/mld/tests/emlsr_with_bt.c | 140 ++++++++++++++++++ 4 files changed, 148 insertions(+), 2 deletions(-) create mode 100644 drivers/net/wireless/intel/iwlwifi/mld/tests/emlsr_with_bt.c diff --git a/drivers/net/wireless/intel/iwlwifi/mld/mlo.c b/drivers/net/wireless/intel/iwlwifi/mld/mlo.c index c84b39794c8f..824a328da28e 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/mlo.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/mlo.c @@ -640,7 +640,8 @@ s8 iwl_mld_get_emlsr_rssi_thresh(struct iwl_mld *mld, #define IWL_MLD_BT_COEX_ENABLE_EMLSR_RSSI_THRESH -63 #define IWL_MLD_BT_COEX_WIFI_LOSS_THRESH 7 -static bool +VISIBLE_IF_IWLWIFI_KUNIT +bool iwl_mld_bt_allows_emlsr(struct iwl_mld *mld, struct ieee80211_bss_conf *link, bool check_entry) { @@ -669,6 +670,7 @@ iwl_mld_bt_allows_emlsr(struct iwl_mld *mld, struct ieee80211_bss_conf *link, link->link_id, bt_penalty); return bt_penalty < IWL_MLD_BT_COEX_WIFI_LOSS_THRESH; } +EXPORT_SYMBOL_IF_IWLWIFI_KUNIT(iwl_mld_bt_allows_emlsr); static u32 iwl_mld_emlsr_disallowed_with_link(struct iwl_mld *mld, diff --git a/drivers/net/wireless/intel/iwlwifi/mld/mlo.h b/drivers/net/wireless/intel/iwlwifi/mld/mlo.h index c2bf04b799fb..2122a7c3e945 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/mlo.h +++ b/drivers/net/wireless/intel/iwlwifi/mld/mlo.h @@ -161,6 +161,10 @@ struct iwl_mld_link_sel_data { u32 iwl_mld_emlsr_pair_state(struct ieee80211_vif *vif, struct iwl_mld_link_sel_data *a, struct iwl_mld_link_sel_data *b); + +bool iwl_mld_bt_allows_emlsr(struct iwl_mld *mld, + struct ieee80211_bss_conf *link, + bool entry_criteria); #endif #endif /* __iwl_mld_mlo_h__ */ diff --git a/drivers/net/wireless/intel/iwlwifi/mld/tests/Makefile b/drivers/net/wireless/intel/iwlwifi/mld/tests/Makefile index 36317feb923b..3e2ae6020613 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/tests/Makefile +++ b/drivers/net/wireless/intel/iwlwifi/mld/tests/Makefile @@ -1,5 +1,5 @@ # 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 += module.o hcmd.o utils.o link.o rx.o agg.o link-selection.o emlsr_with_bt.o ccflags-y += -I$(src)/../ obj-$(CONFIG_IWLWIFI_KUNIT_TESTS) += iwlmld-tests.o diff --git a/drivers/net/wireless/intel/iwlwifi/mld/tests/emlsr_with_bt.c b/drivers/net/wireless/intel/iwlwifi/mld/tests/emlsr_with_bt.c new file mode 100644 index 000000000000..91556ee5c142 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/mld/tests/emlsr_with_bt.c @@ -0,0 +1,140 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * KUnit tests for link selection functions + * + * Copyright (C) 2025 Intel Corporation + */ +#include + +#include "utils.h" +#include "mld.h" +#include "mlo.h" + +static const struct emlsr_with_bt_test_case { + const char *desc; + struct { + struct iwl_bt_coex_profile_notif notif; + s32 signal; + bool check_entry; + } input; + bool emlsr_allowed; +} emlsr_with_bt_cases[] = { + { + .desc = "BT penalty(exit) with low rssi 4.5: emlsr allowed", + .input = { + .notif.wifi_loss_low_rssi[1] = {4, 5}, + .notif.wifi_loss_mid_high_rssi[1] = {7, 9}, + .signal = -69, + .check_entry = false, + }, + .emlsr_allowed = true, + }, + { + .desc = "BT penalty(exit) from high rssi 5: emlsr allowed", + .input = { + .notif.wifi_loss_low_rssi[1] = {7, 9}, + .notif.wifi_loss_mid_high_rssi[1] = {5, 5}, + .signal = -68, + .check_entry = false, + }, + .emlsr_allowed = true, + }, + { + .desc = "BT penalty(exit) with low rssi 8: emlsr not allowed", + .input = { + .notif.wifi_loss_low_rssi[1] = {7, 9}, + .notif.wifi_loss_mid_high_rssi[1] = {4, 5}, + .signal = -69, + .check_entry = false, + }, + .emlsr_allowed = false, + }, + { + .desc = "BT penalty(exit) from high rssi 9: emlsr not allowed", + .input = { + .notif.wifi_loss_low_rssi[1] = {4, 5}, + .notif.wifi_loss_mid_high_rssi[1] = {9, 9}, + .signal = -68, + .check_entry = false, + }, + .emlsr_allowed = false, + }, + { + .desc = "BT penalty(entry) with low rssi 4.5: emlsr allowed", + .input = { + .notif.wifi_loss_low_rssi[1] = {4, 5}, + .notif.wifi_loss_mid_high_rssi[1] = {7, 9}, + .signal = -63, + .check_entry = true, + }, + .emlsr_allowed = true, + }, + { + .desc = "BT penalty(entry) from high rssi 5: emlsr allowed", + .input = { + .notif.wifi_loss_low_rssi[1] = {7, 9}, + .notif.wifi_loss_mid_high_rssi[1] = {5, 5}, + .signal = -62, + .check_entry = false, + }, + .emlsr_allowed = true, + }, + { + .desc = "BT penalty(entry) with low rssi 8: emlsr not allowed", + .input = { + .notif.wifi_loss_low_rssi[1] = {7, 9}, + .notif.wifi_loss_mid_high_rssi[1] = {4, 5}, + .signal = -63, + .check_entry = false, + }, + .emlsr_allowed = true, + }, + { + .desc = "BT penalty(entry) from high rssi 9: emlsr not allowed", + .input = { + .notif.wifi_loss_low_rssi[1] = {4, 5}, + .notif.wifi_loss_mid_high_rssi[1] = {9, 9}, + .signal = -62, + .check_entry = true, + }, + .emlsr_allowed = false, + }, +}; + +KUNIT_ARRAY_PARAM_DESC(emlsr_with_bt, emlsr_with_bt_cases, desc); + +static void test_emlsr_with_bt(struct kunit *test) +{ + struct iwl_mld *mld = test->priv; + const struct emlsr_with_bt_test_case *test_param = + (const void *)(test->param_value); + struct ieee80211_vif *vif = + iwlmld_kunit_add_vif(true, NL80211_IFTYPE_STATION); + struct ieee80211_bss_conf *link = iwlmld_kunit_add_link(vif, 1); + bool actual_value = false; + + KUNIT_ALLOC_AND_ASSERT(test, link->bss); + + /* Extract test case parameters */ + link->bss->signal = DBM_TO_MBM(test_param->input.signal); + memcpy(&mld->last_bt_notif, &test_param->input.notif, + sizeof(struct iwl_bt_coex_profile_notif)); + + actual_value = iwl_mld_bt_allows_emlsr(mld, link, + test_param->input.check_entry); + /* Assert that the returned value matches the expected emlsr_allowed */ + KUNIT_EXPECT_EQ(test, actual_value, test_param->emlsr_allowed); +} + +static struct kunit_case emlsr_with_bt_test_cases[] = { + KUNIT_CASE_PARAM(test_emlsr_with_bt, emlsr_with_bt_gen_params), + {}, +}; + +static struct kunit_suite emlsr_with_bt = { + .name = "iwlmld-emlsr-with-bt-tests", + .test_cases = emlsr_with_bt_test_cases, + .init = iwlmld_kunit_test_init, +}; + +kunit_test_suite(emlsr_with_bt); -- 2.51.0 From 0bd6ede71aef200c3876ca4aefb3fc5a2e474cf6 Mon Sep 17 00:00:00 2001 From: Emmanuel Grumbach Date: Wed, 30 Apr 2025 15:57:16 +0300 Subject: [PATCH 10/16] wifi: iwlwifi: add support for ALIVE v8 The firmware added a field to describe the platform Id. Just print it. Signed-off-by: Emmanuel Grumbach Signed-off-by: Miri Korenblit Link: https://patch.msgid.link/20250430155443.dc387ae36e0f.Iafd364c6b23749597b658015be97295ad0c1730d@changeid --- .../net/wireless/intel/iwlwifi/fw/api/alive.h | 10 +++++++++ drivers/net/wireless/intel/iwlwifi/mld/fw.c | 21 +++++++++++++++++-- drivers/net/wireless/intel/iwlwifi/mvm/fw.c | 13 +++++++++++- 3 files changed, 41 insertions(+), 3 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/fw/api/alive.h b/drivers/net/wireless/intel/iwlwifi/fw/api/alive.h index 42360a8f23aa..3ce477c248ce 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/api/alive.h +++ b/drivers/net/wireless/intel/iwlwifi/fw/api/alive.h @@ -112,6 +112,16 @@ struct iwl_alive_ntf_v6 { struct iwl_imr_alive_info imr; } __packed; /* UCODE_ALIVE_NTFY_API_S_VER_6 */ +struct iwl_alive_ntf { + __le16 status; + __le16 flags; + struct iwl_lmac_alive lmac_data[2]; + struct iwl_umac_alive umac_data; + struct iwl_sku_id sku_id; + struct iwl_imr_alive_info imr; + __le64 platform_id; +} __packed; /* UCODE_ALIVE_NTFY_API_S_VER_8 */ + /** * enum iwl_extended_cfg_flags - commands driver may send before * finishing init flow diff --git a/drivers/net/wireless/intel/iwlwifi/mld/fw.c b/drivers/net/wireless/intel/iwlwifi/mld/fw.c index 238e8ad8ba82..268ff7eceb47 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/fw.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/fw.c @@ -103,12 +103,13 @@ static bool iwl_alive_fn(struct iwl_notif_wait_data *notif_wait, struct iwl_rx_packet *pkt, void *data) { unsigned int pkt_len = iwl_rx_packet_payload_len(pkt); + unsigned int expected_sz; struct iwl_mld *mld = container_of(notif_wait, struct iwl_mld, notif_wait); struct iwl_trans *trans = mld->trans; u32 version = iwl_fw_lookup_notif_ver(mld->fw, LEGACY_GROUP, UCODE_ALIVE_NTFY, 0); - struct iwl_alive_ntf_v6 *palive; + struct iwl_alive_ntf *palive; bool *alive_valid = data; struct iwl_umac_alive *umac; struct iwl_lmac_alive *lmac1; @@ -117,7 +118,19 @@ static bool iwl_alive_fn(struct iwl_notif_wait_data *notif_wait, u32 umac_error_table; u16 status; - if (version < 6 || version > 7 || pkt_len != sizeof(*palive)) + switch (version) { + case 6: + case 7: + expected_sz = sizeof(struct iwl_alive_ntf_v6); + break; + case 8: + expected_sz = sizeof(struct iwl_alive_ntf); + break; + default: + return false; + } + + if (pkt_len != expected_sz) return false; palive = (void *)pkt->data; @@ -171,6 +184,10 @@ static bool iwl_alive_fn(struct iwl_notif_wait_data *notif_wait, IWL_DEBUG_FW(mld, "FW alive flags 0x%x\n", le16_to_cpu(palive->flags)); + if (version >= 8) + IWL_DEBUG_FW(mld, "platform_id 0x%llx\n", + le64_to_cpu(palive->platform_id)); + iwl_fwrt_update_fw_versions(&mld->fwrt, lmac1, umac); return true; diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/fw.c b/drivers/net/wireless/intel/iwlwifi/mvm/fw.c index 5bdd35b433e8..d955f7c4ab8a 100644 --- a/drivers/net/wireless/intel/iwlwifi/mvm/fw.c +++ b/drivers/net/wireless/intel/iwlwifi/mvm/fw.c @@ -114,7 +114,7 @@ static bool iwl_alive_fn(struct iwl_notif_wait_data *notif_wait, u32 i; - if (version == 6) { + if (version >= 6) { struct iwl_alive_ntf_v6 *palive; if (pkt_len < sizeof(*palive)) @@ -157,6 +157,17 @@ static bool iwl_alive_fn(struct iwl_notif_wait_data *notif_wait, } } } + + if (version >= 8) { + const struct iwl_alive_ntf *palive_v8 = + (void *)pkt->data; + + if (pkt_len < sizeof(*palive_v8)) + return false; + + IWL_DEBUG_FW(mvm, "platform id: 0x%llx\n", + palive_v8->platform_id); + } } if (version >= 5) { -- 2.51.0 From bd795a65c2718d7161029bb2d4b79fa2577c4fd2 Mon Sep 17 00:00:00 2001 From: Miri Korenblit Date: Wed, 30 Apr 2025 15:57:17 +0300 Subject: [PATCH 11/16] wifi: iwlwifi: mld: support iwl_mac_power_cmd version 2 This version fixes the issue that was worked around by iwl_mld_smps_wa. So for FWs with the new version don't do the workaround, and set new bit added in this version when appropriate. While at it, rename iwl_mld_smps_wa to iwl_mld_smps_workaround. Signed-off-by: Miri Korenblit Reviewed-by: Emmanuel Grumbach Link: https://patch.msgid.link/20250430155443.77fdb18d1ee0.I93688612ac4f6ec99da9bc54bee16554ef40a40b@changeid --- drivers/net/wireless/intel/iwlwifi/mld/mac80211.c | 13 +++++++++---- drivers/net/wireless/intel/iwlwifi/mld/power.c | 3 +++ 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c b/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c index 6710dcacecd3..ef976e4d700f 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c @@ -1248,9 +1248,14 @@ iwl_mld_mac80211_link_info_changed(struct ieee80211_hw *hw, } static void -iwl_mld_smps_wa(struct iwl_mld *mld, struct ieee80211_vif *vif, bool enable) +iwl_mld_smps_workaround(struct iwl_mld *mld, struct ieee80211_vif *vif, bool enable) { struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif); + bool workaround_required = + iwl_fw_lookup_cmd_ver(mld->fw, MAC_PM_POWER_TABLE, 0) < 2; + + if (!workaround_required) + return; /* Send the device-level power commands since the * firmware checks the POWER_TABLE_CMD's POWER_SAVE_EN bit to @@ -1297,7 +1302,7 @@ void iwl_mld_mac80211_vif_cfg_changed(struct ieee80211_hw *hw, } if (changes & BSS_CHANGED_PS) { - iwl_mld_smps_wa(mld, vif, vif->cfg.ps); + iwl_mld_smps_workaround(mld, vif, vif->cfg.ps); iwl_mld_update_mac_power(mld, vif, false); } @@ -1710,7 +1715,7 @@ static int iwl_mld_move_sta_state_up(struct iwl_mld *mld, FW_CTXT_ACTION_MODIFY); if (ret) return ret; - iwl_mld_smps_wa(mld, vif, vif->cfg.ps); + iwl_mld_smps_workaround(mld, vif, vif->cfg.ps); } /* MFP is set by default before the station is authorized. @@ -1753,7 +1758,7 @@ static int iwl_mld_move_sta_state_down(struct iwl_mld *mld, &mld_vif->emlsr.check_tpt_wk); iwl_mld_reset_cca_40mhz_workaround(mld, vif); - iwl_mld_smps_wa(mld, vif, true); + iwl_mld_smps_workaround(mld, vif, true); } /* once we move into assoc state, need to update the FW to diff --git a/drivers/net/wireless/intel/iwlwifi/mld/power.c b/drivers/net/wireless/intel/iwlwifi/mld/power.c index 2f16c174b57e..8cc276041360 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/power.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/power.c @@ -253,6 +253,9 @@ static void iwl_mld_power_build_cmd(struct iwl_mld *mld, cmd->flags |= cpu_to_le16(POWER_FLAGS_POWER_MANAGEMENT_ENA_MSK); + if (iwl_fw_lookup_cmd_ver(mld->fw, MAC_PM_POWER_TABLE, 0) >= 2) + 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 || -- 2.51.0 From 792eb35718367c9b84b61e316700a8201f8a5036 Mon Sep 17 00:00:00 2001 From: Miri Korenblit Date: Wed, 30 Apr 2025 15:57:18 +0300 Subject: [PATCH 12/16] wifi: iwlwifi: mvm: support ROC command version 6 This was suppsed to be supported only in iwlmld, but turns out that it will be needed also in iwlmvm. Add support for it. Signed-off-by: Miri Korenblit Reviewed-by: Emmanuel Grumbach Link: https://patch.msgid.link/20250430155443.d118ee63aca4.I12ea349ca6587d8ea606f6ece4a9f3c0c2bb1494@changeid --- drivers/net/wireless/intel/iwlwifi/mvm/time-event.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/time-event.c b/drivers/net/wireless/intel/iwlwifi/mvm/time-event.c index 1a30bb1ff8ca..478408f802d9 100644 --- a/drivers/net/wireless/intel/iwlwifi/mvm/time-event.c +++ b/drivers/net/wireless/intel/iwlwifi/mvm/time-event.c @@ -771,15 +771,17 @@ static void iwl_mvm_cancel_session_protection(struct iwl_mvm *mvm, static void iwl_mvm_roc_rm_cmd(struct iwl_mvm *mvm, u32 activity) { - struct iwl_roc_req_v5 roc_cmd = { + struct iwl_roc_req roc_cmd = { .action = cpu_to_le32(FW_CTXT_ACTION_REMOVE), .activity = cpu_to_le32(activity), }; + u8 ver = iwl_fw_lookup_cmd_ver(mvm->fw, WIDE_ID(MAC_CONF_GROUP, ROC_CMD), 0); + u16 cmd_len = ver < 6 ? sizeof(struct iwl_roc_req_v5) : sizeof(roc_cmd); int ret; lockdep_assert_held(&mvm->mutex); ret = iwl_mvm_send_cmd_pdu(mvm, WIDE_ID(MAC_CONF_GROUP, ROC_CMD), 0, - sizeof(roc_cmd), &roc_cmd); + cmd_len, &roc_cmd); if (ret) IWL_ERR(mvm, "Couldn't send the ROC_CMD: %d\n", ret); } @@ -1102,11 +1104,13 @@ int iwl_mvm_roc_add_cmd(struct iwl_mvm *mvm, { int res; u32 duration_tu, delay; - struct iwl_roc_req_v5 roc_req = { + struct iwl_roc_req roc_req = { .action = cpu_to_le32(FW_CTXT_ACTION_ADD), .activity = cpu_to_le32(activity), .sta_id = cpu_to_le32(mvm->aux_sta.sta_id), }; + u8 ver = iwl_fw_lookup_cmd_ver(mvm->fw, WIDE_ID(MAC_CONF_GROUP, ROC_CMD), 0); + u16 cmd_len = ver < 6 ? sizeof(struct iwl_roc_req_v5) : sizeof(roc_req); struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); lockdep_assert_held(&mvm->mutex); @@ -1136,7 +1140,7 @@ int iwl_mvm_roc_add_cmd(struct iwl_mvm *mvm, memcpy(roc_req.node_addr, vif->addr, ETH_ALEN); res = iwl_mvm_send_cmd_pdu(mvm, WIDE_ID(MAC_CONF_GROUP, ROC_CMD), - 0, sizeof(roc_req), &roc_req); + 0, cmd_len, &roc_req); if (!res) mvmvif->roc_activity = activity; -- 2.51.0 From abbcea13bc5d01fb77d93c718d79e81145f8bb39 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Wed, 30 Apr 2025 15:57:19 +0300 Subject: [PATCH 13/16] wifi: iwlwifi: mld: handle SW reset w/o NIC error For the upcoming TOP reset, the sw_reset() method may be called without nic_error() before it. In this case, also abort any still pending scans and set in_hw_restart. Signed-off-by: Johannes Berg Signed-off-by: Miri Korenblit Link: https://patch.msgid.link/20250430155443.b732ff4e9f31.I3948c2d892f1ee82b3a6bf9f123be02a1eaa82e5@changeid --- drivers/net/wireless/intel/iwlwifi/mld/mld.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/net/wireless/intel/iwlwifi/mld/mld.c b/drivers/net/wireless/intel/iwlwifi/mld/mld.c index 87624730fb50..5d91de598256 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/mld.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/mld.c @@ -674,6 +674,13 @@ static bool iwl_mld_sw_reset(struct iwl_op_mode *op_mode, { struct iwl_mld *mld = IWL_OP_MODE_GET_MLD(op_mode); + /* SW reset can happen for TOP error w/o NIC error, so + * also abort scan here and set in_hw_restart, when we + * had a NIC error both were already done. + */ + iwl_mld_report_scan_aborted(mld); + mld->fw_status.in_hw_restart = true; + /* Do restart only in the following conditions are met: * - we consider the FW as running * - The trigger that brought us here is defined as one that requires -- 2.51.0 From 909e1be6546258efb45993851d51d86862e2e5ee Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Wed, 30 Apr 2025 15:57:20 +0300 Subject: [PATCH 14/16] wifi: iwlwifi: implement TOP reset Implement TOP reset (new in the SC family), which resets much of the (shared) hardware without resetting the bus interfaces. Use it to recover from TOP fatal error, or if manually used; we'll need to add using it for FSEQ updates later. Signed-off-by: Johannes Berg Signed-off-by: Miri Korenblit Link: https://patch.msgid.link/20250430155443.12f38024a3b4.I9c22f6c4f6de64f3b34ccd898370ec1859ab7dbf@changeid --- .../intel/iwlwifi/iwl-context-info-gen3.h | 7 +- .../net/wireless/intel/iwlwifi/iwl-op-mode.h | 6 ++ .../net/wireless/intel/iwlwifi/iwl-trans.c | 47 ++++++++++++-- .../net/wireless/intel/iwlwifi/iwl-trans.h | 9 +++ .../intel/iwlwifi/pcie/ctxt-info-gen3.c | 38 +++++++---- .../wireless/intel/iwlwifi/pcie/ctxt-info.c | 2 +- .../wireless/intel/iwlwifi/pcie/internal.h | 22 +++++-- drivers/net/wireless/intel/iwlwifi/pcie/rx.c | 12 ++-- .../wireless/intel/iwlwifi/pcie/trans-gen2.c | 65 ++++++++++++++++--- .../net/wireless/intel/iwlwifi/pcie/trans.c | 19 ++++-- 10 files changed, 182 insertions(+), 45 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-context-info-gen3.h b/drivers/net/wireless/intel/iwlwifi/iwl-context-info-gen3.h index 6111ca970ed2..b028343672cc 100644 --- a/drivers/net/wireless/intel/iwlwifi/iwl-context-info-gen3.h +++ b/drivers/net/wireless/intel/iwlwifi/iwl-context-info-gen3.h @@ -58,6 +58,7 @@ enum iwl_prph_scratch_mtr_format { * @IWL_PRPH_SCRATCH_RB_SIZE_EXT_16K: 16kB RB size * @IWL_PRPH_SCRATCH_SCU_FORCE_ACTIVE: Indicate fw to set SCU_FORCE_ACTIVE * upon reset. + * @IWL_PRPH_SCRATCH_TOP_RESET: request TOP reset */ enum iwl_prph_scratch_flags { IWL_PRPH_SCRATCH_IMR_DEBUG_EN = BIT(1), @@ -74,6 +75,7 @@ enum iwl_prph_scratch_flags { IWL_PRPH_SCRATCH_RB_SIZE_EXT_12K = 9 << 20, IWL_PRPH_SCRATCH_RB_SIZE_EXT_16K = 10 << 20, IWL_PRPH_SCRATCH_SCU_FORCE_ACTIVE = BIT(29), + IWL_PRPH_SCRATCH_TOP_RESET = BIT(30), }; /** @@ -321,8 +323,9 @@ struct iwl_context_info_gen3 { __le32 reserved; } __packed; /* IPC_CONTEXT_INFO_S */ -int iwl_pcie_ctxt_info_gen3_init(struct iwl_trans *trans, - const struct fw_img *fw); +int iwl_pcie_ctxt_info_gen3_alloc(struct iwl_trans *trans, + const struct fw_img *fw); +void iwl_pcie_ctxt_info_gen3_kick(struct iwl_trans *trans); void iwl_pcie_ctxt_info_gen3_free(struct iwl_trans *trans, bool alive); int iwl_trans_pcie_ctx_info_gen3_load_pnvm(struct iwl_trans *trans, diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-op-mode.h b/drivers/net/wireless/intel/iwlwifi/iwl-op-mode.h index 6bccb30c0981..b5d39026fa2f 100644 --- a/drivers/net/wireless/intel/iwlwifi/iwl-op-mode.h +++ b/drivers/net/wireless/intel/iwlwifi/iwl-op-mode.h @@ -53,6 +53,9 @@ struct iwl_cfg; * the device will be shut down * @IWL_ERR_TYPE_CMD_QUEUE_FULL: command queue was full * @IWL_ERR_TYPE_TOP_RESET_BY_BT: TOP reset initiated by BT + * @IWL_ERR_TYPE_TOP_FATAL_ERROR: TOP fatal error + * @IWL_ERR_TYPE_TOP_RESET_FAILED: TOP reset failed + * @IWL_ERR_TYPE_DEBUGFS: error/reset indication from debugfs */ enum iwl_fw_error_type { IWL_ERR_TYPE_IRQ, @@ -60,6 +63,9 @@ enum iwl_fw_error_type { IWL_ERR_TYPE_RESET_HS_TIMEOUT, IWL_ERR_TYPE_CMD_QUEUE_FULL, IWL_ERR_TYPE_TOP_RESET_BY_BT, + IWL_ERR_TYPE_TOP_FATAL_ERROR, + IWL_ERR_TYPE_TOP_RESET_FAILED, + IWL_ERR_TYPE_DEBUGFS, }; /** diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-trans.c b/drivers/net/wireless/intel/iwlwifi/iwl-trans.c index 71baf09d2f04..edb56a6772f7 100644 --- a/drivers/net/wireless/intel/iwlwifi/iwl-trans.c +++ b/drivers/net/wireless/intel/iwlwifi/iwl-trans.c @@ -130,18 +130,46 @@ iwl_trans_determine_restart_mode(struct iwl_trans *trans) struct iwl_trans_dev_restart_data *data; enum iwl_reset_mode at_least = 0; unsigned int index; - static const enum iwl_reset_mode escalation_list[] = { + static const enum iwl_reset_mode escalation_list_old[] = { IWL_RESET_MODE_SW_RESET, IWL_RESET_MODE_REPROBE, IWL_RESET_MODE_REPROBE, IWL_RESET_MODE_FUNC_RESET, - /* FIXME: add TOP reset */ IWL_RESET_MODE_PROD_RESET, - /* FIXME: add TOP reset */ + }; + static const enum iwl_reset_mode escalation_list_sc[] = { + IWL_RESET_MODE_SW_RESET, + IWL_RESET_MODE_REPROBE, + IWL_RESET_MODE_REPROBE, + IWL_RESET_MODE_FUNC_RESET, + IWL_RESET_MODE_TOP_RESET, IWL_RESET_MODE_PROD_RESET, - /* FIXME: add TOP reset */ + IWL_RESET_MODE_TOP_RESET, + IWL_RESET_MODE_PROD_RESET, + IWL_RESET_MODE_TOP_RESET, IWL_RESET_MODE_PROD_RESET, }; + const enum iwl_reset_mode *escalation_list; + size_t escalation_list_size; + + /* used by TOP fatal error/TOP reset */ + if (trans->restart.mode.type == IWL_ERR_TYPE_TOP_RESET_FAILED) + return IWL_RESET_MODE_PROD_RESET; + + if (trans->request_top_reset) { + trans->request_top_reset = 0; + if (trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_SC) + return IWL_RESET_MODE_TOP_RESET; + return IWL_RESET_MODE_PROD_RESET; + } + + if (trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_SC) { + escalation_list = escalation_list_sc; + escalation_list_size = ARRAY_SIZE(escalation_list_sc); + } else { + escalation_list = escalation_list_old; + escalation_list_size = ARRAY_SIZE(escalation_list_old); + } if (trans->restart.during_reset) at_least = IWL_RESET_MODE_REPROBE; @@ -155,8 +183,8 @@ iwl_trans_determine_restart_mode(struct iwl_trans *trans) data->restart_count = 0; index = data->restart_count; - if (index >= ARRAY_SIZE(escalation_list)) - index = ARRAY_SIZE(escalation_list) - 1; + if (index >= escalation_list_size) + index = escalation_list_size - 1; return max(at_least, escalation_list[index]); } @@ -203,8 +231,13 @@ static void iwl_trans_restart_wk(struct work_struct *wk) iwl_trans_inc_restart_count(trans->dev); switch (mode) { + case IWL_RESET_MODE_TOP_RESET: + trans->do_top_reset = 1; + IWL_ERR(trans, "Device error - TOP reset\n"); + fallthrough; case IWL_RESET_MODE_SW_RESET: - IWL_ERR(trans, "Device error - SW reset\n"); + if (mode == IWL_RESET_MODE_SW_RESET) + IWL_ERR(trans, "Device error - SW reset\n"); iwl_trans_opmode_sw_reset(trans, trans->restart.mode.type); break; case IWL_RESET_MODE_REPROBE: diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-trans.h b/drivers/net/wireless/intel/iwlwifi/iwl-trans.h index 91abab56faba..6eeafe6f96cc 100644 --- a/drivers/net/wireless/intel/iwlwifi/iwl-trans.h +++ b/drivers/net/wireless/intel/iwlwifi/iwl-trans.h @@ -893,6 +893,10 @@ struct iwl_txq { * @dsbr_urm_fw_dependent: switch to URM based on fw settings * @dsbr_urm_permanent: switch to URM permanently * @ext_32khz_clock_valid: if true, the external 32 KHz clock can be used + * @request_top_reset: TOP reset was requested, used by the reset + * worker that should be scheduled (with appropriate reason) + * @do_top_reset: indication to the (PCIe) transport/context-info + * to do the TOP reset */ struct iwl_trans { bool csme_own; @@ -974,6 +978,9 @@ struct iwl_trans { struct delayed_work me_recheck_wk; s8 me_present; + u8 request_top_reset:1, + do_top_reset:1; + /* pointer to trans specific struct */ /*Ensure that this pointer will always be aligned to sizeof pointer */ char trans_specific[] __aligned(sizeof(void *)); @@ -1267,6 +1274,8 @@ enum iwl_reset_mode { /* upper level modes: */ IWL_RESET_MODE_SW_RESET, IWL_RESET_MODE_REPROBE, + /* TOP reset doesn't require PCIe remove */ + IWL_RESET_MODE_TOP_RESET, /* PCIE level modes: */ IWL_RESET_MODE_REMOVE_ONLY, IWL_RESET_MODE_RESCAN, diff --git a/drivers/net/wireless/intel/iwlwifi/pcie/ctxt-info-gen3.c b/drivers/net/wireless/intel/iwlwifi/pcie/ctxt-info-gen3.c index 4f367c7fce25..e383757cfbe0 100644 --- a/drivers/net/wireless/intel/iwlwifi/pcie/ctxt-info-gen3.c +++ b/drivers/net/wireless/intel/iwlwifi/pcie/ctxt-info-gen3.c @@ -97,8 +97,8 @@ out: *control_flags |= IWL_PRPH_SCRATCH_EARLY_DEBUG_EN | dbg_flags; } -int iwl_pcie_ctxt_info_gen3_init(struct iwl_trans *trans, - const struct fw_img *fw) +int iwl_pcie_ctxt_info_gen3_alloc(struct iwl_trans *trans, + const struct fw_img *fw) { struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); struct iwl_context_info_gen3 *ctxt_info_gen3; @@ -168,6 +168,11 @@ int iwl_pcie_ctxt_info_gen3_init(struct iwl_trans *trans, IWL_PRPH_SCRATCH_SCU_FORCE_ACTIVE); } + if (trans->do_top_reset) { + WARN_ON(trans->trans_cfg->device_family < IWL_DEVICE_FAMILY_SC); + control_flags |= IWL_PRPH_SCRATCH_TOP_RESET; + } + /* initialize RX default queue */ prph_sc_ctrl->rbd_cfg.free_rbd_addr = cpu_to_le64(trans_pcie->rxq->bd_dma); @@ -266,18 +271,6 @@ int iwl_pcie_ctxt_info_gen3_init(struct iwl_trans *trans, memcpy(trans_pcie->iml, trans->iml, trans->iml_len); - iwl_enable_fw_load_int_ctx_info(trans); - - /* kick FW self load */ - iwl_write64(trans, CSR_CTXT_INFO_ADDR, - trans_pcie->ctxt_info_dma_addr); - iwl_write64(trans, CSR_IML_DATA_ADDR, - trans_pcie->iml_dma_addr); - iwl_write32(trans, CSR_IML_SIZE_ADDR, trans->iml_len); - - iwl_set_bit(trans, CSR_CTXT_INFO_BOOT_CTRL, - CSR_AUTO_FUNC_BOOT_ENA); - return 0; err_free_ctxt_info: @@ -298,6 +291,23 @@ err_free_prph_scratch: } +void iwl_pcie_ctxt_info_gen3_kick(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + + iwl_enable_fw_load_int_ctx_info(trans, trans->do_top_reset); + + /* kick FW self load */ + iwl_write64(trans, CSR_CTXT_INFO_ADDR, + trans_pcie->ctxt_info_dma_addr); + iwl_write64(trans, CSR_IML_DATA_ADDR, + trans_pcie->iml_dma_addr); + iwl_write32(trans, CSR_IML_SIZE_ADDR, trans->iml_len); + + iwl_set_bit(trans, CSR_CTXT_INFO_BOOT_CTRL, + CSR_AUTO_FUNC_BOOT_ENA); +} + void iwl_pcie_ctxt_info_gen3_free(struct iwl_trans *trans, bool alive) { struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); diff --git a/drivers/net/wireless/intel/iwlwifi/pcie/ctxt-info.c b/drivers/net/wireless/intel/iwlwifi/pcie/ctxt-info.c index 3f0256b3565d..4e79ca7e47b2 100644 --- a/drivers/net/wireless/intel/iwlwifi/pcie/ctxt-info.c +++ b/drivers/net/wireless/intel/iwlwifi/pcie/ctxt-info.c @@ -232,7 +232,7 @@ int iwl_pcie_ctxt_info_init(struct iwl_trans *trans, trans_pcie->ctxt_info = ctxt_info; - iwl_enable_fw_load_int_ctx_info(trans); + iwl_enable_fw_load_int_ctx_info(trans, false); /* Configure debug, if exists */ if (iwl_pcie_dbg_on(trans)) diff --git a/drivers/net/wireless/intel/iwlwifi/pcie/internal.h b/drivers/net/wireless/intel/iwlwifi/pcie/internal.h index 45460f93d24a..390e447b452c 100644 --- a/drivers/net/wireless/intel/iwlwifi/pcie/internal.h +++ b/drivers/net/wireless/intel/iwlwifi/pcie/internal.h @@ -269,6 +269,7 @@ enum iwl_pcie_fw_reset_state { FW_RESET_REQUESTED, FW_RESET_OK, FW_RESET_ERROR, + FW_RESET_TOP_REQUESTED, }; /** @@ -940,11 +941,13 @@ static inline void iwl_enable_fw_load_int(struct iwl_trans *trans) } } -static inline void iwl_enable_fw_load_int_ctx_info(struct iwl_trans *trans) +static inline void iwl_enable_fw_load_int_ctx_info(struct iwl_trans *trans, + bool top_reset) { struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); - IWL_DEBUG_ISR(trans, "Enabling ALIVE interrupt only\n"); + IWL_DEBUG_ISR(trans, "Enabling %s interrupt only\n", + top_reset ? "RESET" : "ALIVE"); if (!trans_pcie->msix_enabled) { /* @@ -954,11 +957,20 @@ static inline void iwl_enable_fw_load_int_ctx_info(struct iwl_trans *trans) * RX interrupt which will allow us to receive the ALIVE * notification (which is Rx) and continue the flow. */ - trans_pcie->inta_mask = CSR_INT_BIT_ALIVE | CSR_INT_BIT_FH_RX; + if (top_reset) + trans_pcie->inta_mask = CSR_INT_BIT_RESET_DONE; + else + trans_pcie->inta_mask = CSR_INT_BIT_ALIVE | + CSR_INT_BIT_FH_RX; iwl_write32(trans, CSR_INT_MASK, trans_pcie->inta_mask); } else { - iwl_enable_hw_int_msk_msix(trans, - MSIX_HW_INT_CAUSES_REG_ALIVE); + u32 val = top_reset ? MSIX_HW_INT_CAUSES_REG_RESET_DONE + : MSIX_HW_INT_CAUSES_REG_ALIVE; + + iwl_enable_hw_int_msk_msix(trans, val); + + if (top_reset) + return; /* * Leave all the FH causes enabled to get the ALIVE * notification. diff --git a/drivers/net/wireless/intel/iwlwifi/pcie/rx.c b/drivers/net/wireless/intel/iwlwifi/pcie/rx.c index bbeecb621593..b619a77f81f1 100644 --- a/drivers/net/wireless/intel/iwlwifi/pcie/rx.c +++ b/drivers/net/wireless/intel/iwlwifi/pcie/rx.c @@ -2133,7 +2133,7 @@ irqreturn_t iwl_pcie_irq_handler(int irq, void *dev_id) iwl_enable_rfkill_int(trans); /* Re-enable the ALIVE / Rx interrupt if it occurred */ else if (handled & (CSR_INT_BIT_ALIVE | CSR_INT_BIT_FH_RX)) - iwl_enable_fw_load_int_ctx_info(trans); + iwl_enable_fw_load_int_ctx_info(trans, false); spin_unlock_bh(&trans_pcie->irq_lock); } @@ -2356,9 +2356,13 @@ irqreturn_t iwl_pcie_irq_msix_handler(int irq, void *dev_id) if (inta_hw & MSIX_HW_INT_CAUSES_REG_TOP_FATAL_ERR) { IWL_ERR(trans, "TOP Fatal error detected, inta_hw=0x%x.\n", inta_hw); - if (trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_BZ) - iwl_trans_pcie_reset(trans, - IWL_RESET_MODE_PROD_RESET); + if (trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_BZ) { + trans->request_top_reset = 1; + iwl_op_mode_nic_error(trans->op_mode, + IWL_ERR_TYPE_TOP_FATAL_ERROR); + iwl_trans_schedule_reset(trans, + IWL_ERR_TYPE_TOP_FATAL_ERROR); + } } /* Error detected by uCode */ diff --git a/drivers/net/wireless/intel/iwlwifi/pcie/trans-gen2.c b/drivers/net/wireless/intel/iwlwifi/pcie/trans-gen2.c index 08409e24aed6..abddaffcaaf0 100644 --- a/drivers/net/wireless/intel/iwlwifi/pcie/trans-gen2.c +++ b/drivers/net/wireless/intel/iwlwifi/pcie/trans-gen2.c @@ -488,12 +488,16 @@ int iwl_trans_pcie_gen2_start_fw(struct iwl_trans *trans, { struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); bool hw_rfkill, keep_ram_busy; + bool top_reset_done = false; int ret; + mutex_lock(&trans_pcie->mutex); +again: /* This may fail if AMT took ownership of the device */ if (iwl_pcie_prepare_card_hw(trans)) { IWL_WARN(trans, "Exit HW not ready\n"); - return -EIO; + ret = -EIO; + goto out; } iwl_enable_rfkill_int(trans); @@ -510,8 +514,6 @@ int iwl_trans_pcie_gen2_start_fw(struct iwl_trans *trans, /* Make sure it finished running */ iwl_pcie_synchronize_irqs(trans); - mutex_lock(&trans_pcie->mutex); - /* If platform's RF_KILL switch is NOT set to KILL */ hw_rfkill = iwl_pcie_check_hw_rf_kill(trans); if (hw_rfkill && !run_in_rfkill) { @@ -541,12 +543,27 @@ int iwl_trans_pcie_gen2_start_fw(struct iwl_trans *trans, goto out; } - if (trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_AX210) - ret = iwl_pcie_ctxt_info_gen3_init(trans, fw); - else + if (WARN_ON(trans->do_top_reset && + trans->trans_cfg->device_family < IWL_DEVICE_FAMILY_SC)) + return -EINVAL; + + /* we need to wait later - set state */ + if (trans->do_top_reset) + trans_pcie->fw_reset_state = FW_RESET_TOP_REQUESTED; + + if (trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_AX210) { + if (!top_reset_done) { + ret = iwl_pcie_ctxt_info_gen3_alloc(trans, fw); + if (ret) + goto out; + } + + iwl_pcie_ctxt_info_gen3_kick(trans); + } else { ret = iwl_pcie_ctxt_info_init(trans, fw); - if (ret) - goto out; + if (ret) + goto out; + } keep_ram_busy = !iwl_pcie_set_ltr(trans); @@ -565,6 +582,38 @@ int iwl_trans_pcie_gen2_start_fw(struct iwl_trans *trans, if (keep_ram_busy) iwl_pcie_spin_for_iml(trans); + if (trans->do_top_reset) { + trans->do_top_reset = 0; + +#define FW_TOP_RESET_TIMEOUT (HZ / 4) + ret = wait_event_timeout(trans_pcie->fw_reset_waitq, + trans_pcie->fw_reset_state != FW_RESET_TOP_REQUESTED, + FW_TOP_RESET_TIMEOUT); + + if (trans_pcie->fw_reset_state != FW_RESET_OK) { + if (trans_pcie->fw_reset_state != FW_RESET_TOP_REQUESTED) + IWL_ERR(trans, + "TOP reset interrupted by error (state %d)!\n", + trans_pcie->fw_reset_state); + else + IWL_ERR(trans, "TOP reset timed out!\n"); + iwl_op_mode_nic_error(trans->op_mode, + IWL_ERR_TYPE_TOP_RESET_FAILED); + iwl_trans_schedule_reset(trans, + IWL_ERR_TYPE_TOP_RESET_FAILED); + ret = -EIO; + goto out; + } + + msleep(10); + IWL_INFO(trans, "TOP reset successful, reinit now\n"); + /* now load the firmware again properly */ + trans_pcie->prph_scratch->ctrl_cfg.control.control_flags &= + ~cpu_to_le32(IWL_PRPH_SCRATCH_TOP_RESET); + top_reset_done = true; + goto again; + } + /* re-check RF-Kill state since we may have missed the interrupt */ hw_rfkill = iwl_pcie_check_hw_rf_kill(trans); if (hw_rfkill && !run_in_rfkill) diff --git a/drivers/net/wireless/intel/iwlwifi/pcie/trans.c b/drivers/net/wireless/intel/iwlwifi/pcie/trans.c index b2258c13f7f7..75e87b8c4a2d 100644 --- a/drivers/net/wireless/intel/iwlwifi/pcie/trans.c +++ b/drivers/net/wireless/intel/iwlwifi/pcie/trans.c @@ -3261,8 +3261,9 @@ static ssize_t iwl_dbgfs_reset_write(struct file *file, { struct iwl_trans *trans = file->private_data; static const char * const modes[] = { - [IWL_RESET_MODE_SW_RESET] = "n/a", - [IWL_RESET_MODE_REPROBE] = "n/a", + [IWL_RESET_MODE_SW_RESET] = "sw", + [IWL_RESET_MODE_REPROBE] = "reprobe", + [IWL_RESET_MODE_TOP_RESET] = "top", [IWL_RESET_MODE_REMOVE_ONLY] = "remove", [IWL_RESET_MODE_RESCAN] = "rescan", [IWL_RESET_MODE_FUNC_RESET] = "function", @@ -3281,8 +3282,18 @@ static ssize_t iwl_dbgfs_reset_write(struct file *file, if (mode < 0) return mode; - if (mode < IWL_RESET_MODE_REMOVE_ONLY) - return -EINVAL; + if (mode < IWL_RESET_MODE_REMOVE_ONLY) { + if (!test_bit(STATUS_DEVICE_ENABLED, &trans->status)) + return -EINVAL; + if (mode == IWL_RESET_MODE_TOP_RESET) { + if (trans->trans_cfg->device_family < IWL_DEVICE_FAMILY_SC) + 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); + return count; + } iwl_trans_pcie_reset(trans, mode); -- 2.51.0 From c766c8bc1146efd032aac739db89b13bd3717dab Mon Sep 17 00:00:00 2001 From: Miri Korenblit Date: Wed, 30 Apr 2025 15:57:21 +0300 Subject: [PATCH 15/16] wifi: iwlwifi: mvm: support iwl_mac_power_cmd version 2 This version fixes the issue that was worked around by iwl_mvm_smps_workaround. So for FWs with the new version don't do the workaround, and set new bit added in this version when appropriate. Signed-off-by: Miri Korenblit Reviewed-by: Emmanuel Grumbach Link: https://patch.msgid.link/20250430155443.5c9a0181a84b.I9a03bc07a7b3f6e37cc1c0c1af5719e765a05897@changeid --- drivers/net/wireless/intel/iwlwifi/mvm/mac80211.c | 3 ++- drivers/net/wireless/intel/iwlwifi/mvm/power.c | 5 ++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/mac80211.c b/drivers/net/wireless/intel/iwlwifi/mvm/mac80211.c index 5d8f50a455d7..70b2cdace86d 100644 --- a/drivers/net/wireless/intel/iwlwifi/mvm/mac80211.c +++ b/drivers/net/wireless/intel/iwlwifi/mvm/mac80211.c @@ -4105,7 +4105,8 @@ void iwl_mvm_smps_workaround(struct iwl_mvm *mvm, struct ieee80211_vif *vif, { struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); - if (!iwl_mvm_has_rlc_offload(mvm)) + if (!iwl_mvm_has_rlc_offload(mvm) || + iwl_fw_lookup_cmd_ver(mvm->fw, MAC_PM_POWER_TABLE, 0) >= 2) return; mvmvif->ps_disabled = !vif->cfg.ps; diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/power.c b/drivers/net/wireless/intel/iwlwifi/mvm/power.c index a386b315e52f..0057fddf88f0 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-2024 Intel Corporation + * Copyright (C) 2012-2014, 2018-2019, 2021-2025 Intel Corporation * Copyright (C) 2013-2014 Intel Mobile Communications GmbH * Copyright (C) 2015-2017 Intel Deutschland GmbH */ @@ -376,6 +376,9 @@ static void iwl_mvm_power_build_cmd(struct iwl_mvm *mvm, if (!vif->cfg.ps || !mvmvif->pm_enabled) return; + if (iwl_fw_lookup_cmd_ver(mvm->fw, MAC_PM_POWER_TABLE, 0) >= 2) + cmd->flags |= cpu_to_le16(POWER_FLAGS_ENABLE_SMPS_MSK); + if (iwl_mvm_vif_low_latency(mvmvif) && vif->p2p && (!fw_has_capa(&mvm->fw->ucode_capa, IWL_UCODE_TLV_CAPA_SHORT_PM_TIMEOUTS) || -- 2.51.0 From 64667ab85631d4823c094325c8429605a9bcc6ac Mon Sep 17 00:00:00 2001 From: Emmanuel Grumbach Date: Wed, 30 Apr 2025 15:57:22 +0300 Subject: [PATCH 16/16] wifi: iwlwifi: mld: support for COMPRESSED_BA_RES_API_S_VER_7 The rate format changed but since we don't use the rate, just claim support for this new API. Signed-off-by: Emmanuel Grumbach Signed-off-by: Miri Korenblit Link: https://patch.msgid.link/20250430155443.c4be90e242ff.Ie8a0f0d6320613bd8c5cb6c82a063069fffa3b67@changeid --- drivers/net/wireless/intel/iwlwifi/fw/api/tx.h | 8 +++++--- drivers/net/wireless/intel/iwlwifi/mld/notif.c | 3 ++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/fw/api/tx.h b/drivers/net/wireless/intel/iwlwifi/fw/api/tx.h index 0a39e4b6eb62..50d1c590044f 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/api/tx.h +++ b/drivers/net/wireless/intel/iwlwifi/fw/api/tx.h @@ -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-2025 Intel Corporation * Copyright (C) 2016-2017 Intel Deutschland GmbH */ #ifndef __iwl_fw_api_tx_h__ @@ -701,7 +701,8 @@ enum iwl_mvm_ba_resp_flags { * @rts_retry_cnt: RTS retry count * @reserved: reserved (for alignment) * @wireless_time: Wireless-media time - * @tx_rate: the rate the aggregation was sent at + * @tx_rate: the rate the aggregation was sent at. Format depends on command + * version. * @tfd_cnt: number of TFD-Q elements * @ra_tid_cnt: number of RATID-Q elements * @tfd: array of TFD queue status updates. See &iwl_compressed_ba_tfd @@ -730,7 +731,8 @@ struct iwl_compressed_ba_notif { DECLARE_FLEX_ARRAY(struct iwl_compressed_ba_tfd, tfd); }; } __packed; /* COMPRESSED_BA_RES_API_S_VER_4, - COMPRESSED_BA_RES_API_S_VER_5 */ + COMPRESSED_BA_RES_API_S_VER_6, + COMPRESSED_BA_RES_API_S_VER_7 */ /** * struct iwl_mac_beacon_cmd_v6 - beacon template command diff --git a/drivers/net/wireless/intel/iwlwifi/mld/notif.c b/drivers/net/wireless/intel/iwlwifi/mld/notif.c index 10f1bee89205..76b1a21135a8 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/notif.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/notif.c @@ -307,7 +307,8 @@ CMD_VERSIONS(tx_resp_notif, CMD_VER_ENTRY(8, iwl_tx_resp)) CMD_VERSIONS(compressed_ba_notif, CMD_VER_ENTRY(5, iwl_compressed_ba_notif) - CMD_VER_ENTRY(6, iwl_compressed_ba_notif)) + CMD_VER_ENTRY(6, iwl_compressed_ba_notif) + CMD_VER_ENTRY(7, iwl_compressed_ba_notif)) CMD_VERSIONS(tlc_notif, CMD_VER_ENTRY(3, iwl_tlc_update_notif)) CMD_VERSIONS(mu_mimo_grp_notif, -- 2.51.0