@@ -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
@@ -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);
}
@@ -82,14 +82,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");
@@ -98,6 +98,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
@@ -144,15 +169,21 @@ 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.work);
- 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;
@@ -194,24 +225,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);
@@ -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);
@@ -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);