]> git.ipfire.org Git - thirdparty/kernel/stable.git/commitdiff
wifi: iwlwifi: implement TOP reset
authorJohannes Berg <johannes.berg@intel.com>
Wed, 30 Apr 2025 12:57:20 +0000 (15:57 +0300)
committerMiri Korenblit <miriam.rachel.korenblit@intel.com>
Tue, 6 May 2025 19:22:07 +0000 (22:22 +0300)
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 <johannes.berg@intel.com>
Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
Link: https://patch.msgid.link/20250430155443.12f38024a3b4.I9c22f6c4f6de64f3b34ccd898370ec1859ab7dbf@changeid
drivers/net/wireless/intel/iwlwifi/iwl-context-info-gen3.h
drivers/net/wireless/intel/iwlwifi/iwl-op-mode.h
drivers/net/wireless/intel/iwlwifi/iwl-trans.c
drivers/net/wireless/intel/iwlwifi/iwl-trans.h
drivers/net/wireless/intel/iwlwifi/pcie/ctxt-info-gen3.c
drivers/net/wireless/intel/iwlwifi/pcie/ctxt-info.c
drivers/net/wireless/intel/iwlwifi/pcie/internal.h
drivers/net/wireless/intel/iwlwifi/pcie/rx.c
drivers/net/wireless/intel/iwlwifi/pcie/trans-gen2.c
drivers/net/wireless/intel/iwlwifi/pcie/trans.c

index 6111ca970ed2efe080049f51dc9cccf743d0a93d..b028343672cc7ded23f6e1d567f2d744c9ba9158 100644 (file)
@@ -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,
index 6bccb30c0981e335508f208cecc18c256ad2927f..b5d39026fa2f9a28921367ad6f38cf0893acf39a 100644 (file)
@@ -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,
 };
 
 /**
index 71baf09d2f04c27f30c67576d9ca433591229afd..edb56a6772f7d1e2e7f31ba01d4f71a609906326 100644 (file)
@@ -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:
index 91abab56faba5413f7cc4bf00064dc0a86ec334c..6eeafe6f96cc73249b54670716b7bc8cc77122d6 100644 (file)
@@ -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,
index 4f367c7fce259160a55af87b8f973a75a6005be0..e383757cfbe09cc09f5960e198b5d8d7ba46c966 100644 (file)
@@ -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);
index 3f0256b3565d648ddbf9e16e25ba8341a972c8a0..4e79ca7e47b21c62c6795fcc6bf243683a18db3f 100644 (file)
@@ -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))
index 45460f93d24add5c41520db20ef68aef33d6866d..390e447b452c10922c47bf2157a3f89e778be73a 100644 (file)
@@ -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.
index bbeecb62159326e81f6ffd6a659ec92c652f04cf..b619a77f81f1eab5c815a32ddb264e514eb04330 100644 (file)
@@ -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 */
index 08409e24aed63b4330a1095dc0428d46c998f79e..abddaffcaaf09254220cb1f565d112732f839d24 100644 (file)
@@ -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)
index b2258c13f7f7f5155310cb89482acdd5b304753a..75e87b8c4a2d193eda6ea26d131dec4244cbc14e 100644 (file)
@@ -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);