* [PATCH 2/2] Bluetooth: btintel_pcie: Add TOP silent reset support
2026-10-14 11:47 [PATCH 1/2] Bluetooth: btintel: Add TOP silent reset hooks Chandrashekar Devegowda
@ 2026-10-14 11:47 ` Chandrashekar Devegowda
2026-10-14 12:55 ` [1/2] Bluetooth: btintel: Add TOP silent reset hooks bluez.test.bot
1 sibling, 0 replies; 3+ messages in thread
From: Chandrashekar Devegowda @ 2026-10-14 11:47 UTC (permalink / raw)
To: linux-bluetooth; +Cc: chethan.tumkur.narayan, kiran.k, Chandrashekar Devegowda
Add TOP (Total On-chip Platform) silent reset, a lighter recovery
than PLDR for HW >= SCP2 on subsystem PEP. Both BT- and
WiFi-initiated resets are supported, falling back to PLDR on
failure.
On a BT-initiated reset, hw_error() marks the per-BDF recovery
struct and triggers an FLR; the reprobe restores INTEL_TOP_RESET,
and btintel_pcie_do_top_reset() asserts the reset bit in
FUNC_SCRATCH and replays btintel_send_intel_reset() using the
cached boot_addr, then waits for the GP1 mailbox event and
BOOT_STAGE_REG to clear before a final FLR reprobes the device.
On a WiFi-initiated reset, the GP1 handler decodes the
TOP_SILENT_RESET sub-events from mbox2; if BT is the follower it
goes straight to PLDR instead.
Signed-off-by: Chandrashekar Devegowda <chandrashekar.devegowda@intel.com>
---
drivers/bluetooth/btintel_pcie.c | 638 ++++++++++++++++++++++++++++---
drivers/bluetooth/btintel_pcie.h | 58 ++-
2 files changed, 644 insertions(+), 52 deletions(-)
diff --git a/drivers/bluetooth/btintel_pcie.c b/drivers/bluetooth/btintel_pcie.c
index 91b1ee9d6010..a513bba1d21c 100644
--- a/drivers/bluetooth/btintel_pcie.c
+++ b/drivers/bluetooth/btintel_pcie.c
@@ -67,6 +67,7 @@ MODULE_DEVICE_TABLE(pci, btintel_pcie_table);
struct btintel_pcie_dev_recovery {
struct list_head list;
+ unsigned long flags;
u8 count;
time64_t last_error;
char name[];
@@ -87,6 +88,9 @@ struct btintel_pcie_dev_recovery {
#define BTINTEL_PCIE_RESET_WINDOW_SECS 5
#define BTINTEL_PCIE_FLR_MAX_RETRY 1
+#define BTINTEL_PCIE_TOP_RESET_INTERVAL_MS 250
+#define BTINTEL_PCIE_TOP_RESET_ROLE_SWITCH_MS 5000
+#define BTINTEL_PCIE_TOP_RESET_FALLBACK_MS 250
/* Alive interrupt context */
enum {
@@ -526,6 +530,14 @@ static inline void btintel_pcie_dump_debug_registers(struct hci_dev *hdev)
hci_recv_diag(hdev, skb);
}
+static inline bool btintel_pcie_in_rom(struct btintel_pcie_data *data);
+static void btintel_pcie_reset_ia(struct btintel_pcie_data *data);
+static int btintel_pcie_start_rx(struct btintel_pcie_data *data);
+static struct btintel_pcie_dev_recovery *
+btintel_pcie_get_recovery(struct pci_dev *pdev);
+static bool btintel_pcie_request_reset(struct btintel_pcie_data *data,
+ enum btintel_pcie_reset_type type);
+
static int btintel_pcie_send_sync(struct btintel_pcie_data *data,
struct sk_buff *skb, u32 pkt_type, u16 opcode)
{
@@ -593,6 +605,33 @@ static int btintel_pcie_send_sync(struct btintel_pcie_data *data,
}
if (wait_on_alive) {
+ /* After TOP reset, device returns to ROM state. The normal
+ * alive interrupt won't arrive since FW isn't loaded yet.
+ * Reinitialize RX/TX queues and skip the alive wait so FW
+ * re-download can proceed immediately.
+ */
+ if (test_bit(BTINTEL_PCIE_TOP_RESET, &data->flags) &&
+ btintel_pcie_in_rom(data)) {
+ bool armed;
+
+ /* Claim the transition under irq_lock, same as the
+ * dxstate fallback above, so a concurrent gp0 handler
+ * run observing the same boot stage cannot also arm
+ * RX and race this thread's reset_ia()/start_rx().
+ */
+ spin_lock(&data->irq_lock);
+ armed = data->alive_intr_ctxt == BTINTEL_PCIE_FW_DL;
+ if (!armed)
+ data->alive_intr_ctxt = BTINTEL_PCIE_FW_DL;
+ spin_unlock(&data->irq_lock);
+
+ if (!armed) {
+ btintel_pcie_reset_ia(data);
+ btintel_pcie_start_rx(data);
+ }
+ return 0;
+ }
+
ret = wait_event_timeout(data->gp0_wait_q,
data->gp0_received,
msecs_to_jiffies(BTINTEL_DEFAULT_INTR_TIMEOUT_MS));
@@ -1488,6 +1527,13 @@ static inline bool btintel_pcie_in_iml(struct btintel_pcie_data *data)
!(data->boot_stage_cache & BTINTEL_PCIE_CSR_BOOT_STAGE_OPFW);
}
+static inline bool btintel_pcie_in_rom(struct btintel_pcie_data *data)
+{
+ return data->boot_stage_cache & BTINTEL_PCIE_CSR_BOOT_STAGE_ROM &&
+ !(data->boot_stage_cache & BTINTEL_PCIE_CSR_BOOT_STAGE_IML) &&
+ !(data->boot_stage_cache & BTINTEL_PCIE_CSR_BOOT_STAGE_OPFW);
+}
+
static inline bool btintel_pcie_in_d3(struct btintel_pcie_data *data)
{
return data->boot_stage_cache & BTINTEL_PCIE_CSR_BOOT_STAGE_D3_STATE_READY;
@@ -1870,27 +1916,172 @@ static int btintel_parse_mbox_tlv(struct btintel_pcie_data *data)
return err;
}
+/* Returns true when either the boot-stage register reads 0, or the
+ * recorded generation differs from @gen (the GP1 handler bumped it,
+ * meaning data->top_reset_deadline was extended and should be
+ * re-evaluated by the caller's wait loop).
+ */
+static inline bool btintel_pcie_top_reset_done(struct btintel_pcie_data *data,
+ unsigned int gen)
+{
+ return btintel_pcie_rd_reg32(data, BTINTEL_PCIE_CSR_BOOT_STAGE_REG) == 0 ||
+ READ_ONCE(data->top_reset_gen) != gen;
+}
+
+/* Handle a TOP silent reset MBOX2 sub-event. Called directly from the GP1
+ * handler as soon as it arrives, whether or not data->hdev is valid yet.
+ * bt_dev_*() is safe with a NULL hdev (bt_dev_name() falls back to
+ * "null"), and all state this function touches lives on 'data' (always
+ * allocated whenever MSI-X is live), not the separately-allocated
+ * recovery struct, so there is nothing here that depends on hdev being
+ * set or on a GFP_ATOMIC allocation succeeding.
+ */
+static void btintel_pcie_handle_top_reset_mbox2(struct btintel_pcie_data *data,
+ u32 mbox2)
+{
+ u32 expires = BTINTEL_PCIE_TOP_RESET_INTERVAL_MS;
+ u32 reg;
+
+ switch (mbox2) {
+ case BTINTEL_PCIE_TOP_RESET_WIFI_REQUEST:
+ bt_dev_info(data->hdev,
+ "TOP reset request from WiFi, triggering PLDR");
+ if (!test_bit(BTINTEL_PCIE_TOP_RESET, &data->flags)) {
+ bt_dev_info(data->hdev,
+ "BT is follower, triggering PLDR");
+ set_bit(BTINTEL_PCIE_CORE_HALTED, &data->flags);
+ if (!btintel_pcie_request_reset(data,
+ BTINTEL_PCIE_IOSF_PRR_PLDR)) {
+ /* Rejected because BTINTEL_PCIE_SETUP_DONE
+ * is not set yet; retry once setup
+ * completes instead of dropping the
+ * WiFi-follower request (see
+ * BTINTEL_PCIE_TOP_RESET_WIFI_PLDR_PENDING
+ * in btintel_pcie_setup()).
+ */
+ bt_dev_warn(data->hdev,
+ "TOP reset: PLDR request rejected, deferring until setup completes");
+ clear_bit(BTINTEL_PCIE_CORE_HALTED, &data->flags);
+ set_bit(BTINTEL_PCIE_TOP_RESET_WIFI_PLDR_PENDING,
+ &data->flags);
+
+ /* Close the race with btintel_pcie_setup()'s
+ * one-shot replay of this flag at
+ * BTINTEL_PCIE_SETUP_DONE time: if SETUP_DONE
+ * is already visible here, that replay may
+ * already have run (and found nothing
+ * pending, since we had not set the flag
+ * yet) before this point, so replay it
+ * ourselves instead of leaving it stuck until
+ * a SETUP_DONE transition that already
+ * happened. test_and_clear_bit() ensures only
+ * one of the two sides actually replays if
+ * both observe the race window.
+ */
+ if (test_bit(BTINTEL_PCIE_SETUP_DONE, &data->flags) &&
+ test_and_clear_bit(BTINTEL_PCIE_TOP_RESET_WIFI_PLDR_PENDING,
+ &data->flags)) {
+ bt_dev_info(data->hdev,
+ "Replaying deferred WiFi-follower PLDR request");
+ set_bit(BTINTEL_PCIE_CORE_HALTED, &data->flags);
+ if (!btintel_pcie_request_reset(data,
+ BTINTEL_PCIE_IOSF_PRR_PLDR))
+ clear_bit(BTINTEL_PCIE_CORE_HALTED, &data->flags);
+ }
+ }
+ return;
+ }
+ break;
+ case BTINTEL_PCIE_TOP_RESET_WIFI_ACK:
+ bt_dev_info(data->hdev, "TOP reset ack from WiFi");
+ break;
+ case BTINTEL_PCIE_TOP_RESET_WIFI_NACK:
+ bt_dev_info(data->hdev, "TOP reset NACK from WiFi");
+ set_bit(BTINTEL_PCIE_TOP_RESET_FAILED, &data->flags);
+ break;
+ case BTINTEL_PCIE_TOP_RESET_WIFI_REQ_ROLE_SWITCH:
+ bt_dev_info(data->hdev, "TOP reset WIFI_REQ_ROLE_SWITCH");
+ expires = BTINTEL_PCIE_TOP_RESET_ROLE_SWITCH_MS;
+ reg = btintel_pcie_rd_reg32(data,
+ BTINTEL_PCIE_CSR_RESET_DEBUG_AD);
+ if (reg == BTINTEL_PCIE_TOP_RESET_DBG_RESET_REG)
+ bt_dev_dbg(data->hdev, "TOP reset initiated by WiFi");
+ break;
+ case BTINTEL_PCIE_TOP_RESET_WIFI_NO_RESPONSE:
+ bt_dev_err(data->hdev, "TOP reset WIFI_NO_RESPONSE");
+ set_bit(BTINTEL_PCIE_TOP_RESET_FAILED, &data->flags);
+ break;
+ case BTINTEL_PCIE_TOP_RESET_WIFI_RESPONSE_INVALID:
+ bt_dev_err(data->hdev, "TOP reset WIFI_RESPONSE_INVALID");
+ set_bit(BTINTEL_PCIE_TOP_RESET_FAILED, &data->flags);
+ break;
+ default:
+ bt_dev_err(data->hdev, "TOP reset with undefined MBOX2 0x%8.8x",
+ mbox2);
+ /* An unrecognized sub-event means the negotiation outcome
+ * is unknown; do not let a stale boot_stage==0 read be
+ * mistaken for success further down and select FLR.
+ */
+ set_bit(BTINTEL_PCIE_TOP_RESET_FAILED, &data->flags);
+ break;
+ }
+
+ data->top_reset_expires = expires;
+ /* Record an absolute deadline and bump the generation counter so
+ * an in-progress second wait in btintel_pcie_do_top_reset() can
+ * notice a later extension (e.g. WIFI_REQ_ROLE_SWITCH) instead of
+ * timing out against a stale, shorter value.
+ */
+ WRITE_ONCE(data->top_reset_deadline,
+ jiffies + msecs_to_jiffies(expires));
+ WRITE_ONCE(data->top_reset_gen, data->top_reset_gen + 1);
+ set_bit(BTINTEL_PCIE_MBOX_TOP_RESET, &data->flags);
+ bt_dev_info(data->hdev, "TOP reset timer expires in %u ms", expires);
+ wake_up(&data->gp0_wait_q);
+}
+
static void btintel_pcie_msix_gp1_handler(struct btintel_pcie_data *data)
{
+ ktime_t calltime, delta, rettime;
+ unsigned long long duration;
+ bool top_reset_mbox = false;
bool target_access = false;
+ bool mbox2_status;
u32 addr = 0, size = 0;
+ calltime = ktime_get();
+
/* Read the Mail box status and registers */
data->mbox.mbox_status = btintel_pcie_rd_reg32(data, BTINTEL_PCIE_CSR_MBOX_STATUS_REG);
+ mbox2_status = data->mbox.mbox_status & BTINTEL_PCIE_CSR_MBOX_STATUS_MBOX2;
if (data->mbox.mbox_status & BTINTEL_PCIE_CSR_MBOX_STATUS_MBOX1) {
- data->mbox.mbox1 = btintel_pcie_rd_reg32(data, BTINTEL_PCIE_CSR_MBOX_1_REG);
- if (data->mbox.mbox1 ==
- BTINTEL_PCIE_BUILD_SPECIFIC_RESOURCES_MAPPING) {
- bt_dev_info(data->hdev,
- "mailbox for target access");
+ data->mbox.mbox1 = btintel_pcie_rd_reg32(data,
+ BTINTEL_PCIE_CSR_MBOX_1_REG);
+ if (data->mbox.mbox1 == BTINTEL_PCIE_BUILD_SPECIFIC_RESOURCES_MAPPING) {
+ bt_dev_info(data->hdev, "mailbox for target access");
+ clear_bit(BTINTEL_PCIE_TOP_RESET_MBOX1_PENDING,
+ &data->flags);
target_access = true;
+ } else if (data->mbox.mbox1 == BTINTEL_PCIE_TOP_SILENT_RESET) {
+ if (mbox2_status)
+ top_reset_mbox = true;
+ else
+ set_bit(BTINTEL_PCIE_TOP_RESET_MBOX1_PENDING,
+ &data->flags);
+ } else {
+ clear_bit(BTINTEL_PCIE_TOP_RESET_MBOX1_PENDING,
+ &data->flags);
}
}
- if (data->mbox.mbox_status & BTINTEL_PCIE_CSR_MBOX_STATUS_MBOX2) {
- data->mbox.mbox2 = btintel_pcie_rd_reg32(data, BTINTEL_PCIE_CSR_MBOX_2_REG);
+ if (mbox2_status) {
+ data->mbox.mbox2 = btintel_pcie_rd_reg32(data,
+ BTINTEL_PCIE_CSR_MBOX_2_REG);
if (target_access)
addr = data->mbox.mbox2;
+ if (test_and_clear_bit(BTINTEL_PCIE_TOP_RESET_MBOX1_PENDING,
+ &data->flags))
+ top_reset_mbox = true;
}
if (data->mbox.mbox_status & BTINTEL_PCIE_CSR_MBOX_STATUS_MBOX3) {
@@ -1929,10 +2120,38 @@ static void btintel_pcie_msix_gp1_handler(struct btintel_pcie_data *data)
}
}
+ /* Handle SCU state change inline, just log it */
+ if ((data->mbox.mbox_status & BTINTEL_PCIE_CSR_MBOX_STATUS_MBOX1) &&
+ data->mbox.mbox1 == BTINTEL_PCIE_CNVI_SCU_REG_MAIN_FSM_STICKY &&
+ mbox2_status) {
+ if (data->mbox.mbox2 == BTINTEL_PCIE_SCU_CORE_IDLE_SET)
+ bt_dev_dbg(data->hdev, "TOP reset SCU CORE IDLE set");
+ if (data->mbox.mbox2 == BTINTEL_PCIE_SCU_CORE_IDLE_RESET)
+ bt_dev_dbg(data->hdev, "TOP reset SCU CORE IDLE reset");
+ }
+
+ /*
+ * Handle TOP silent reset mbox notification inline. MBOX1 =
+ * TOP_SILENT_RESET signals a TOP reset event, but the actual
+ * sub-event code lives in MBOX2. If MBOX1 arrived alone,
+ * BTINTEL_PCIE_TOP_RESET_MBOX1_PENDING pairs it with this MBOX2
+ * interrupt instead of using stale mailbox data. Handled directly
+ * here (not deferred) regardless of whether data->hdev is valid
+ * yet: btintel_pcie_handle_top_reset_mbox2() only touches state on
+ * 'data', which is always allocated whenever MSI-X is live.
+ */
+ if (top_reset_mbox)
+ btintel_pcie_handle_top_reset_mbox2(data, data->mbox.mbox2);
+
/* Mailbox is read, ack to FW */
btintel_pcie_set_reg_bits(data,
BTINTEL_PCIE_CSR_IPC_DOORBELL_VEC_REG,
BTINTEL_PCIE_CSR_DOORBELL_MBOX_READ_CONFIRM);
+
+ rettime = ktime_get();
+ delta = ktime_sub(rettime, calltime);
+ duration = (unsigned long long)ktime_to_ns(delta) >> 10;
+ bt_dev_dbg(data->hdev, "Mailbox acked in %llu usecs", duration);
}
/* This function handles the MSI-X interrupt for gp0 cause (bit 0 in
@@ -1987,11 +2206,27 @@ static void btintel_pcie_msix_gp0_handler(struct btintel_pcie_data *data)
signal_waitq = true;
break;
case BTINTEL_PCIE_FW_DL:
- /* Error case is already handled. Ideally control shall not
- * reach here
+ /* During a TOP silent reset the controller returns to
+ * boot_stage 0 while alive_intr_ctxt is still FW_DL (set
+ * by btintel_pcie_send_sync()'s post-TOP fast-path).
+ * btintel_pcie_do_top_reset()'s second wait polls
+ * BOOT_STAGE_REG == 0 on gp0_wait_q; without this wake
+ * the wait only re-checks on timer expiry and the driver
+ * needlessly waits the full 250 ms / 5000 ms timeout even
+ * if the FW signalled ready earlier.
*/
+ if (test_bit(BTINTEL_PCIE_TOP_RESET, &data->flags))
+ signal_waitq = true;
break;
case BTINTEL_PCIE_INTEL_HCI_RESET1:
+ /* As with the FW_DL case above, a TOP silent reset can
+ * bring GP0 here while still waiting on BOOT_STAGE_REG == 0
+ * in btintel_pcie_do_top_reset()'s second wait. Wake it so
+ * it re-checks immediately instead of timing out.
+ */
+ if (test_bit(BTINTEL_PCIE_TOP_RESET, &data->flags))
+ signal_waitq = true;
+
if (btintel_pcie_in_op(data)) {
submit_rx = true;
signal_waitq = true;
@@ -2835,6 +3070,7 @@ static struct btintel_pcie_causes_list causes_list[] = {
{ BTINTEL_PCIE_MSIX_FH_INT_CAUSES_0, BTINTEL_PCIE_CSR_MSIX_FH_INT_MASK, 0x00 },
{ BTINTEL_PCIE_MSIX_FH_INT_CAUSES_1, BTINTEL_PCIE_CSR_MSIX_FH_INT_MASK, 0x01 },
{ BTINTEL_PCIE_MSIX_HW_INT_CAUSES_GP0, BTINTEL_PCIE_CSR_MSIX_HW_INT_MASK, 0x20 },
+ { BTINTEL_PCIE_MSIX_HW_INT_CAUSES_GP1, BTINTEL_PCIE_CSR_MSIX_HW_INT_MASK, 0x21 },
{ BTINTEL_PCIE_MSIX_HW_INT_CAUSES_HWEXP, BTINTEL_PCIE_CSR_MSIX_HW_INT_MASK, 0x23 },
{ BTINTEL_PCIE_MSIX_HW_INT_CAUSES_FWTRIG, BTINTEL_PCIE_CSR_MSIX_HW_INT_MASK, 0x25 },
};
@@ -3526,12 +3762,160 @@ static int btintel_pcie_setup_internal(struct hci_dev *hdev)
return err;
}
+static int btintel_pcie_do_top_reset(struct btintel_pcie_data *data,
+ struct hci_dev *hdev)
+{
+ struct btintel_data *intel_data = hci_get_priv(hdev);
+ long ret;
+ u32 reg;
+ int err;
+
+ /* Set scratch register to indicate TOP silent reset to FW */
+ btintel_pcie_set_reg_bits(data, BTINTEL_PCIE_CSR_FUNC_SCRATCH_CSR,
+ BTINTEL_PCIE_CSR_FUNC_SCRATCH_TOP_SILENT_RESET);
+
+ /* Clear mbox flag before sending reset to avoid race with ISR.
+ * Both flags live on data->flags (not the separately-allocated
+ * recovery struct): they are scoped to this single call and do
+ * not need to survive a device_reprobe(), so tracking them here
+ * removes any dependency on btintel_pcie_get_recovery()'s
+ * GFP_ATOMIC allocation succeeding.
+ */
+ clear_bit(BTINTEL_PCIE_MBOX_TOP_RESET, &data->flags);
+ clear_bit(BTINTEL_PCIE_TOP_RESET_FAILED, &data->flags);
+
+ err = btintel_send_intel_reset(hdev, intel_data->boot_addr);
+ if (err) {
+ bt_dev_err(hdev, "Intel Soft Reset failed (%d)", err);
+ data->reset_type = BTINTEL_PCIE_IOSF_PRR_PLDR;
+ goto done;
+ }
+
+ /* First wait: wait for initial mbox event from FW */
+ ret = wait_event_timeout(data->gp0_wait_q,
+ test_bit(BTINTEL_PCIE_MBOX_TOP_RESET, &data->flags),
+ msecs_to_jiffies(BTINTEL_PCIE_TOP_RESET_FALLBACK_MS));
+
+ if (!ret) {
+ bt_dev_warn(hdev,
+ "TOP reset: no mbox received within %u ms; doing PLDR",
+ BTINTEL_PCIE_TOP_RESET_FALLBACK_MS);
+ data->reset_type = BTINTEL_PCIE_IOSF_PRR_PLDR;
+ goto done;
+ }
+
+ /* Second wait: mbox received, wait for boot_stage == 0 against the
+ * absolute deadline in data->top_reset_deadline. The GP1 handler
+ * can extend that deadline (e.g. on WIFI_REQ_ROLE_SWITCH) and bumps
+ * top_reset_gen when it does; loop so such an extension is honored
+ * instead of being capped by whatever remaining time was passed to
+ * an already-running wait_event_timeout().
+ */
+ for (;;) {
+ unsigned int gen = READ_ONCE(data->top_reset_gen);
+ long remaining = (long)READ_ONCE(data->top_reset_deadline) -
+ (long)jiffies;
+
+ if (remaining <= 0) {
+ ret = 0;
+ break;
+ }
+
+ wait_event_timeout(data->gp0_wait_q,
+ btintel_pcie_top_reset_done(data, gen),
+ remaining);
+
+ reg = btintel_pcie_rd_reg32(data, BTINTEL_PCIE_CSR_BOOT_STAGE_REG);
+ if (reg == 0) {
+ ret = 1;
+ break;
+ }
+ /* Either genuinely timed out, or woken by a generation bump
+ * (deadline extended) with boot_stage still nonzero; either
+ * way, loop back and recompute remaining time against the
+ * latest deadline.
+ */
+ }
+
+ reg = btintel_pcie_rd_reg32(data, BTINTEL_PCIE_CSR_BOOT_STAGE_REG);
+ bt_dev_info(hdev, "After side band reset boot-stage: 0x%8.8x", reg);
+
+ /* A NACK / no-response / invalid-response from WiFi means the TOP
+ * reset negotiation itself failed, regardless of what boot_stage
+ * happens to read right now; do not treat that as success.
+ */
+ if (test_bit(BTINTEL_PCIE_TOP_RESET_FAILED, &data->flags)) {
+ bt_dev_warn(hdev,
+ "TOP reset: WiFi reported failure, triggering PLDR");
+ data->reset_type = BTINTEL_PCIE_IOSF_PRR_PLDR;
+ } else if (!ret && reg != 0) {
+ bt_dev_warn(hdev,
+ "TOP reset failed (boot_stage:0x%08x), triggering PLDR",
+ reg);
+ data->reset_type = BTINTEL_PCIE_IOSF_PRR_PLDR;
+ } else {
+ data->reset_type = BTINTEL_PCIE_IOSF_PRR_FLR;
+ }
+
+done:
+ clear_bit(BTINTEL_PCIE_MBOX_TOP_RESET, &data->flags);
+ btintel_clear_flag(hdev, INTEL_TOP_RESET);
+ btintel_pcie_clr_reg_bits(data, BTINTEL_PCIE_CSR_FUNC_SCRATCH_CSR,
+ BTINTEL_PCIE_CSR_FUNC_SCRATCH_TOP_SILENT_RESET);
+
+ /* BTINTEL_PCIE_TOP_RESET is still set on data->flags, allowing
+ * btintel_pcie_request_reset() to proceed without SETUP_DONE.
+ * The flag is cleared by reset_work after recovery completes.
+ */
+ btintel_pcie_request_reset(data, data->reset_type);
+
+ /* Return -ECANCELED so btintel_pcie_setup() propagates failure
+ * up to hci_dev_open_sync(). The reset_work scheduled above
+ * will tear down this hdev instance via device_reprobe(); if
+ * HCI init proceeded on the current instance it would race
+ * with that teardown and enqueue commands to a controller that
+ * is being reset.
+ */
+ return -ECANCELED;
+}
+
static int btintel_pcie_setup(struct hci_dev *hdev)
{
- int err, fw_dl_retry = 0;
struct btintel_pcie_data *data = hci_get_drvdata(hdev);
+ struct btintel_pcie_dev_recovery *rec;
+ int err, fw_dl_retry = 0;
+
+ /* Restore TOP_RESET intent from persistent recovery data across
+ * device_reprobe(). The recovery struct is keyed by PCI BDF and
+ * outlives the freed 'data' from the previous instance.
+ */
+ rec = btintel_pcie_get_recovery(data->pdev);
+ if (rec && test_and_clear_bit(BTINTEL_PCIE_REC_TOP_RESET_PENDING,
+ &rec->flags)) {
+ set_bit(BTINTEL_PCIE_TOP_RESET, &data->flags);
+ btintel_set_flag(hdev, INTEL_TOP_RESET);
+ }
+
+ while ((err = btintel_pcie_setup_internal(hdev)) != 0) {
+ if (err == -ENODEV)
+ break;
+
+ /* Any failure while a TOP reset is pending means the
+ * normal firmware-download retry below is not going to
+ * help (the controller is in the silent-reset state the
+ * TOP reset exists to recover from); dispatch it instead
+ * of exhausting retries on a path that cannot succeed.
+ * Checked on every failing iteration, not just via the
+ * loop condition, so a TOP reset pending during the final
+ * retry attempt (whatever error it returns, not only
+ * -ECANCELED) is not missed.
+ */
+ if (test_bit(BTINTEL_PCIE_TOP_RESET, &data->flags))
+ return btintel_pcie_do_top_reset(data, hdev);
+
+ if (fw_dl_retry++ >= 1)
+ break;
- while ((err = btintel_pcie_setup_internal(hdev)) && fw_dl_retry++ < 1) {
bt_dev_err(hdev, "Firmware download retry count: %d",
fw_dl_retry);
btintel_pcie_dump_debug_registers(hdev);
@@ -3554,30 +3938,53 @@ static int btintel_pcie_setup(struct hci_dev *hdev)
btintel_pcie_start_rx(data);
}
- if (!err)
+ if (!err) {
set_bit(BTINTEL_PCIE_SETUP_DONE, &data->flags);
+
+ /* Retry a WiFi-follower PLDR request that
+ * btintel_pcie_handle_top_reset_mbox2() had to defer
+ * because setup had not completed yet, instead of
+ * leaving it dropped.
+ */
+ if (test_and_clear_bit(BTINTEL_PCIE_TOP_RESET_WIFI_PLDR_PENDING,
+ &data->flags)) {
+ bt_dev_info(hdev,
+ "Replaying deferred WiFi-follower PLDR request");
+ set_bit(BTINTEL_PCIE_CORE_HALTED, &data->flags);
+ if (!btintel_pcie_request_reset(data,
+ BTINTEL_PCIE_IOSF_PRR_PLDR))
+ clear_bit(BTINTEL_PCIE_CORE_HALTED, &data->flags);
+ }
+ }
return err;
}
static struct btintel_pcie_dev_recovery *
-btintel_pcie_get_recovery(struct pci_dev *pdev, struct device *dev)
+btintel_pcie_get_recovery(struct pci_dev *pdev)
{
struct btintel_pcie_dev_recovery *tmp, *data = NULL;
const char *name = pci_name(pdev);
const size_t name_len = strlen(name) + 1;
- struct hci_dev *hdev = to_hci_dev(dev);
+ unsigned long flags;
- spin_lock(&btintel_pcie_recovery_lock);
+ /*
+ * This function runs both in process context (setup, reset_work,
+ * request_reset, hw_error) and in hardirq context (GP1 MSI-X
+ * handler). Use irqsave/irqrestore so a GP1 interrupt firing on
+ * the same CPU while a process-context caller holds the lock
+ * cannot spin waiting on itself and hard-lock the system.
+ */
+ spin_lock_irqsave(&btintel_pcie_recovery_lock, flags);
list_for_each_entry(tmp, &btintel_pcie_recovery_list, list) {
if (strcmp(tmp->name, name))
continue;
data = tmp;
break;
}
- spin_unlock(&btintel_pcie_recovery_lock);
+ spin_unlock_irqrestore(&btintel_pcie_recovery_lock, flags);
if (data) {
- bt_dev_dbg(hdev, "Found restart data for BDF: %s", data->name);
+ dev_dbg(&pdev->dev, "Found restart data for BDF: %s", data->name);
return data;
}
@@ -3586,9 +3993,23 @@ btintel_pcie_get_recovery(struct pci_dev *pdev, struct device *dev)
return NULL;
strscpy(data->name, name, name_len);
- spin_lock(&btintel_pcie_recovery_lock);
+ spin_lock_irqsave(&btintel_pcie_recovery_lock, flags);
+ /* Re-search under lock to avoid a TOCTOU race where a concurrent
+ * caller (GP1 ISR, hw_error, setup or reset_work) inserted an
+ * entry for the same BDF between the initial search and this
+ * insertion. Without this check the list can end up with
+ * duplicate entries and later lookups may return an orphaned
+ * struct that no longer receives updates.
+ */
+ list_for_each_entry(tmp, &btintel_pcie_recovery_list, list) {
+ if (strcmp(tmp->name, name))
+ continue;
+ spin_unlock_irqrestore(&btintel_pcie_recovery_lock, flags);
+ kfree(data);
+ return tmp;
+ }
list_add_tail(&data->list, &btintel_pcie_recovery_list);
- spin_unlock(&btintel_pcie_recovery_lock);
+ spin_unlock_irqrestore(&btintel_pcie_recovery_lock, flags);
return data;
}
@@ -3604,13 +4025,12 @@ static void btintel_pcie_free_restart_list(void)
}
}
-static void btintel_pcie_inc_recovery_count(struct pci_dev *pdev,
- struct device *dev)
+static void btintel_pcie_inc_recovery_count(struct pci_dev *pdev)
{
struct btintel_pcie_dev_recovery *data;
time64_t retry_window;
- data = btintel_pcie_get_recovery(pdev, dev);
+ data = btintel_pcie_get_recovery(pdev);
if (!data)
return;
@@ -3681,7 +4101,16 @@ static int btintel_pcie_acpi_reset_method(struct btintel_pcie_data *data)
return ret;
}
-static void btintel_pcie_perform_pldr(struct btintel_pcie_data *data)
+/* Returns 0 if the ACPI _PRR reset succeeded and device_reprobe() tore
+ * down and re-probed 'data' (which is therefore no longer valid to
+ * touch), or a negative errno if the reset itself failed and 'data' is
+ * still alive and needs the caller's failure cleanup. Must not be
+ * inferred from device_reprobe()'s own return value: device_reprobe()
+ * always runs .remove() (freeing 'data') before attempting the fresh
+ * probe, so a nonzero device_reprobe() result does not mean 'data' is
+ * still alive.
+ */
+static int btintel_pcie_perform_pldr(struct btintel_pcie_data *data)
{
struct pci_dev *pdev = data->pdev;
struct pci_dev *wifi = NULL;
@@ -3702,7 +4131,7 @@ static void btintel_pcie_perform_pldr(struct btintel_pcie_data *data)
bus = pdev->bus;
if (!bus)
- return;
+ return -ENODEV;
list_for_each_entry(tmp, &bus->devices, bus_list) {
if (pci_match_id(wifi_device_ids, tmp)) {
@@ -3729,10 +4158,10 @@ static void btintel_pcie_perform_pldr(struct btintel_pcie_data *data)
pci_dev_put(wifi);
}
- if (!ret) {
- if (device_reprobe(&pdev->dev))
- BT_ERR("BT reprobe failed for BDF:%s", pci_name(pdev));
- }
+ if (!ret && device_reprobe(&pdev->dev))
+ BT_ERR("BT reprobe failed for BDF:%s", pci_name(pdev));
+
+ return ret;
}
/*
@@ -3769,6 +4198,27 @@ static int btintel_pcie_perform_flr(struct btintel_pcie_data *data)
return 0;
}
+/* Restore the reset-serialization/bookkeeping state after a reset attempt
+ * failed with 'data' still alive (no device_reprobe() tore it down and
+ * started fresh), so a later recovery attempt is not permanently blocked.
+ * Does not touch BTINTEL_PCIE_CORE_HALTED, which is a separate one-shot
+ * latch intentionally cleared only by re-probe (see
+ * btintel_pcie_msix_hw_exp_handler()).
+ */
+static void btintel_pcie_reset_failed(struct btintel_pcie_data *data)
+{
+ struct btintel_pcie_dev_recovery *rec = btintel_pcie_get_recovery(data->pdev);
+
+ clear_bit(BTINTEL_PCIE_TOP_RESET, &data->flags);
+ clear_bit(BTINTEL_PCIE_RECOVERY_IN_PROGRESS, &data->flags);
+ if (rec)
+ clear_bit(BTINTEL_PCIE_REC_TOP_RESET_PENDING, &rec->flags);
+ enable_work(&data->coredump_work);
+ enable_work(&data->hwexp_work);
+ enable_work(&data->fwtrigger_work);
+ enable_work(&data->mbox_work);
+}
+
static void btintel_pcie_reset_work(struct work_struct *wk)
{
struct btintel_pcie_data *data =
@@ -3810,15 +4260,39 @@ static void btintel_pcie_reset_work(struct work_struct *wk)
* or removed from the bus list while this code runs.
*/
if (data->reset_type == BTINTEL_PCIE_IOSF_PRR_PLDR) {
- btintel_pcie_perform_pldr(data);
+ if (btintel_pcie_perform_pldr(data))
+ btintel_pcie_reset_failed(data);
goto out;
}
if (btintel_pcie_perform_flr(data)) {
- enable_work(&data->coredump_work);
- enable_work(&data->hwexp_work);
- enable_work(&data->fwtrigger_work);
- enable_work(&data->mbox_work);
+ struct btintel_pcie_dev_recovery *rec;
+
+ rec = btintel_pcie_get_recovery(data->pdev);
+ if (test_bit(BTINTEL_PCIE_TOP_RESET, &data->flags)) {
+ bt_dev_warn(data->hdev,
+ "TOP reset FLR failed, falling back to PLDR");
+ clear_bit(BTINTEL_PCIE_TOP_RESET, &data->flags);
+ if (rec)
+ clear_bit(BTINTEL_PCIE_REC_TOP_RESET_PENDING,
+ &rec->flags);
+ data->reset_type = BTINTEL_PCIE_IOSF_PRR_PLDR;
+ /* The TOP_RESET bypass above was consumed by this
+ * fallback attempt; if it also fails, run the
+ * shared cleanup below instead of leaving the
+ * device stuck with no further recovery.
+ */
+ if (btintel_pcie_perform_pldr(data))
+ btintel_pcie_reset_failed(data);
+ goto out;
+ }
+
+ /* FLR failed: 'data' is still alive and no fresh probe
+ * ran to consume the persistent TOP-reset intent. Restore
+ * the bookkeeping used by the reset-serialization contract
+ * so a later recovery can proceed.
+ */
+ btintel_pcie_reset_failed(data);
}
out:
@@ -3848,19 +4322,51 @@ static void btintel_pcie_reset_work(struct work_struct *wk)
* tears down and re-probes 'data', so there is no "in-flight"
* reset to follow up after device_reprobe() succeeds.
*/
-static void btintel_pcie_request_reset(struct btintel_pcie_data *data,
+static bool btintel_pcie_request_reset(struct btintel_pcie_data *data,
enum btintel_pcie_reset_type type)
{
- if (!test_bit(BTINTEL_PCIE_SETUP_DONE, &data->flags))
- return;
+ struct btintel_pcie_dev_recovery *rec = NULL;
if (test_and_set_bit(BTINTEL_PCIE_RECOVERY_IN_PROGRESS, &data->flags))
- return;
+ return false;
+
+ /* Normal path requires SETUP_DONE. TOP reset recovery calls this
+ * from btintel_pcie_do_top_reset() before setup completes; the
+ * BTINTEL_PCIE_TOP_RESET flag authorizes bypassing that gate.
+ */
+ if (!test_bit(BTINTEL_PCIE_SETUP_DONE, &data->flags) &&
+ !test_bit(BTINTEL_PCIE_TOP_RESET, &data->flags)) {
+ clear_bit(BTINTEL_PCIE_RECOVERY_IN_PROGRESS, &data->flags);
+ return false;
+ }
+
+ if (type == BTINTEL_PCIE_IOSF_TOP_RESET) {
+ rec = btintel_pcie_get_recovery(data->pdev);
+ if (!rec) {
+ clear_bit(BTINTEL_PCIE_RECOVERY_IN_PROGRESS,
+ &data->flags);
+ return false;
+ }
+
+ set_bit(BTINTEL_PCIE_TOP_RESET, &data->flags);
+ set_bit(BTINTEL_PCIE_REC_TOP_RESET_PENDING, &rec->flags);
+ }
data->reset_type = type;
pci_dev_get(data->pdev);
- schedule_work(&data->reset_work);
+ if (!schedule_work(&data->reset_work)) {
+ pci_dev_put(data->pdev);
+ if (type == BTINTEL_PCIE_IOSF_TOP_RESET) {
+ clear_bit(BTINTEL_PCIE_TOP_RESET, &data->flags);
+ clear_bit(BTINTEL_PCIE_REC_TOP_RESET_PENDING,
+ &rec->flags);
+ }
+ clear_bit(BTINTEL_PCIE_RECOVERY_IN_PROGRESS, &data->flags);
+ return false;
+ }
+
+ return true;
}
static void btintel_pcie_hci_reset(struct hci_dev *hdev)
@@ -3907,10 +4413,41 @@ static struct attribute *btintel_pcie_attrs[] = {
ATTRIBUTE_GROUPS(btintel_pcie);
+static const char *btintel_pcie_resettype_to_str(u8 reset_type)
+{
+ switch (reset_type) {
+ case BTINTEL_PCIE_IOSF_PRR_FLR:
+ return "FLR";
+ case BTINTEL_PCIE_IOSF_PRR_PLDR:
+ return "PLDR";
+ case BTINTEL_PCIE_IOSF_TOP_RESET:
+ return "TOP_RESET";
+ default:
+ return "UNKNOWN";
+ }
+}
+
+static bool btintel_pcie_top_reset_supported(struct btintel_pcie_data *data)
+{
+ struct btintel_data *cnvi_data = hci_get_priv(data->hdev);
+ u8 hw_variant = INTEL_HW_VARIANT(cnvi_data->cnvi_bt);
+
+ if (hw_variant >= BTINTEL_HWID_SCP2 &&
+ data->pdev->subsystem_device == BTINTEL_PCIE_SUBSYSTEM_ID_PEP) {
+ bt_dev_info(data->hdev, "HW variant 0x%02x supports top-reset",
+ hw_variant);
+ return true;
+ }
+
+ bt_dev_dbg(data->hdev,
+ "HW variant 0x%02x does not support top-reset", hw_variant);
+ return false;
+}
+
static void btintel_pcie_hw_error(struct hci_dev *hdev, u8 code)
{
- struct btintel_pcie_dev_recovery *rec;
struct btintel_pcie_data *dev_data = hci_get_drvdata(hdev);
+ struct btintel_pcie_dev_recovery *rec;
struct pci_dev *pdev = dev_data->pdev;
enum btintel_pcie_reset_type type;
time64_t retry_window;
@@ -3920,15 +4457,18 @@ static void btintel_pcie_hw_error(struct hci_dev *hdev, u8 code)
btintel_pcie_dump_debug_registers(hdev);
- rec = btintel_pcie_get_recovery(pdev, &hdev->dev);
+ rec = btintel_pcie_get_recovery(pdev);
if (!rec)
return;
- type = (code == 0x13) ? BTINTEL_PCIE_IOSF_PRR_PLDR
- : BTINTEL_PCIE_IOSF_PRR_FLR;
+ if (code == 0x13 && btintel_pcie_top_reset_supported(dev_data))
+ type = BTINTEL_PCIE_IOSF_TOP_RESET;
+ else
+ type = (code == 0x13) ? BTINTEL_PCIE_IOSF_PRR_PLDR
+ : BTINTEL_PCIE_IOSF_PRR_FLR;
bt_dev_err(hdev, "Encountered exception err:0x%x triggering: %s", code,
- type == BTINTEL_PCIE_IOSF_PRR_PLDR ? "PLDR" : "FLR");
+ btintel_pcie_resettype_to_str(type));
retry_window = ktime_get_boottime_seconds() - rec->last_error;
if (retry_window < BTINTEL_PCIE_RESET_WINDOW_SECS &&
@@ -3941,8 +4481,9 @@ static void btintel_pcie_hw_error(struct hci_dev *hdev, u8 code)
rec->last_error);
return;
}
- btintel_pcie_inc_recovery_count(pdev, &hdev->dev);
- btintel_pcie_request_reset(dev_data, type);
+
+ if (btintel_pcie_request_reset(dev_data, type))
+ btintel_pcie_inc_recovery_count(pdev);
}
static bool btintel_pcie_wakeup(struct hci_dev *hdev)
@@ -4045,10 +4586,15 @@ static int btintel_pcie_setup_hdev(struct btintel_pcie_data *data)
/* Publish hdev only after successful registration; the coredump
* worker bails on !data->hdev, so it never observes a half-set-up
- * device.
+ * device. A TOP reset MBOX2 event racing this publish is handled
+ * directly by the GP1 handler regardless of ordering (see
+ * btintel_pcie_handle_top_reset_mbox2()), so no synchronization is
+ * needed here.
*/
data->hdev = hdev;
+
data->dmp_hdr.driver_name = KBUILD_MODNAME;
+
return 0;
}
diff --git a/drivers/bluetooth/btintel_pcie.h b/drivers/bluetooth/btintel_pcie.h
index 3030b4e8b750..3911bfcd8f6f 100644
--- a/drivers/bluetooth/btintel_pcie.h
+++ b/drivers/bluetooth/btintel_pcie.h
@@ -10,6 +10,7 @@
#define BTINTEL_PCIE_CSR_BASE (0x000)
#define BTINTEL_PCIE_CSR_FUNC_CTRL_REG (BTINTEL_PCIE_CSR_BASE + 0x024)
#define BTINTEL_PCIE_CSR_HW_REV_REG (BTINTEL_PCIE_CSR_BASE + 0x028)
+#define BTINTEL_PCIE_CSR_FUNC_SCRATCH_CSR (BTINTEL_PCIE_CSR_BASE + 0x02C)
#define BTINTEL_PCIE_CSR_RF_ID_REG (BTINTEL_PCIE_CSR_BASE + 0x09C)
#define BTINTEL_PCIE_CSR_BOOT_STAGE_REG (BTINTEL_PCIE_CSR_BASE + 0x108)
#define BTINTEL_PCIE_CSR_IPC_CONTROL_REG (BTINTEL_PCIE_CSR_BASE + 0x10C)
@@ -24,6 +25,7 @@
#define BTINTEL_PCIE_CSR_MBOX_3_REG (BTINTEL_PCIE_CSR_BASE + 0x178)
#define BTINTEL_PCIE_CSR_MBOX_4_REG (BTINTEL_PCIE_CSR_BASE + 0x17C)
#define BTINTEL_PCIE_CSR_MBOX_STATUS_REG (BTINTEL_PCIE_CSR_BASE + 0x180)
+#define BTINTEL_PCIE_CSR_RESET_DEBUG_AD (BTINTEL_PCIE_CSR_BASE + 0x220)
#define BTINTEL_PCIE_PRPH_DEV_ADDR_REG (BTINTEL_PCIE_CSR_BASE + 0x440)
#define BTINTEL_PCIE_PRPH_DEV_RD_REG (BTINTEL_PCIE_CSR_BASE + 0x458)
#define BTINTEL_PCIE_CSR_HBUS_TARG_WRPTR (BTINTEL_PCIE_CSR_BASE + 0x460)
@@ -125,6 +127,13 @@
#define BTINTEL_PCIE_DEVICE_ID_NVL_Hx_SCP2 0xD346
#define BTINTEL_PCIE_DEVICE_ID_PTL_FMP2 0xE476
+#define BTINTEL_PCIE_SUBSYSTEM_ID_PEP 0x0012
+
+#define BTINTEL_PCIE_TOP_RESET_DBG_RESET_REG 0x0AAAFFFF
+#define BTINTEL_PCIE_CSR_FUNC_SCRATCH_TOP_SILENT_RESET BIT(9)
+#define BTINTEL_PCIE_SCU_CORE_IDLE_SET 0x0000000F
+#define BTINTEL_PCIE_SCU_CORE_IDLE_RESET 0x0000000E
+
/* Causes for the FH register interrupts */
enum msix_fh_int_causes {
BTINTEL_PCIE_MSIX_FH_INT_CAUSES_0 = BIT(0), /* cause 0 */
@@ -158,7 +167,23 @@ enum {
BTINTEL_PCIE_SETUP_DONE,
BTINTEL_PCIE_MAIL_BOX_INTR,
BTINTEL_PCIE_MBOX_PARSE_PENDING,
- BTINTEL_PCIE_MBOX_PARSE_READY
+ BTINTEL_PCIE_MBOX_PARSE_READY,
+ BTINTEL_PCIE_TOP_RESET,
+ BTINTEL_PCIE_MBOX_TOP_RESET,
+ BTINTEL_PCIE_TOP_RESET_MBOX1_PENDING,
+ /* Set on data->flags: a WiFi-follower PLDR request was rejected by
+ * btintel_pcie_request_reset() because setup had not completed yet;
+ * retried once BTINTEL_PCIE_SETUP_DONE is set.
+ */
+ BTINTEL_PCIE_TOP_RESET_WIFI_PLDR_PENDING,
+ /* Only BTINTEL_PCIE_REC_TOP_RESET_PENDING below lives on rec->flags
+ * (it must survive device_reprobe(), which frees 'data'); the rest
+ * of the TOP reset mbox negotiation state is scoped to a single
+ * btintel_pcie_do_top_reset() call and lives on data->flags so it
+ * never depends on the recovery struct's GFP_ATOMIC allocation.
+ */
+ BTINTEL_PCIE_REC_TOP_RESET_PENDING,
+ BTINTEL_PCIE_TOP_RESET_FAILED,
};
enum btintel_pcie_tlv_type {
@@ -188,14 +213,25 @@ enum msix_mbox_int_causes {
enum btintel_pcie_reset_type {
BTINTEL_PCIE_IOSF_PRR_FLR = 0,
BTINTEL_PCIE_IOSF_PRR_PLDR = 1,
+ BTINTEL_PCIE_IOSF_TOP_RESET = 2,
};
-enum btintel_pcie_mbox_msg {
+enum btintel_pcie_mbox1_msg {
BTINTEL_PCIE_NO_USE = 0,
- BTINTEL_PCIE_TOP_SILENT_RESET,
- BTINTEL_PCIE_SB_AUDIO_DEVICE_REPORT,
- BTINTEL_PCIE_BUILD_SPECIFIC_RESOURCES_MAPPING,
- BTINTEL_PCIE_LAST_MESSAGE = 4095
+ BTINTEL_PCIE_TOP_SILENT_RESET = 1,
+ BTINTEL_PCIE_SB_AUDIO_DEVICE_REPORT = 2,
+ BTINTEL_PCIE_BUILD_SPECIFIC_RESOURCES_MAPPING = 3,
+ BTINTEL_PCIE_CNVI_SCU_REG_MAIN_FSM_STICKY = 0xC08,
+ BTINTEL_PCIE_LAST_MESSAGE = 0xFFF,
+};
+
+enum btintel_pcie_mbox2_msg {
+ BTINTEL_PCIE_TOP_RESET_WIFI_REQUEST = 257, /* 0x101 */
+ BTINTEL_PCIE_TOP_RESET_WIFI_ACK = 513, /* 0x201 */
+ BTINTEL_PCIE_TOP_RESET_WIFI_NACK = 514, /* 0x202 */
+ BTINTEL_PCIE_TOP_RESET_WIFI_REQ_ROLE_SWITCH = 515, /* 0x203 */
+ BTINTEL_PCIE_TOP_RESET_WIFI_NO_RESPONSE = 771, /* 0x303 */
+ BTINTEL_PCIE_TOP_RESET_WIFI_RESPONSE_INVALID = 4095, /* 0xFFF */
};
#define BTINTEL_PCIE_MSIX_NON_AUTO_CLEAR_CAUSE BIT(7)
@@ -783,6 +819,16 @@ struct btintel_pcie_data {
wait_queue_head_t mbox_parse_wait_q;
/* Timestamp captured in GP1 handler when mbox interrupt is received */
ktime_t mbox_intr_ts;
+ wait_queue_head_t coredump_wait_q;
+ u32 top_reset_expires;
+ /* Absolute jiffies deadline for the second wait in
+ * btintel_pcie_do_top_reset(); top_reset_gen is bumped every
+ * time the GP1 handler updates the deadline so an in-progress
+ * wait_event_timeout() notices the change instead of timing
+ * out against a stale value (e.g. on WIFI_REQ_ROLE_SWITCH).
+ */
+ unsigned long top_reset_deadline;
+ unsigned int top_reset_gen;
};
static inline u32 btintel_pcie_rd_reg32(struct btintel_pcie_data *data,
--
2.43.0
^ permalink raw reply related [flat|nested] 3+ messages in thread