* [PATCH 1/2] Bluetooth: btintel: Add TOP silent reset hooks
@ 2026-10-14 11:47 Chandrashekar Devegowda
2026-10-14 11:47 ` [PATCH 2/2] Bluetooth: btintel_pcie: Add TOP silent reset support Chandrashekar Devegowda
2026-10-14 12:55 ` [1/2] Bluetooth: btintel: Add TOP silent reset hooks bluez.test.bot
0 siblings, 2 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 an INTEL_TOP_RESET flag so btintel_boot() can abort with
-ECANCELED and let the caller substitute its own reset sequence,
and cache boot_addr in struct btintel_data so that sequence can
replay btintel_send_intel_reset() without re-parsing firmware.
Preserve the flag across the reset in btintel_bootloader_setup_tlv().
Inert on its own; used by the follow-up btintel_pcie patch.
Signed-off-by: Chandrashekar Devegowda <chandrashekar.devegowda@intel.com>
---
drivers/bluetooth/btintel.c | 68 +++++++++++++++++++++++++++++++++----
drivers/bluetooth/btintel.h | 2 ++
2 files changed, 64 insertions(+), 6 deletions(-)
diff --git a/drivers/bluetooth/btintel.c b/drivers/bluetooth/btintel.c
index 0709fb3212f9..f48cda37ffa0 100644
--- a/drivers/bluetooth/btintel.c
+++ b/drivers/bluetooth/btintel.c
@@ -1238,6 +1238,17 @@ static bool btintel_firmware_version(struct hci_dev *hdev,
{
const u8 *fw_ptr;
+ /* Invalidate any previously cached boot_addr up front so that if
+ * this firmware image lacks CMD_WRITE_BOOT_PARAMS below, the TOP
+ * silent reset replay path in btintel_pcie does not reuse a stale
+ * address from an earlier firmware load.
+ */
+ if (hdev->bus == HCI_PCI) {
+ struct btintel_data *data = hci_get_priv(hdev);
+
+ data->boot_addr = 0;
+ }
+
fw_ptr = fw->data;
while (fw_ptr - fw->data < fw->size) {
@@ -1255,6 +1266,18 @@ static bool btintel_firmware_version(struct hci_dev *hdev,
*boot_addr = le32_to_cpu(params->boot_addr);
+ /* boot_addr is cached only for the PCIe TOP silent
+ * reset replay path. UART (hci_intel) devices have no
+ * struct btintel_data allocated as hci_dev private
+ * data, so hci_get_priv(hdev) would return memory past
+ * the end of hdev; skip the cache write there.
+ */
+ if (hdev->bus == HCI_PCI) {
+ struct btintel_data *data = hci_get_priv(hdev);
+
+ data->boot_addr = *boot_addr;
+ }
+
bt_dev_info(hdev, "Boot Address: 0x%x", *boot_addr);
bt_dev_info(hdev, "Firmware Version: %u-%u.%u",
@@ -2059,11 +2082,30 @@ static int btintel_boot_wait_d0(struct hci_dev *hdev, ktime_t calltime,
return 0;
}
+/* Returns true when a TOP silent reset is pending on this hdev and the
+ * caller must cancel whatever FW-download/boot step it was about to run
+ * instead of proceeding, so the reset dispatch in btintel_pcie_setup()
+ * is not bypassed.
+ */
+static bool btintel_top_reset_pending(struct hci_dev *hdev)
+{
+ if (hdev->bus == HCI_PCI && btintel_test_flag(hdev, INTEL_TOP_RESET)) {
+ bt_dev_info(hdev, "INTEL_TOP_RESET flag set, cancel boot");
+ return true;
+ }
+
+ return false;
+}
+
static int btintel_boot(struct hci_dev *hdev, u32 boot_addr)
{
ktime_t calltime;
int err;
+ /* TOP reset flag set, abort FW download before sending Intel soft reset */
+ if (btintel_top_reset_pending(hdev))
+ return -ECANCELED;
+
calltime = ktime_get();
btintel_set_flag(hdev, INTEL_BOOTING);
@@ -3543,12 +3585,13 @@ static int btintel_try_load_unlocker(struct hci_dev *hdev,
int btintel_bootloader_setup_tlv(struct hci_dev *hdev,
struct intel_version_tlv *ver)
{
- u32 boot_param;
- char ddcname[64];
- int err;
- struct intel_version_tlv new_ver;
struct btintel_data *data = hci_get_priv(hdev);
u8 hw_variant = INTEL_HW_VARIANT(ver->cnvi_bt);
+ struct intel_version_tlv new_ver;
+ bool backup = false;
+ char ddcname[64];
+ u32 boot_param;
+ int err, i;
bt_dev_dbg(hdev, "");
@@ -3562,9 +3605,15 @@ int btintel_bootloader_setup_tlv(struct hci_dev *hdev,
* same hdev instance if there is any error on firmware download.
* Need to clear stale bits of previous firmware download attempt.
*/
- for (int i = 0; i < __INTEL_NUM_FLAGS; i++)
+ if (btintel_test_flag(hdev, INTEL_TOP_RESET))
+ backup = true;
+
+ for (i = 0; i < __INTEL_NUM_FLAGS; i++)
btintel_clear_flag(hdev, i);
+ if (backup)
+ btintel_set_flag(hdev, INTEL_TOP_RESET);
+
btintel_set_flag(hdev, INTEL_BOOTLOADER);
data->unlocker = false;
@@ -3597,8 +3646,15 @@ int btintel_bootloader_setup_tlv(struct hci_dev *hdev,
return err;
/* check if controller is already having an operational firmware */
- if (ver->img_type == BTINTEL_IMG_OP)
+ if (ver->img_type == BTINTEL_IMG_OP) {
+ /* A pending TOP reset must still be dispatched by the
+ * caller even though there is no bootloader stage to run
+ * through btintel_boot() in this case.
+ */
+ if (btintel_top_reset_pending(hdev))
+ return -ECANCELED;
goto finish;
+ }
err = btintel_boot(hdev, boot_param);
if (err)
diff --git a/drivers/bluetooth/btintel.h b/drivers/bluetooth/btintel.h
index 9526acf65959..97fbe0ddf2d3 100644
--- a/drivers/bluetooth/btintel.h
+++ b/drivers/bluetooth/btintel.h
@@ -257,6 +257,7 @@ enum {
INTEL_ROM_LEGACY_NO_WBS_SUPPORT,
INTEL_ACPI_RESET_ACTIVE,
INTEL_WAIT_FOR_D0,
+ INTEL_TOP_RESET,
__INTEL_NUM_FLAGS,
};
@@ -265,6 +266,7 @@ struct btintel_data {
DECLARE_BITMAP(flags, __INTEL_NUM_FLAGS);
int (*acpi_reset_method)(struct hci_dev *hdev);
u32 cnvi_bt;
+ u32 boot_addr;
bool unlocker;
};
--
2.43.0
^ permalink raw reply related [flat|nested] 3+ messages in thread
* [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
* RE: [1/2] Bluetooth: btintel: Add TOP silent reset hooks
2026-10-14 11:47 [PATCH 1/2] Bluetooth: btintel: Add TOP silent reset hooks Chandrashekar Devegowda
2026-10-14 11:47 ` [PATCH 2/2] Bluetooth: btintel_pcie: Add TOP silent reset support Chandrashekar Devegowda
@ 2026-10-14 12:55 ` bluez.test.bot
1 sibling, 0 replies; 3+ messages in thread
From: bluez.test.bot @ 2026-10-14 12:55 UTC (permalink / raw)
To: linux-bluetooth, chandrashekar.devegowda
[-- Attachment #1: Type: text/plain, Size: 1019 bytes --]
This is automated email and please do not reply to this email!
Dear submitter,
Thank you for submitting the patches to the linux bluetooth mailing list.
This is a CI test results with your patch series:
PW Link:https://patchwork.kernel.org/series/1185516/
---Test result---
Test Summary:
CheckPatch PASS 2.53 seconds
VerifyFixes PASS 0.12 seconds
VerifySignedoff PASS 0.12 seconds
GitLint PASS 0.65 seconds
SubjectPrefix PASS 0.23 seconds
BuildKernel PASS 30.63 seconds
CheckAllWarning PASS 33.75 seconds
CheckSparse PASS 36.20 seconds
BuildKernel32 PASS 29.28 seconds
CheckKernelLLVM PASS 35.12 seconds
TestRunnerSetup PASS 700.92 seconds
IncrementalBuild PASS 29.79 seconds
https://github.com/bluez/bluetooth-next/pull/932
---
Regards,
Linux Bluetooth
^ permalink raw reply [flat|nested] 3+ messages in thread
end of thread, other threads:[~2026-10-14 12:55 UTC | newest]
Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-14 11:47 [PATCH 1/2] Bluetooth: btintel: Add TOP silent reset hooks Chandrashekar Devegowda
2026-10-14 11:47 ` [PATCH 2/2] Bluetooth: btintel_pcie: Add TOP silent reset support Chandrashekar Devegowda
2026-10-14 12:55 ` [1/2] Bluetooth: btintel: Add TOP silent reset hooks bluez.test.bot
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.