* [PATCH v1 2/4] Bluetooth: btintel_pcie: add MDBGC multi-buffer DBGC allocation
2026-08-28 14:27 [PATCH v1 1/4] Bluetooth: btintel_pcie: remove duplicate BTINTEL_PCIE_MAGIC_NUM definition Chandrashekar Devegowda
@ 2026-08-28 14:27 ` Chandrashekar Devegowda
2026-08-28 14:27 ` [PATCH v1 3/4] Bluetooth: btintel_pcie: log mailbox handler latency in msix_gp1_handler Chandrashekar Devegowda
` (6 subsequent siblings)
7 siblings, 0 replies; 15+ messages in thread
From: Chandrashekar Devegowda @ 2026-08-28 14:27 UTC (permalink / raw)
To: linux-bluetooth
Cc: ravishankar.srivatsa, chethan.tumkur.narayan,
Chandrashekar Devegowda
Newer Intel BT PCIe variants (Nova Lake SCP2 and PTL FMP2) require
three independent DRAM debug (DBGC) buffer pools instead of the single
pool used by the existing controllers.
Add multi-DBGC (MDBGC) support by factoring the per-pool buffer
allocation into a common helper and adding a setup path that allocates
three pools using the new fragment context format. The context
information is programmed with the MDBGC fragment when the device
reports an MDBGC-capable variant, otherwise the existing single-pool
DBGC path is used.
Assisted-by: Copilot:claude-opus-4.7 sparse
Signed-off-by: Chandrashekar Devegowda <chandrashekar.devegowda@intel.com>
---
drivers/bluetooth/btintel_pcie.c | 178 ++++++++++++++++++++++++++-----
drivers/bluetooth/btintel_pcie.h | 30 ++++++
2 files changed, 180 insertions(+), 28 deletions(-)
diff --git a/drivers/bluetooth/btintel_pcie.c b/drivers/bluetooth/btintel_pcie.c
index b90f87e8368d..0946364a63ba 100644
--- a/drivers/bluetooth/btintel_pcie.c
+++ b/drivers/bluetooth/btintel_pcie.c
@@ -134,6 +134,20 @@ struct btintel_pcie_dbgc_ctxt {
struct btintel_pcie_dbgc_ctxt_buf bufs[BTINTEL_PCIE_DBGC_BUFFER_COUNT];
};
+struct btintel_pcie_mdbgc_ctxt {
+ u32 magic_num;
+ u32 ver;
+ u32 buf1_index;
+ u32 buf1_count;
+ struct btintel_pcie_dbgc_ctxt_buf buf1[BTINTEL_PCIE_DBGC_BUFFER_COUNT];
+ u32 buf2_index;
+ u32 buf2_count;
+ struct btintel_pcie_dbgc_ctxt_buf buf2[BTINTEL_PCIE_DBGC_BUFFER_COUNT];
+ u32 buf3_index;
+ u32 buf3_count;
+ struct btintel_pcie_dbgc_ctxt_buf buf3[BTINTEL_PCIE_DBGC_BUFFER_COUNT];
+};
+
struct btintel_pcie_trigger_evt {
u8 type;
u8 len;
@@ -184,57 +198,159 @@ static inline bool btintel_pcie_dbg_to_wifi(struct btintel_pcie_data *data)
return data->dbg_path_cache != BTINTEL_PCIE_DRAM;
}
-/* This function initializes the memory for DBGC buffers and formats the
- * DBGC fragment which consists header info and DBGC buffer's LSB, MSB and
- * size as the payload
- */
-static int btintel_pcie_setup_dbgc(struct btintel_pcie_data *data)
+/* Helper function to allocate and setup a debug buffer group */
+static int btintel_pcie_alloc_dbgc_buf(struct btintel_pcie_data *data,
+ struct data_buf **buf,
+ dma_addr_t *p_addr,
+ void **v_addr,
+ struct btintel_pcie_dbgc_ctxt_buf *frag,
+ u32 buf_index,
+ u32 buf_count)
{
- struct btintel_pcie_dbgc_ctxt db_frag;
- struct data_buf *buf;
+ struct data_buf *b;
int i;
- data->dbgc.count = BTINTEL_PCIE_DBGC_BUFFER_COUNT;
- data->dbgc.bufs = devm_kcalloc(&data->pdev->dev, data->dbgc.count,
- sizeof(*buf), GFP_KERNEL);
- if (!data->dbgc.bufs)
+ *buf = devm_kcalloc(&data->pdev->dev, buf_count,
+ sizeof(**buf), GFP_KERNEL);
+ if (!*buf) {
+ BT_ERR("Failed to allocate dbgc buf: %u",
+ buf_index + 1);
return -ENOMEM;
+ }
- data->dbgc.buf_v_addr = dmam_alloc_coherent(&data->pdev->dev,
- data->dbgc.count *
- BTINTEL_PCIE_DBGC_BUFFER_SIZE,
- &data->dbgc.buf_p_addr,
- GFP_KERNEL | __GFP_NOWARN);
- if (!data->dbgc.buf_v_addr)
+ *v_addr = dmam_alloc_coherent(&data->pdev->dev,
+ buf_count *
+ BTINTEL_PCIE_DBGC_BUFFER_SIZE,
+ p_addr,
+ GFP_KERNEL | __GFP_NOWARN);
+ if (!*v_addr) {
+ BT_ERR("Failed to allocate dbgc buf: %u DMA",
+ buf_index + 1);
return -ENOMEM;
+ }
+
+ for (i = 0; i < buf_count; i++) {
+ b = &(*buf)[i];
+ b->data_p_addr = *p_addr +
+ i * BTINTEL_PCIE_DBGC_BUFFER_SIZE;
+ b->data = *v_addr +
+ i * BTINTEL_PCIE_DBGC_BUFFER_SIZE;
+ frag[i].buf_addr_lsb =
+ lower_32_bits(b->data_p_addr);
+ frag[i].buf_addr_msb =
+ upper_32_bits(b->data_p_addr);
+ frag[i].buf_size = BTINTEL_PCIE_DBGC_BUFFER_SIZE;
+ }
+
+ return 0;
+}
+
+/* This function initializes the memory for MDBGC buffers */
+static int btintel_pcie_setup_mdbgc(struct btintel_pcie_data *data)
+{
+ struct btintel_pcie_mdbgc_ctxt db_frag;
+ u32 frag_size = sizeof(db_frag);
+ void *frag_v_addr;
+ int err;
+
+ data->mdbgc.count = BTINTEL_PCIE_DBGC_BUFFER_COUNT;
+
+ frag_v_addr = dmam_alloc_coherent(&data->pdev->dev, frag_size,
+ &data->mdbgc.frag_p_addr,
+ GFP_KERNEL | __GFP_NOWARN);
+ if (!frag_v_addr)
+ return -ENOMEM;
+
+ data->mdbgc.frag_v_addr = frag_v_addr;
+ data->mdbgc.frag_size = frag_size;
+
+ memset(&db_frag, 0, sizeof(db_frag));
+ db_frag.magic_num = BTINTEL_PCIE_MAGIC_NUM;
+ db_frag.ver = BTINTEL_PCIE_MDBGC_FRAG_VERSION;
+
+ /* Allocate DBGC buffer 1 */
+ db_frag.buf1_index = BTINTEL_PCIE_MDBGC_ALLOCATIONID_1;
+ db_frag.buf1_count = data->mdbgc.count;
+ err = btintel_pcie_alloc_dbgc_buf(data,
+ &data->mdbgc.buf1,
+ &data->mdbgc.buf1_p_addr,
+ &data->mdbgc.buf1_v_addr,
+ db_frag.buf1, 0,
+ data->mdbgc.count);
+ if (err)
+ return err;
+
+ /* Allocate DBGC buffer 2 */
+ db_frag.buf2_index = BTINTEL_PCIE_MDBGC_ALLOCATIONID_2;
+ db_frag.buf2_count = data->mdbgc.count;
+ err = btintel_pcie_alloc_dbgc_buf(data,
+ &data->mdbgc.buf2,
+ &data->mdbgc.buf2_p_addr,
+ &data->mdbgc.buf2_v_addr,
+ db_frag.buf2, 1,
+ data->mdbgc.count);
+ if (err)
+ return err;
+
+ /* Allocate DBGC buffer 3 */
+ db_frag.buf3_index = BTINTEL_PCIE_MDBGC_ALLOCATIONID_3;
+ db_frag.buf3_count = data->mdbgc.count;
+ err = btintel_pcie_alloc_dbgc_buf(data,
+ &data->mdbgc.buf3,
+ &data->mdbgc.buf3_p_addr,
+ &data->mdbgc.buf3_v_addr,
+ db_frag.buf3, 2,
+ data->mdbgc.count);
+ if (err)
+ return err;
+
+ memcpy(data->mdbgc.frag_v_addr, &db_frag, sizeof(db_frag));
+ return 0;
+}
+
+/* This function initializes the memory for DBGC buffers */
+static int btintel_pcie_setup_dbgc(struct btintel_pcie_data *data)
+{
+ struct btintel_pcie_dbgc_ctxt db_frag;
+ int err;
+
+ data->dbgc.count = BTINTEL_PCIE_DBGC_BUFFER_COUNT;
data->dbgc.frag_v_addr = dmam_alloc_coherent(&data->pdev->dev,
- sizeof(struct btintel_pcie_dbgc_ctxt),
- &data->dbgc.frag_p_addr,
- GFP_KERNEL | __GFP_NOWARN);
+ sizeof(struct btintel_pcie_dbgc_ctxt),
+ &data->dbgc.frag_p_addr,
+ GFP_KERNEL | __GFP_NOWARN);
if (!data->dbgc.frag_v_addr)
return -ENOMEM;
data->dbgc.frag_size = sizeof(struct btintel_pcie_dbgc_ctxt);
+ memset(&db_frag, 0, sizeof(db_frag));
db_frag.magic_num = BTINTEL_PCIE_MAGIC_NUM;
db_frag.ver = BTINTEL_PCIE_DBGC_FRAG_VERSION;
db_frag.total_size = BTINTEL_PCIE_DBGC_FRAG_PAYLOAD_SIZE;
db_frag.num_buf = BTINTEL_PCIE_DBGC_FRAG_BUFFER_COUNT;
- for (i = 0; i < data->dbgc.count; i++) {
- buf = &data->dbgc.bufs[i];
- buf->data_p_addr = data->dbgc.buf_p_addr + i * BTINTEL_PCIE_DBGC_BUFFER_SIZE;
- buf->data = data->dbgc.buf_v_addr + i * BTINTEL_PCIE_DBGC_BUFFER_SIZE;
- db_frag.bufs[i].buf_addr_lsb = lower_32_bits(buf->data_p_addr);
- db_frag.bufs[i].buf_addr_msb = upper_32_bits(buf->data_p_addr);
- db_frag.bufs[i].buf_size = BTINTEL_PCIE_DBGC_BUFFER_SIZE;
- }
+ err = btintel_pcie_alloc_dbgc_buf(data,
+ &data->dbgc.bufs,
+ &data->dbgc.buf_p_addr,
+ &data->dbgc.buf_v_addr,
+ db_frag.bufs, 0,
+ data->dbgc.count);
+ if (err)
+ return err;
memcpy(data->dbgc.frag_v_addr, &db_frag, sizeof(db_frag));
return 0;
}
+static bool btintel_pcie_is_mdbgc_supported(struct btintel_pcie_data *data)
+{
+ return data->pdev->device == BTINTEL_PCIE_DEVICE_ID_NVL_S_SCP2 ||
+ data->pdev->device == BTINTEL_PCIE_DEVICE_ID_NVL_Hx_SCP2 ||
+ data->pdev->device == BTINTEL_PCIE_DEVICE_ID_PTL_FMP2;
+}
+
static inline void ipc_print_ia_ring(struct hci_dev *hdev, struct ia *ia,
u16 queue_num)
{
@@ -2247,10 +2363,14 @@ static void btintel_pcie_init_ci(struct btintel_pcie_data *data,
*/
ci->dbgc_addr = 0;
ci->dbgc_size = 0;
+ } else if (btintel_pcie_is_mdbgc_supported(data)) {
+ ci->dbgc_addr = data->mdbgc.frag_p_addr;
+ ci->dbgc_size = data->mdbgc.frag_size;
} else {
ci->dbgc_addr = data->dbgc.frag_p_addr;
ci->dbgc_size = data->dbgc.frag_size;
}
+
ci->dbg_preset = 0x00;
}
@@ -2483,6 +2603,8 @@ static int btintel_pcie_alloc(struct btintel_pcie_data *data)
* buffer allocation entirely.
*/
err = 0;
+ } else if (btintel_pcie_is_mdbgc_supported(data)) {
+ err = btintel_pcie_setup_mdbgc(data);
} else {
err = btintel_pcie_setup_dbgc(data);
}
diff --git a/drivers/bluetooth/btintel_pcie.h b/drivers/bluetooth/btintel_pcie.h
index 9baa214d9bbe..5c35e65d3e81 100644
--- a/drivers/bluetooth/btintel_pcie.h
+++ b/drivers/bluetooth/btintel_pcie.h
@@ -106,8 +106,19 @@
* Bits[2:3] DBGI O/P : 01 = WiFi DBGC
*/
#define BTINTEL_PCIE_DRAM 0x01
+#define BTINTEL_PCIE_FW_MON_MODE_DRAM 0x02
#define BTINTEL_PCIE_WIFI_DBGC 0x06
+#define BTINTEL_PCIE_MDBGC_FRAG_VERSION 2
+
+#define BTINTEL_PCIE_MDBGC_ALLOCATIONID_1 0
+#define BTINTEL_PCIE_MDBGC_ALLOCATIONID_2 1
+#define BTINTEL_PCIE_MDBGC_ALLOCATIONID_3 2
+
+#define BTINTEL_PCIE_DEVICE_ID_NVL_S_SCP2 0x6E74
+#define BTINTEL_PCIE_DEVICE_ID_NVL_Hx_SCP2 0xD346
+#define BTINTEL_PCIE_DEVICE_ID_PTL_FMP2 0xE476
+
/* Causes for the FH register interrupts */
enum msix_fh_int_causes {
BTINTEL_PCIE_MSIX_FH_INT_CAUSES_0 = BIT(0), /* cause 0 */
@@ -459,6 +470,24 @@ struct btintel_pcie_dbgc {
struct data_buf *bufs;
};
+struct btintel_pcie_mdbgc {
+ u32 count;
+
+ void *frag_v_addr;
+ dma_addr_t frag_p_addr;
+ u32 frag_size;
+
+ dma_addr_t buf1_p_addr;
+ void *buf1_v_addr;
+ dma_addr_t buf2_p_addr;
+ void *buf2_v_addr;
+ dma_addr_t buf3_p_addr;
+ void *buf3_v_addr;
+ struct data_buf *buf1;
+ struct data_buf *buf2;
+ struct data_buf *buf3;
+};
+
struct btintel_pcie_dump_mem_info {
u32 exception_dump_addr;
u32 exception_dump_len;
@@ -599,6 +628,7 @@ struct btintel_pcie_data {
u32 alive_intr_ctxt;
enum btintel_pcie_reset_type reset_type;
struct btintel_pcie_dbgc dbgc;
+ struct btintel_pcie_mdbgc mdbgc;
struct btintel_pcie_dump_header dmp_hdr;
u8 pm_sx_event;
u32 debug_evt_addr;
--
2.43.0
^ permalink raw reply related [flat|nested] 15+ messages in thread* [PATCH v1 3/4] Bluetooth: btintel_pcie: log mailbox handler latency in msix_gp1_handler
2026-08-28 14:27 [PATCH v1 1/4] Bluetooth: btintel_pcie: remove duplicate BTINTEL_PCIE_MAGIC_NUM definition Chandrashekar Devegowda
2026-08-28 14:27 ` [PATCH v1 2/4] Bluetooth: btintel_pcie: add MDBGC multi-buffer DBGC allocation Chandrashekar Devegowda
@ 2026-08-28 14:27 ` Chandrashekar Devegowda
2026-08-28 14:27 ` [PATCH v1 4/4] Bluetooth: btintel_pcie: unified decoder coredump format Chandrashekar Devegowda
` (5 subsequent siblings)
7 siblings, 0 replies; 15+ messages in thread
From: Chandrashekar Devegowda @ 2026-08-28 14:27 UTC (permalink / raw)
To: linux-bluetooth
Cc: ravishankar.srivatsa, chethan.tumkur.narayan,
Chandrashekar Devegowda
Add ktime-based measurement around btintel_pcie_msix_gp1_handler() so
the time taken to service the mailbox interrupt and acknowledge the FW
can be observed via bt_dev_dbg. Drop the informational log emitted on
every mailbox interrupt (it is noisy at bt_dev_info level and the same
timestamp is now available through the new debug print) and collapse a
couple of over-wrapped lines for readability.
No functional change beyond the log verbosity adjustment.
Assisted-by: Copilot:claude-opus-4.7 sparse
Signed-off-by: Chandrashekar Devegowda <chandrashekar.devegowda@intel.com>
---
drivers/bluetooth/btintel_pcie.c | 19 +++++++++++--------
1 file changed, 11 insertions(+), 8 deletions(-)
diff --git a/drivers/bluetooth/btintel_pcie.c b/drivers/bluetooth/btintel_pcie.c
index 0946364a63ba..6617ad157ca0 100644
--- a/drivers/bluetooth/btintel_pcie.c
+++ b/drivers/bluetooth/btintel_pcie.c
@@ -1331,6 +1331,10 @@ static void btintel_pcie_msix_gp1_handler(struct btintel_pcie_data *data)
{
bool target_access = false;
u32 addr = 0, size = 0;
+ ktime_t calltime, delta, rettime;
+ unsigned long long duration;
+
+ 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);
@@ -1338,8 +1342,7 @@ static void btintel_pcie_msix_gp1_handler(struct btintel_pcie_data *data)
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");
+ bt_dev_info(data->hdev, "mailbox for target access");
target_access = true;
}
}
@@ -1373,10 +1376,6 @@ static void btintel_pcie_msix_gp1_handler(struct btintel_pcie_data *data)
set_bit(BTINTEL_PCIE_MBOX_PARSE_PENDING, &data->flags);
data->mbox_intr_ts = ktime_get();
- bt_dev_info(data->hdev,
- "mbox interrupt received at %lld ns; queuing mbox_work",
- ktime_to_ns(data->mbox_intr_ts));
-
WRITE_ONCE(data->debug_table_addr, addr);
WRITE_ONCE(data->debug_table_size, size);
if (!queue_work(data->dump_workqueue,
@@ -1387,9 +1386,13 @@ static void btintel_pcie_msix_gp1_handler(struct btintel_pcie_data *data)
}
/* Mailbox is read, ack to FW */
- btintel_pcie_set_reg_bits(data,
- BTINTEL_PCIE_CSR_IPC_DOORBELL_VEC_REG,
+ 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
--
2.43.0
^ permalink raw reply related [flat|nested] 15+ messages in thread* [PATCH v1 4/4] Bluetooth: btintel_pcie: unified decoder coredump format
2026-08-28 14:27 [PATCH v1 1/4] Bluetooth: btintel_pcie: remove duplicate BTINTEL_PCIE_MAGIC_NUM definition Chandrashekar Devegowda
2026-08-28 14:27 ` [PATCH v1 2/4] Bluetooth: btintel_pcie: add MDBGC multi-buffer DBGC allocation Chandrashekar Devegowda
2026-08-28 14:27 ` [PATCH v1 3/4] Bluetooth: btintel_pcie: log mailbox handler latency in msix_gp1_handler Chandrashekar Devegowda
@ 2026-08-28 14:27 ` Chandrashekar Devegowda
2026-08-28 22:15 ` [v1,1/4] Bluetooth: btintel_pcie: remove duplicate BTINTEL_PCIE_MAGIC_NUM definition bluez.test.bot
` (4 subsequent siblings)
7 siblings, 0 replies; 15+ messages in thread
From: Chandrashekar Devegowda @ 2026-08-28 14:27 UTC (permalink / raw)
To: linux-bluetooth
Cc: ravishankar.srivatsa, chethan.tumkur.narayan,
Chandrashekar Devegowda
Replace the legacy TLV-based coredump with a unified INI-format dump
that is compatible with the iwlwifi decoder tool, so a single decoder
can parse coredumps across all Intel BT PCIe controller variants.
The dump is emitted through dev_coredumpsg() as a scatter-gather list
of structured regions built from the firmware-provided memory
addresses parsed from the mailbox TLVs:
- DRAM monitor buffers (single or multi-buffer per MDBGC support)
- SMEM monitor region
- Exception event buffer
- DCCM, SDS, SDS IOSF and ECL memory regions
- Dump info metadata (FW version, HW variant, trigger reason)
Regions are collected only when the firmware advertises a valid
address and size. Only the regions actually collected have their
corresponding bit set in the dump-info regions_mask, so the decoder
knows which region IDs to expect.
The dump-info metadata is populated from the mailbox-advertised CNVi
and CNVr registers using the following bit-field encoding, matching
the iwlwifi decoder expectations:
HwType = CNVi bits 11:0
HwStep = CNVi bits 27:24
RfIdType = CNVr bits 11:0
RfIdDash = CNVr bits 23:20
RfIdStep = CNVr bits 27:24
RfIdFlavor = CNVr bits 31:28
Two new extractor macros INTEL_CNVX_TOP_DASH() and
INTEL_CNVX_TOP_FLAVOR() are added to btintel.h alongside the existing
INTEL_CNVX_TOP_TYPE() and INTEL_CNVX_TOP_STEP() helpers, so the same
shared bit definitions are reused for the HwType/HwStep and RfId*
fields.
Serialization of the dump path relies on existing infrastructure:
the ordered dump_workqueue guarantees that any companion event
reader (fwtrigger_work / hwexp_work) runs before coredump_work so
dmp_hdr trigger metadata is fully populated when dump_info consumes
it, and the BTINTEL_PCIE_COREDUMP_INPROGRESS bit acts as a
single-writer guard against re-entrant triggers. Teardown drains all
three works via disable_work_sync() before freeing the driver data.
The unified format has no consumer for the legacy debug-trigger
event type / event id fields, so drop the now-dead writes to
dmp_hdr.event_type / event_id and remove those two members from
struct btintel_pcie_dump_header. The values are still logged from
the firmware event via bt_dev_dbg for debugging.
Signed-off-by: Chandrashekar Devegowda <chandrashekar.devegowda@intel.com>
---
drivers/bluetooth/btintel.h | 2 +
drivers/bluetooth/btintel_pcie.c | 837 ++++++++++++++++++++++++-------
drivers/bluetooth/btintel_pcie.h | 147 +++++-
3 files changed, 812 insertions(+), 174 deletions(-)
diff --git a/drivers/bluetooth/btintel.h b/drivers/bluetooth/btintel.h
index ef232820c31b..c00f793d9dc8 100644
--- a/drivers/bluetooth/btintel.h
+++ b/drivers/bluetooth/btintel.h
@@ -225,7 +225,9 @@ struct btintel_sar_rev2 {
#define INTEL_HW_PLATFORM(cnvx_bt) ((u8)(((cnvx_bt) & 0x0000ff00) >> 8))
#define INTEL_HW_VARIANT(cnvx_bt) ((u8)(((cnvx_bt) & 0x003f0000) >> 16))
#define INTEL_CNVX_TOP_TYPE(cnvx_top) ((cnvx_top) & 0x00000fff)
+#define INTEL_CNVX_TOP_DASH(cnvx_top) (((cnvx_top) & 0x00f00000) >> 20)
#define INTEL_CNVX_TOP_STEP(cnvx_top) (((cnvx_top) & 0x0f000000) >> 24)
+#define INTEL_CNVX_TOP_FLAVOR(cnvx_top) (((cnvx_top) & 0xf0000000) >> 28)
#define INTEL_CNVX_TOP_PACK_SWAB(t, s) __swab16(((__u16)(((t) << 4) | (s))))
enum {
diff --git a/drivers/bluetooth/btintel_pcie.c b/drivers/bluetooth/btintel_pcie.c
index 6617ad157ca0..f38d7c488809 100644
--- a/drivers/bluetooth/btintel_pcie.c
+++ b/drivers/bluetooth/btintel_pcie.c
@@ -19,6 +19,7 @@
#include <linux/unaligned.h>
#include <linux/devcoredump.h>
+#include <linux/scatterlist.h>
#include <net/bluetooth/bluetooth.h>
#include <net/bluetooth/hci_core.h>
@@ -198,7 +199,15 @@ static inline bool btintel_pcie_dbg_to_wifi(struct btintel_pcie_data *data)
return data->dbg_path_cache != BTINTEL_PCIE_DRAM;
}
-/* Helper function to allocate and setup a debug buffer group */
+/* Helper function to allocate and setup a debug buffer group
+ * @data: driver data structure
+ * @buf: pointer to data_buf array pointer
+ * @p_addr: pointer to physical DMA address
+ * @v_addr: pointer to virtual address
+ * @frag: pointer to fragment buffer array
+ * @buf_index: buffer index (for error messages)
+ * @buf_count: number of buffers to allocate
+ */
static int btintel_pcie_alloc_dbgc_buf(struct btintel_pcie_data *data,
struct data_buf **buf,
dma_addr_t *p_addr,
@@ -213,39 +222,36 @@ static int btintel_pcie_alloc_dbgc_buf(struct btintel_pcie_data *data,
*buf = devm_kcalloc(&data->pdev->dev, buf_count,
sizeof(**buf), GFP_KERNEL);
if (!*buf) {
- BT_ERR("Failed to allocate dbgc buf: %u",
- buf_index + 1);
+ BT_ERR("Failed to allocate dbgc buf: %u", buf_index + 1);
return -ENOMEM;
}
*v_addr = dmam_alloc_coherent(&data->pdev->dev,
buf_count *
- BTINTEL_PCIE_DBGC_BUFFER_SIZE,
- p_addr,
- GFP_KERNEL | __GFP_NOWARN);
+ BTINTEL_PCIE_DBGC_BUFFER_SIZE,
+ p_addr,
+ GFP_KERNEL | __GFP_NOWARN);
if (!*v_addr) {
- BT_ERR("Failed to allocate dbgc buf: %u DMA",
- buf_index + 1);
+ BT_ERR("Failed to allocate dbgc buf: %u DMA", buf_index + 1);
return -ENOMEM;
}
for (i = 0; i < buf_count; i++) {
b = &(*buf)[i];
- b->data_p_addr = *p_addr +
- i * BTINTEL_PCIE_DBGC_BUFFER_SIZE;
- b->data = *v_addr +
- i * BTINTEL_PCIE_DBGC_BUFFER_SIZE;
- frag[i].buf_addr_lsb =
- lower_32_bits(b->data_p_addr);
- frag[i].buf_addr_msb =
- upper_32_bits(b->data_p_addr);
+ b->data_p_addr = *p_addr + i * BTINTEL_PCIE_DBGC_BUFFER_SIZE;
+ b->data = *v_addr + i * BTINTEL_PCIE_DBGC_BUFFER_SIZE;
+ frag[i].buf_addr_lsb = lower_32_bits(b->data_p_addr);
+ frag[i].buf_addr_msb = upper_32_bits(b->data_p_addr);
frag[i].buf_size = BTINTEL_PCIE_DBGC_BUFFER_SIZE;
}
return 0;
}
-/* This function initializes the memory for MDBGC buffers */
+/* This function initializes the memory for MDBGC buffers and formats the
+ * DBGC fragment which consists header info and DBGC buffer's LSB, MSB and
+ * size as the payload
+ */
static int btintel_pcie_setup_mdbgc(struct btintel_pcie_data *data)
{
struct btintel_pcie_mdbgc_ctxt db_frag;
@@ -255,15 +261,19 @@ static int btintel_pcie_setup_mdbgc(struct btintel_pcie_data *data)
data->mdbgc.count = BTINTEL_PCIE_DBGC_BUFFER_COUNT;
+ /* Allocate fragment context structure */
frag_v_addr = dmam_alloc_coherent(&data->pdev->dev, frag_size,
&data->mdbgc.frag_p_addr,
GFP_KERNEL | __GFP_NOWARN);
- if (!frag_v_addr)
+ if (!frag_v_addr) {
+ BT_ERR("Failed to allocate mdbgc context");
return -ENOMEM;
+ }
data->mdbgc.frag_v_addr = frag_v_addr;
data->mdbgc.frag_size = frag_size;
+ /* Initialize fragment header */
memset(&db_frag, 0, sizeof(db_frag));
db_frag.magic_num = BTINTEL_PCIE_MAGIC_NUM;
db_frag.ver = BTINTEL_PCIE_MDBGC_FRAG_VERSION;
@@ -275,7 +285,8 @@ static int btintel_pcie_setup_mdbgc(struct btintel_pcie_data *data)
&data->mdbgc.buf1,
&data->mdbgc.buf1_p_addr,
&data->mdbgc.buf1_v_addr,
- db_frag.buf1, 0,
+ db_frag.buf1,
+ 0,
data->mdbgc.count);
if (err)
return err;
@@ -287,7 +298,8 @@ static int btintel_pcie_setup_mdbgc(struct btintel_pcie_data *data)
&data->mdbgc.buf2,
&data->mdbgc.buf2_p_addr,
&data->mdbgc.buf2_v_addr,
- db_frag.buf2, 1,
+ db_frag.buf2,
+ 1,
data->mdbgc.count);
if (err)
return err;
@@ -299,58 +311,64 @@ static int btintel_pcie_setup_mdbgc(struct btintel_pcie_data *data)
&data->mdbgc.buf3,
&data->mdbgc.buf3_p_addr,
&data->mdbgc.buf3_v_addr,
- db_frag.buf3, 2,
+ db_frag.buf3,
+ 2,
data->mdbgc.count);
if (err)
return err;
+ /* Copy fragment to DMA coherent memory */
memcpy(data->mdbgc.frag_v_addr, &db_frag, sizeof(db_frag));
return 0;
}
-/* This function initializes the memory for DBGC buffers */
+/* This function initializes the memory for DBGC buffers and formats the
+ * DBGC fragment which consists header info and DBGC buffer's LSB, MSB and
+ * size as the payload
+ */
static int btintel_pcie_setup_dbgc(struct btintel_pcie_data *data)
{
struct btintel_pcie_dbgc_ctxt db_frag;
+ u32 frag_size = sizeof(db_frag);
int err;
data->dbgc.count = BTINTEL_PCIE_DBGC_BUFFER_COUNT;
+ /* Allocate fragment context structure */
data->dbgc.frag_v_addr = dmam_alloc_coherent(&data->pdev->dev,
- sizeof(struct btintel_pcie_dbgc_ctxt),
- &data->dbgc.frag_p_addr,
- GFP_KERNEL | __GFP_NOWARN);
- if (!data->dbgc.frag_v_addr)
+ frag_size,
+ &data->dbgc.frag_p_addr,
+ GFP_KERNEL | __GFP_NOWARN);
+ if (!data->dbgc.frag_v_addr) {
+ BT_ERR("Failed to allocate dbgc context");
return -ENOMEM;
+ }
data->dbgc.frag_size = sizeof(struct btintel_pcie_dbgc_ctxt);
+ /* Initialize fragment header */
memset(&db_frag, 0, sizeof(db_frag));
db_frag.magic_num = BTINTEL_PCIE_MAGIC_NUM;
db_frag.ver = BTINTEL_PCIE_DBGC_FRAG_VERSION;
db_frag.total_size = BTINTEL_PCIE_DBGC_FRAG_PAYLOAD_SIZE;
db_frag.num_buf = BTINTEL_PCIE_DBGC_FRAG_BUFFER_COUNT;
+ /* Allocate DBGC buffers */
err = btintel_pcie_alloc_dbgc_buf(data,
&data->dbgc.bufs,
&data->dbgc.buf_p_addr,
&data->dbgc.buf_v_addr,
- db_frag.bufs, 0,
+ db_frag.bufs,
+ 0,
data->dbgc.count);
if (err)
return err;
+ /* Copy fragment to DMA coherent memory */
memcpy(data->dbgc.frag_v_addr, &db_frag, sizeof(db_frag));
return 0;
}
-static bool btintel_pcie_is_mdbgc_supported(struct btintel_pcie_data *data)
-{
- return data->pdev->device == BTINTEL_PCIE_DEVICE_ID_NVL_S_SCP2 ||
- data->pdev->device == BTINTEL_PCIE_DEVICE_ID_NVL_Hx_SCP2 ||
- data->pdev->device == BTINTEL_PCIE_DEVICE_ID_PTL_FMP2;
-}
-
static inline void ipc_print_ia_ring(struct hci_dev *hdev, struct ia *ia,
u16 queue_num)
{
@@ -753,52 +771,461 @@ static void btintel_pcie_release_mac_access(struct btintel_pcie_data *data)
}
}
-static void *btintel_pcie_copy_tlv(void *dest, enum btintel_pcie_tlv_type type,
- void *data, size_t size)
+static struct scatterlist *btintel_pcie_alloc_sgtable(ssize_t size)
+{
+ int nents, i;
+ struct page *page;
+ struct scatterlist *sg, *result;
+
+ if (size <= 0)
+ return NULL;
+
+ nents = DIV_ROUND_UP(size, PAGE_SIZE);
+ result = kcalloc(nents, sizeof(*result), GFP_KERNEL);
+ if (!result)
+ return NULL;
+
+ sg_init_table(result, nents);
+ sg = result;
+
+ for (i = 0; size > 0; i++) {
+ ssize_t bytes = min_t(ssize_t, size, PAGE_SIZE);
+
+ page = alloc_page(GFP_KERNEL | __GFP_ZERO);
+ if (!page)
+ goto err_free;
+
+ sg_set_page(sg, page, bytes, 0);
+ sg = sg_next(sg);
+ size -= bytes;
+ }
+ return result;
+
+err_free:
+ for (sg = result; sg; sg = sg_next(sg)) {
+ page = sg_page(sg);
+ if (page)
+ __free_page(page);
+ }
+ kfree(result);
+ return NULL;
+}
+
+static struct btintel_pcie_dump_entry *
+btintel_pcie_dump_entry_alloc(u32 data_size)
{
- struct intel_tlv *tlv;
+ struct btintel_pcie_dump_entry *entry;
- tlv = dest;
- tlv->type = type;
- tlv->len = size;
- memcpy(tlv->val, data, tlv->len);
- return dest + sizeof(*tlv) + size;
+ entry = vzalloc(sizeof(*entry) + data_size);
+ if (!entry)
+ return NULL;
+
+ entry->size = data_size;
+ INIT_LIST_HEAD(&entry->list);
+ return entry;
}
-static int btintel_pcie_read_dram_buffers(struct btintel_pcie_data *data)
+static void btintel_pcie_dump_list_free(struct list_head *list)
{
- u32 offset, prev_size, wr_ptr_status, dump_size, data_len;
- u32 status_reg, wrap_reg;
- struct btintel_pcie_dbgc *dbgc = &data->dbgc;
+ struct btintel_pcie_dump_entry *entry, *tmp;
+
+ list_for_each_entry_safe(entry, tmp, list, list) {
+ list_del(&entry->list);
+ vfree(entry);
+ }
+}
+
+static u32 btintel_pcie_dump_list_total_size(struct list_head *list)
+{
+ struct btintel_pcie_dump_entry *entry;
+ u32 total = 0;
+
+ list_for_each_entry(entry, list, list)
+ total += entry->size;
+
+ return total;
+}
+
+static int btintel_pcie_dump_dram(struct list_head *list,
+ u8 count, struct data_buf *bufs,
+ u8 buf_idx, u32 write_ptr, u32 wrap_ctr,
+ u32 region_id, const char *name)
+{
+ struct btintel_pcie_dump_entry *entry;
+ struct btintel_pcie_ini_dump_data *dump_data;
+ struct btintel_pcie_ini_monitor_dump *mon_dump;
+ struct btintel_pcie_dump_range *range;
+ u32 mon_hdr_size, ranges_size, payload_size, total_size;
+ int i;
+
+ mon_hdr_size = sizeof(*mon_dump);
+ ranges_size = count * (sizeof(*range) + BTINTEL_PCIE_DBGC_BUFFER_SIZE);
+ payload_size = mon_hdr_size + ranges_size;
+ total_size = sizeof(*dump_data) + payload_size;
+
+ entry = btintel_pcie_dump_entry_alloc(total_size);
+ if (!entry)
+ return -ENOMEM;
+
+ dump_data = (void *)entry->data;
+ dump_data->type = BTINTEL_PCIE_INI_REGION_DRAM_BUFFER;
+ dump_data->sub_type = 0;
+ dump_data->sub_type_ver = 0;
+ dump_data->reserved = 0;
+ dump_data->len = cpu_to_le32(payload_size);
+
+ mon_dump = (void *)dump_data->data;
+ mon_dump->header.version = cpu_to_le32(BTINTEL_PCIE_INI_DUMP_VER);
+ mon_dump->header.region_id = cpu_to_le32(region_id);
+ mon_dump->header.num_of_ranges = cpu_to_le32(count);
+ mon_dump->header.name_len = cpu_to_le32(BTINTEL_PCIE_INI_MAX_NAME);
+ memset(mon_dump->header.name, 0, BTINTEL_PCIE_INI_MAX_NAME);
+ strscpy(mon_dump->header.name, name, BTINTEL_PCIE_INI_MAX_NAME);
+
+ mon_dump->write_ptr = cpu_to_le32(write_ptr);
+ mon_dump->cycle_cnt = cpu_to_le32(wrap_ctr);
+ mon_dump->cur_frag = cpu_to_le32(buf_idx);
+
+ range = (void *)mon_dump->data;
+ for (i = 0; i < count; i++) {
+ range->range_data_size =
+ cpu_to_le32(BTINTEL_PCIE_DBGC_BUFFER_SIZE);
+ range->dram_base_addr = cpu_to_le64(bufs[i].data_p_addr);
+ memcpy(range->data, bufs[i].data,
+ BTINTEL_PCIE_DBGC_BUFFER_SIZE);
+ range = (void *)range->data + BTINTEL_PCIE_DBGC_BUFFER_SIZE;
+ }
+
+ list_add_tail(&entry->list, list);
+ return 0;
+}
+
+static int btintel_pcie_dump_target_region(struct btintel_pcie_data *data,
+ struct list_head *list,
+ u32 region_id, const char *name,
+ u32 addr_start, u32 size)
+{
+ struct btintel_pcie_dump_entry *entry;
+ struct btintel_pcie_ini_dump_data *dump_data;
+ struct btintel_pcie_ini_dump_header *hdr;
+ struct btintel_pcie_dump_range *range;
+ u32 payload_size, total_size, target_mem_offset, tempdata;
+ u8 *dest;
+ int i;
+
+ if (!size) {
+ bt_dev_warn(data->hdev, "Skipping empty dump region: %s", name);
+ return 0;
+ }
+
+ if (!addr_start) {
+ bt_dev_warn(data->hdev,
+ "Skipping dump region with zero address: %s",
+ name);
+ return 0;
+ }
+
+ if (size > BTINTEL_PCIE_REGION_MAX_SIZE) {
+ bt_dev_warn(data->hdev,
+ "Skipping dump region %s: size %u exceeds max %u",
+ name, size, BTINTEL_PCIE_REGION_MAX_SIZE);
+ return 0;
+ }
+
+ if (addr_start > U32_MAX - size) {
+ bt_dev_warn(data->hdev,
+ "Skipping dump region %s: addr_start 0x%08x + size %u would overflow",
+ name, addr_start, size);
+ return 0;
+ }
+
+ /* Align to 4 bytes - target access reads 32-bit words */
+ size = round_down(size, sizeof(u32));
+
+ bt_dev_dbg(data->hdev,
+ "Target access: region=%s start=0x%08x size=%u",
+ name, addr_start, size);
+
+ payload_size = sizeof(*hdr) + sizeof(*range) + size;
+ total_size = sizeof(*dump_data) + payload_size;
+ entry = btintel_pcie_dump_entry_alloc(total_size);
+ if (!entry)
+ return -ENOMEM;
+
+ dump_data = (void *)entry->data;
+ dump_data->type = BTINTEL_PCIE_INI_REGION_DEVICE_MEMORY;
+ dump_data->sub_type = 0;
+ dump_data->sub_type_ver = 0;
+ dump_data->reserved = 0;
+ dump_data->len = cpu_to_le32(payload_size);
+
+ hdr = (void *)dump_data->data;
+ hdr->version = cpu_to_le32(BTINTEL_PCIE_INI_DUMP_VER);
+ hdr->region_id = cpu_to_le32(region_id);
+ hdr->num_of_ranges = cpu_to_le32(1);
+ hdr->name_len = cpu_to_le32(BTINTEL_PCIE_INI_MAX_NAME);
+ memset(hdr->name, 0, BTINTEL_PCIE_INI_MAX_NAME);
+ strscpy(hdr->name, name, BTINTEL_PCIE_INI_MAX_NAME);
+
+ range = (void *)(hdr + 1);
+ range->range_data_size = cpu_to_le32(size);
+ range->internal_base_addr = cpu_to_le32(addr_start);
+
+ dest = (u8 *)range->data;
+ target_mem_offset = size / sizeof(u32);
+ for (i = 0; i < target_mem_offset; i++) {
+ u32 offset = BTINTEL_PCIE_TARGET_ACCESS_FRAG_OFFSET * i;
+
+ tempdata = btintel_pcie_rd_dev_mem(data,
+ addr_start + offset);
+ memcpy(dest, &tempdata, sizeof(tempdata));
+ dest += sizeof(tempdata);
+ }
+
+ list_add_tail(&entry->list, list);
+ return 0;
+}
+
+static int btintel_pcie_dump_smem_monitor_region(struct btintel_pcie_data *data,
+ struct list_head *list,
+ u32 region_id,
+ const char *name,
+ u32 addr_start, u32 size)
+{
+ struct btintel_pcie_dump_entry *entry;
+ struct btintel_pcie_ini_dump_data *dump_data;
+ struct btintel_pcie_ini_monitor_dump *mon;
+ struct btintel_pcie_dump_range *range;
+ u32 payload_size, total_size, target_mem_offset, tempdata;
+ u8 *dest;
+ int i;
+
+ if (!size || !addr_start) {
+ bt_dev_err(data->hdev, "Skipping smem dump: size = %u addr = %8.8x",
+ size, addr_start);
+ return -EINVAL;
+ }
+
+ payload_size = sizeof(*mon) + sizeof(*range) + size;
+ total_size = sizeof(*dump_data) + payload_size;
+ entry = btintel_pcie_dump_entry_alloc(total_size);
+ if (!entry)
+ return -ENOMEM;
+
+ dump_data = (void *)entry->data;
+ dump_data->type = BTINTEL_PCIE_INI_REGION_INTERNAL_BUFFER;
+ dump_data->sub_type = 0;
+ dump_data->sub_type_ver = 0;
+ dump_data->reserved = 0;
+ dump_data->len = cpu_to_le32(payload_size);
+
+ mon = (void *)dump_data->data;
+ mon->header.version = cpu_to_le32(BTINTEL_PCIE_INI_DUMP_VER);
+ mon->header.region_id = cpu_to_le32(region_id);
+ mon->header.num_of_ranges = cpu_to_le32(1);
+ mon->header.name_len = cpu_to_le32(BTINTEL_PCIE_INI_MAX_NAME);
+ memset(mon->header.name, 0, BTINTEL_PCIE_INI_MAX_NAME);
+ strscpy(mon->header.name, name, BTINTEL_PCIE_INI_MAX_NAME);
+
+ mon->write_ptr = cpu_to_le32(0);
+ mon->cycle_cnt = cpu_to_le32(0);
+ mon->cur_frag = cpu_to_le32(0);
+
+ range = (void *)mon->data;
+ range->range_data_size = cpu_to_le32(size);
+ range->internal_base_addr = cpu_to_le32(addr_start);
+
+ dest = (u8 *)range->data;
+ target_mem_offset = size / sizeof(u32);
+ for (i = 0; i < target_mem_offset; i++) {
+ u32 offset = BTINTEL_PCIE_TARGET_ACCESS_FRAG_OFFSET * i;
+
+ tempdata = btintel_pcie_rd_dev_mem(data,
+ addr_start + offset);
+ memcpy(dest, &tempdata, sizeof(tempdata));
+ dest += sizeof(tempdata);
+ }
+
+ list_add_tail(&entry->list, list);
+ return 0;
+}
+
+static int btintel_pcie_dump_info(struct btintel_pcie_data *data,
+ struct list_head *list,
+ u64 regions_mask)
+{
+ struct btintel_pcie_dump_entry *entry;
+ struct btintel_pcie_error_dump_data *tlv;
+ struct btintel_pcie_ini_dump_info *dump;
+ u32 size = sizeof(*tlv) + sizeof(*dump);
+ char build_tag[64];
+
+ entry = btintel_pcie_dump_entry_alloc(size);
+ if (!entry)
+ return -ENOMEM;
+
+ tlv = (void *)entry->data;
+ tlv->type = cpu_to_le32(BTINTEL_PCIE_INI_DUMP_INFO_TYPE);
+ tlv->len = cpu_to_le32(sizeof(*dump));
+
+ dump = (void *)tlv->data;
+ memset(dump, 0, sizeof(*dump));
+
+ dump->version = cpu_to_le32(BTINTEL_PCIE_INI_DUMP_VER);
+ dump->trigger_reason = cpu_to_le32(data->dmp_hdr.trigger_reason);
+
+ if (data->dmp_hdr.trigger_reason ==
+ BTINTEL_PCIE_TRIGGER_REASON_FW_ASSERT)
+ dump->time_point =
+ cpu_to_le32(BTINTEL_PCIE_TIME_POINT_FW_ASSERT);
+ else if (data->dmp_hdr.trigger_reason ==
+ BTINTEL_PCIE_TRIGGER_REASON_USER_TRIGGER)
+ dump->time_point =
+ cpu_to_le32(BTINTEL_PCIE_TIME_POINT_USER_TRIGGER);
+
+ dump->hw_type = cpu_to_le32(INTEL_CNVX_TOP_TYPE(data->dmp_hdr.cnvi_top));
+ dump->hw_step = cpu_to_le32(INTEL_CNVX_TOP_STEP(data->dmp_hdr.cnvi_top));
+ bt_dev_dbg(data->hdev, "hw_type=0x%x hw_step=0x%x (cnvi_top=0x%x)",
+ le32_to_cpu(dump->hw_type), le32_to_cpu(dump->hw_step),
+ data->dmp_hdr.cnvi_top);
+
+ dump->ver_type = cpu_to_le32(data->dmp_hdr.cnvi_bt);
+ dump->ver_subtype = cpu_to_le32(data->dmp_hdr.fw_sha);
+ dump->rf_id_type = cpu_to_le32(INTEL_CNVX_TOP_TYPE(data->dmp_hdr.cnvr_top));
+ dump->rf_id_dash = cpu_to_le32(INTEL_CNVX_TOP_DASH(data->dmp_hdr.cnvr_top));
+ dump->rf_id_step = cpu_to_le32(INTEL_CNVX_TOP_STEP(data->dmp_hdr.cnvr_top));
+ dump->rf_id_flavor = cpu_to_le32(INTEL_CNVX_TOP_FLAVOR(data->dmp_hdr.cnvr_top));
+ bt_dev_dbg(data->hdev,
+ "rf_id_type=0x%x rf_id_dash=0x%x rf_id_step=0x%x rf_id_flavor=0x%x (cnvr_top=0x%x)",
+ le32_to_cpu(dump->rf_id_type),
+ le32_to_cpu(dump->rf_id_dash),
+ le32_to_cpu(dump->rf_id_step),
+ le32_to_cpu(dump->rf_id_flavor),
+ data->dmp_hdr.cnvr_top);
+ dump->lmac_major = cpu_to_le32(0);
+ dump->lmac_minor = cpu_to_le32(0);
+ dump->umac_major = cpu_to_le32(0);
+ dump->umac_minor = cpu_to_le32(0);
+ dump->fw_mon_mode = cpu_to_le32(BTINTEL_PCIE_FW_MON_MODE_DRAM);
+
+ dump->regions_mask = cpu_to_le64(regions_mask);
+
+ bt_dev_dbg(data->hdev, "ExpectedRegionIDs regions_mask=0x%016llx",
+ le64_to_cpu(dump->regions_mask));
+
+ snprintf(build_tag, sizeof(build_tag), "%08X", data->dmp_hdr.fw_sha);
+
+ dump->build_tag_len = cpu_to_le32(strlen(build_tag));
+ memcpy(dump->build_tag, build_tag, min(strlen(build_tag),
+ sizeof(dump->build_tag)));
+
+ dump->num_of_cfg_names = cpu_to_le32(0);
+
+ bt_dev_dbg(data->hdev,
+ "HwType=0x%08x HwStep=0x%08x RfIdType=0x%08x RfIdDash=0x%08x RfIdStep=0x%08x RfIdFlavor=0x%08x",
+ le32_to_cpu(dump->hw_type), le32_to_cpu(dump->hw_step),
+ le32_to_cpu(dump->rf_id_type),
+ le32_to_cpu(dump->rf_id_dash),
+ le32_to_cpu(dump->rf_id_step),
+ le32_to_cpu(dump->rf_id_flavor));
+ bt_dev_dbg(data->hdev, "VerType=0x%08x VerSubType=0x%08x",
+ le32_to_cpu(dump->ver_type), le32_to_cpu(dump->ver_subtype));
+ bt_dev_dbg(data->hdev,
+ "LmacMajor=0x%08x LmacMinor=0x%08x UmacMajor=0x%08x UmacMinor=0x%08x",
+ le32_to_cpu(dump->lmac_major), le32_to_cpu(dump->lmac_minor),
+ le32_to_cpu(dump->umac_major),
+ le32_to_cpu(dump->umac_minor));
+ bt_dev_dbg(data->hdev, "TriggerReason=0x%04x MonMode=%u BuildTag=%.64s",
+ le32_to_cpu(dump->trigger_reason),
+ le32_to_cpu(dump->fw_mon_mode),
+ dump->build_tag);
+
+ list_add(&entry->list, list);
+
+ return 0;
+}
+
+static bool btintel_pcie_is_mdbgc_supported(struct btintel_pcie_data *data)
+{
+ return data->pdev->device == BTINTEL_PCIE_DEVICE_ID_NVL_S_SCP2 ||
+ data->pdev->device == BTINTEL_PCIE_DEVICE_ID_NVL_Hx_SCP2 ||
+ data->pdev->device == BTINTEL_PCIE_DEVICE_ID_PTL_FMP2;
+}
+
+static void btintel_pcie_dump_mem_range(struct btintel_pcie_data *data,
+ struct list_head *list, u32 region_id,
+ const char *name, u32 addr_start,
+ u32 addr_end, u64 *regions_mask)
+{
+ u32 region_size;
+
+ if (!addr_start || !addr_end || addr_end < addr_start)
+ return;
+
+ if (addr_end > U32_MAX - 0x04) {
+ bt_dev_warn(data->hdev,
+ "Skipping dump region %s: addr_end 0x%08x would overflow",
+ name, addr_end);
+ return;
+ }
+
+ region_size = (addr_end + 0x04) - addr_start;
+ if (region_size > BTINTEL_PCIE_REGION_MAX_SIZE) {
+ bt_dev_warn(data->hdev,
+ "Skipping dump region %s: size %u exceeds max %u",
+ name, region_size,
+ BTINTEL_PCIE_REGION_MAX_SIZE);
+ return;
+ }
+
+ if (!btintel_pcie_dump_target_region(data, list, region_id, name,
+ addr_start, region_size))
+ *regions_mask |= BIT_ULL(region_id);
+}
+
+static int btintel_pcie_read_debug_regions(struct btintel_pcie_data *data)
+{
+ struct btintel_pcie_dbgc *dbgc = NULL;
+ struct btintel_pcie_mdbgc *mdbgc = NULL;
struct hci_dev *hdev = data->hdev;
- u8 *pdata, *p, buf_idx, hw_variant;
- struct intel_tlv *tlv;
- struct timespec64 now;
- struct tm tm_now;
- char fw_build[128];
- char ts[128];
- char vendor[64];
- char driver[64];
+ struct btintel_pcie_dump_entry *entry;
+ struct btintel_pcie_dump_file_hdr *file_hdr;
+ struct scatterlist *sg_dump_data;
+ u32 offset, prev_size, wr_ptr_status;
+ u32 status_reg, wrap_reg;
+ u32 exception_dump_len;
+ u32 exc_addr;
+ u64 regions_mask = 0;
+ u8 buf_idx, hw_variant;
+ u32 smem_rd_addr = 0, smem_rd_size = 0;
+ u32 file_len;
+ u8 count;
+ int ret;
+ LIST_HEAD(dump_list);
if (!IS_ENABLED(CONFIG_DEV_COREDUMP))
return -EOPNOTSUPP;
+ if (btintel_pcie_is_mdbgc_supported(data)) {
+ mdbgc = &data->mdbgc;
+ count = mdbgc->count;
+ } else {
+ dbgc = &data->dbgc;
+ count = dbgc->count;
+ }
hw_variant = INTEL_HW_VARIANT(data->cnvi);
- switch (hw_variant) {
- case BTINTEL_HWID_BZRI:
- case BTINTEL_HWID_BZRIW:
+
+ if (hw_variant == BTINTEL_HWID_BZRI ||
+ hw_variant == BTINTEL_HWID_BZRIW) {
status_reg = BTINTEL_PCIE_DBGC_CUR_DBGBUFF_STATUS;
wrap_reg = BTINTEL_PCIE_DBGC_DBGBUFF_WRAP_ARND;
- break;
- case BTINTEL_HWID_SCP:
- case BTINTEL_HWID_SCP2:
- case BTINTEL_HWID_SCP2F:
+ } else if (hw_variant >= BTINTEL_HWID_SCP) {
status_reg = BTINTEL_PCIE_DBGC_CUR_DBGBUFF_STATUS_SCP;
wrap_reg = BTINTEL_PCIE_DBGC_DBGBUFF_WRAP_ARND_SCP;
- break;
- default:
- bt_dev_err(hdev, "Unsupported Intel hardware variant (0x%2.2x)",
+ } else {
+ bt_dev_err(hdev,
+ "Unsupported Intel hardware variant (0x%2.2x)",
hw_variant);
return -EINVAL;
}
@@ -809,7 +1236,7 @@ static int btintel_pcie_read_dram_buffers(struct btintel_pcie_data *data)
offset = wr_ptr_status & BTINTEL_PCIE_DBG_OFFSET_BIT_MASK;
buf_idx = BTINTEL_PCIE_DBGC_DBG_BUF_IDX(wr_ptr_status);
- if (buf_idx > dbgc->count) {
+ if (buf_idx > count) {
bt_dev_warn(hdev, "Buffer index is invalid");
return -EINVAL;
}
@@ -820,102 +1247,175 @@ static int btintel_pcie_read_dram_buffers(struct btintel_pcie_data *data)
else
return -EINVAL;
- strscpy(vendor, "Vendor: Intel\n");
- snprintf(driver, sizeof(driver), "Driver: %s\n",
- data->dmp_hdr.driver_name);
-
- ktime_get_real_ts64(&now);
- time64_to_tm(now.tv_sec, 0, &tm_now);
- snprintf(ts, sizeof(ts), "Dump Time: %02d-%02d-%04ld %02d:%02d:%02d",
- tm_now.tm_mday, tm_now.tm_mon + 1, tm_now.tm_year + 1900,
- tm_now.tm_hour, tm_now.tm_min, tm_now.tm_sec);
-
- snprintf(fw_build, sizeof(fw_build),
- "Firmware Timestamp: Year %u WW %02u buildtype %u build %u",
- 2000 + (data->dmp_hdr.fw_timestamp >> 8),
- data->dmp_hdr.fw_timestamp & 0xff, data->dmp_hdr.fw_build_type,
- data->dmp_hdr.fw_build_num);
-
- data_len = sizeof(*tlv) + sizeof(data->dmp_hdr.cnvi_bt) +
- sizeof(*tlv) + sizeof(data->dmp_hdr.write_ptr) +
- sizeof(*tlv) + sizeof(data->dmp_hdr.wrap_ctr) +
- sizeof(*tlv) + sizeof(data->dmp_hdr.trigger_reason) +
- sizeof(*tlv) + sizeof(data->dmp_hdr.fw_sha) +
- sizeof(*tlv) + sizeof(data->dmp_hdr.cnvr_top) +
- sizeof(*tlv) + sizeof(data->dmp_hdr.cnvi_top) +
- sizeof(*tlv) + strlen(ts) +
- sizeof(*tlv) + strlen(fw_build) +
- sizeof(*tlv) + strlen(vendor) +
- sizeof(*tlv) + strlen(driver);
-
- if (data->dmp_hdr.event_type && data->dmp_hdr.event_id) {
- data_len += sizeof(*tlv) + sizeof(data->dmp_hdr.event_type);
- data_len += sizeof(*tlv) + sizeof(data->dmp_hdr.event_id);
- }
+ bt_dev_dbg(hdev, "wr_ptr_status=0x%08x buf_idx=%u offset=0x%06x",
+ wr_ptr_status, buf_idx, offset);
+ bt_dev_dbg(hdev, "write_ptr=0x%08x wrap_ctr=0x%08x",
+ data->dmp_hdr.write_ptr, data->dmp_hdr.wrap_ctr);
- /*
- * sizeof(u32) - signature
- * sizeof(data_len) - to store tlv data size
- * data_len - TLV data
- */
- dump_size = sizeof(u32) + sizeof(data_len) + data_len;
+ smem_rd_addr = data->dump_info.smem_addr_start;
+ smem_rd_size = 0;
+ if (data->dump_info.smem_addr_end < smem_rd_addr ||
+ data->dump_info.smem_addr_end > U32_MAX - 0x04) {
+ bt_dev_err(hdev,
+ "Invalid smem region: start=0x%08x end=0x%08x",
+ smem_rd_addr, data->dump_info.smem_addr_end);
+ } else {
+ smem_rd_size = (data->dump_info.smem_addr_end + 0x04) -
+ smem_rd_addr;
- /* Add debug buffers data length to dump size */
- dump_size += BTINTEL_PCIE_DBGC_BUFFER_SIZE * dbgc->count;
+ bt_dev_dbg(hdev,
+ "smem_region: smem_start_addr=0x%08x smem_end_addr=0x%08x smem_rd_size=%u",
+ smem_rd_addr, data->dump_info.smem_addr_end,
+ smem_rd_size);
+
+ if (smem_rd_size == 0 ||
+ smem_rd_size > BTINTEL_PCIE_SMEM_MAX_SIZE) {
+ bt_dev_err(hdev,
+ "Invalid smem region: smem_rd_addr 0x%08x size %u (max %u)",
+ smem_rd_addr, smem_rd_size,
+ BTINTEL_PCIE_SMEM_MAX_SIZE);
+ smem_rd_size = 0;
+ }
+ }
- pdata = vmalloc(dump_size);
- if (!pdata)
+ if (btintel_pcie_is_mdbgc_supported(data)) {
+ ret = btintel_pcie_dump_dram(&dump_list, count,
+ mdbgc->buf1, 0,
+ data->dmp_hdr.write_ptr,
+ data->dmp_hdr.wrap_ctr,
+ BTINTEL_PCIE_INI_ID_DRAM_MONITOR1,
+ "monitor");
+ if (!ret)
+ regions_mask |=
+ BIT_ULL(BTINTEL_PCIE_INI_ID_DRAM_MONITOR1);
+ else
+ bt_dev_warn(hdev, "Failed to dump DRAM buf1: %d", ret);
+
+ ret = btintel_pcie_dump_dram(&dump_list, count,
+ mdbgc->buf2, 0,
+ data->dmp_hdr.write_ptr,
+ data->dmp_hdr.wrap_ctr,
+ BTINTEL_PCIE_INI_ID_DRAM_MONITOR2,
+ "monitor2");
+ if (!ret)
+ regions_mask |=
+ BIT_ULL(BTINTEL_PCIE_INI_ID_DRAM_MONITOR2);
+ else
+ bt_dev_warn(hdev, "Failed to dump DRAM buf2: %d", ret);
+
+ ret = btintel_pcie_dump_dram(&dump_list, count,
+ mdbgc->buf3, 0,
+ data->dmp_hdr.write_ptr,
+ data->dmp_hdr.wrap_ctr,
+ BTINTEL_PCIE_INI_ID_DRAM_MONITOR3,
+ "monitor3");
+ if (!ret)
+ regions_mask |=
+ BIT_ULL(BTINTEL_PCIE_INI_ID_DRAM_MONITOR3);
+ else
+ bt_dev_warn(hdev, "Failed to dump DRAM buf3: %d", ret);
+ } else {
+ ret = btintel_pcie_dump_dram(&dump_list, count,
+ dbgc->bufs, 0,
+ data->dmp_hdr.write_ptr,
+ data->dmp_hdr.wrap_ctr,
+ BTINTEL_PCIE_INI_ID_DRAM_MONITOR1,
+ "monitor");
+ if (!ret)
+ regions_mask |=
+ BIT_ULL(BTINTEL_PCIE_INI_ID_DRAM_MONITOR1);
+ else
+ bt_dev_warn(hdev,
+ "Failed to dump DRAM region: %d", ret);
+ }
+
+ if (smem_rd_size &&
+ !btintel_pcie_dump_smem_monitor_region(data, &dump_list,
+ BTINTEL_PCIE_INI_ID_SMEM,
+ "monitor_smem",
+ smem_rd_addr,
+ smem_rd_size))
+ regions_mask |= BIT_ULL(BTINTEL_PCIE_INI_ID_SMEM);
+
+ exc_addr = data->dump_info.exception_dump_addr;
+ exception_dump_len = data->dump_info.exception_dump_len;
+ ret = btintel_pcie_dump_target_region(data, &dump_list,
+ BTINTEL_PCIE_INI_ID_EXCEPTION_EVT,
+ "EXCEPTION_EVT_BUFFER", exc_addr,
+ exception_dump_len);
+ if (!ret && exception_dump_len)
+ regions_mask |= BIT_ULL(BTINTEL_PCIE_INI_ID_EXCEPTION_EVT);
+
+ btintel_pcie_dump_mem_range(data, &dump_list,
+ BTINTEL_PCIE_INI_ID_DCCM, "DCCM",
+ data->dump_info.dccm_addr_start,
+ data->dump_info.dccm_addr_end,
+ ®ions_mask);
+
+ btintel_pcie_dump_mem_range(data, &dump_list,
+ BTINTEL_PCIE_INI_ID_SDS, "SDS",
+ data->dump_info.sds_start_addr_start,
+ data->dump_info.sds_start_addr_end,
+ ®ions_mask);
+
+ btintel_pcie_dump_mem_range(data, &dump_list,
+ BTINTEL_PCIE_INI_ID_SDS_IOSF, "SDS_IOSF",
+ data->dump_info.sds_iosf_data_addr_start,
+ data->dump_info.sds_iosf_data_addr_end,
+ ®ions_mask);
+
+ btintel_pcie_dump_mem_range(data, &dump_list,
+ BTINTEL_PCIE_INI_ID_ECL, "ECL_REGION",
+ data->dump_info.ecl_addr_start,
+ data->dump_info.ecl_addr_end,
+ ®ions_mask);
+
+ ret = btintel_pcie_dump_info(data, &dump_list, regions_mask);
+ if (ret) {
+ btintel_pcie_dump_list_free(&dump_list);
+ return ret;
+ }
+
+ file_len = sizeof(*file_hdr) +
+ btintel_pcie_dump_list_total_size(&dump_list);
+ entry = btintel_pcie_dump_entry_alloc(sizeof(*file_hdr));
+ if (!entry) {
+ btintel_pcie_dump_list_free(&dump_list);
return -ENOMEM;
- p = pdata;
-
- *(u32 *)p = BTINTEL_PCIE_MAGIC_NUM;
- p += sizeof(u32);
-
- *(u32 *)p = data_len;
- p += sizeof(u32);
-
-
- p = btintel_pcie_copy_tlv(p, BTINTEL_VENDOR, vendor, strlen(vendor));
- p = btintel_pcie_copy_tlv(p, BTINTEL_DRIVER, driver, strlen(driver));
- p = btintel_pcie_copy_tlv(p, BTINTEL_DUMP_TIME, ts, strlen(ts));
- p = btintel_pcie_copy_tlv(p, BTINTEL_FW_BUILD, fw_build,
- strlen(fw_build));
- p = btintel_pcie_copy_tlv(p, BTINTEL_CNVI_BT, &data->dmp_hdr.cnvi_bt,
- sizeof(data->dmp_hdr.cnvi_bt));
- p = btintel_pcie_copy_tlv(p, BTINTEL_WRITE_PTR, &data->dmp_hdr.write_ptr,
- sizeof(data->dmp_hdr.write_ptr));
- p = btintel_pcie_copy_tlv(p, BTINTEL_WRAP_CTR, &data->dmp_hdr.wrap_ctr,
- sizeof(data->dmp_hdr.wrap_ctr));
- p = btintel_pcie_copy_tlv(p, BTINTEL_TRIGGER_REASON, &data->dmp_hdr.trigger_reason,
- sizeof(data->dmp_hdr.trigger_reason));
- p = btintel_pcie_copy_tlv(p, BTINTEL_FW_SHA, &data->dmp_hdr.fw_sha,
- sizeof(data->dmp_hdr.fw_sha));
- p = btintel_pcie_copy_tlv(p, BTINTEL_CNVR_TOP, &data->dmp_hdr.cnvr_top,
- sizeof(data->dmp_hdr.cnvr_top));
- p = btintel_pcie_copy_tlv(p, BTINTEL_CNVI_TOP, &data->dmp_hdr.cnvi_top,
- sizeof(data->dmp_hdr.cnvi_top));
-
- if (data->dmp_hdr.event_type && data->dmp_hdr.event_id) {
- p = btintel_pcie_copy_tlv(p, BTINTEL_EVENT_TYPE,
- &data->dmp_hdr.event_type,
- sizeof(data->dmp_hdr.event_type));
- p = btintel_pcie_copy_tlv(p, BTINTEL_EVENT_ID,
- &data->dmp_hdr.event_id,
- sizeof(data->dmp_hdr.event_id));
- data->dmp_hdr.event_type = 0;
- data->dmp_hdr.event_id = 0;
- }
-
- memcpy(p, dbgc->bufs[0].data, dbgc->count * BTINTEL_PCIE_DBGC_BUFFER_SIZE);
- dev_coredumpv(&hdev->dev, pdata, dump_size, GFP_KERNEL);
- return 0;
+ }
+
+ file_hdr = (void *)entry->data;
+ file_hdr->barker = cpu_to_le32(BTINTEL_PCIE_INI_ERROR_DUMP_BARKER);
+ file_hdr->file_len = cpu_to_le32(file_len);
+ list_add(&entry->list, &dump_list);
+
+ sg_dump_data = btintel_pcie_alloc_sgtable(file_len);
+ if (sg_dump_data) {
+ int sg_entries = sg_nents(sg_dump_data);
+ u32 offs = 0;
+
+ list_for_each_entry(entry, &dump_list, list) {
+ sg_pcopy_from_buffer(sg_dump_data, sg_entries,
+ entry->data, entry->size, offs);
+ offs += entry->size;
+ }
+
+ bt_dev_dbg(hdev, "triggering dev_coredumpsg()");
+ dev_coredumpsg(&hdev->dev, sg_dump_data, file_len, GFP_KERNEL);
+ } else {
+ bt_dev_err(hdev, "Failed to allocate scatter-gather table for coredump");
+ ret = -ENOMEM;
+ }
+
+ btintel_pcie_dump_list_free(&dump_list);
+ return ret;
}
static void btintel_pcie_dump_traces(struct hci_dev *hdev)
{
struct btintel_pcie_data *data = hci_get_drvdata(hdev);
- int ret = 0;
+ int ret;
ret = btintel_pcie_get_mac_access(data);
if (ret) {
@@ -923,7 +1423,7 @@ static void btintel_pcie_dump_traces(struct hci_dev *hdev)
return;
}
- ret = btintel_pcie_read_dram_buffers(data);
+ ret = btintel_pcie_read_debug_regions(data);
btintel_pcie_release_mac_access(data);
@@ -1168,19 +1668,19 @@ static int btintel_parse_mbox_tlv(struct btintel_pcie_data *data)
ptr = buffer;
remaining = buffer_len;
- /* Parse TLV structures: type(1) + length(2) + value */
+ /* Parse TLV structures: 1 byte type + 2 bytes length +
+ * variable value
+ */
while (remaining >= sizeof(struct mbox_tlv)) {
u16 tlv_len;
u32 tlv_total;
tlv = (struct mbox_tlv *)ptr;
tlv_len = le16_to_cpu(tlv->len);
- tlv_total = sizeof(tlv->type) +
- sizeof(tlv->len) + tlv_len;
+ tlv_total = sizeof(tlv->type) + sizeof(tlv->len) + tlv_len;
if (tlv_total > remaining) {
- bt_dev_err(data->hdev,
- "TLV parse error: type=%u, len=%u",
+ bt_dev_err(data->hdev, "TLV parse error: not enough data for TLV value (type=%u, len=%u)",
tlv->type, tlv_len);
break;
}
@@ -1189,7 +1689,7 @@ static int btintel_parse_mbox_tlv(struct btintel_pcie_data *data)
case BTINTEL_PCIE_TLV_TYPE_EXCEPTION_DUMP_ADDRESS:
if (tlv_len < 8) {
bt_dev_err(data->hdev,
- "TLV %s too short: %u",
+ "TLV %s too short: len=%u (need 8)",
btintel_pcie_tlv_str(tlv->type),
tlv_len);
break;
@@ -1202,7 +1702,7 @@ static int btintel_parse_mbox_tlv(struct btintel_pcie_data *data)
case BTINTEL_PCIE_TLV_TYPE_DCCM_MEM_ADDRESS:
if (tlv_len < 8) {
bt_dev_err(data->hdev,
- "TLV %s too short: %u",
+ "TLV %s too short: len=%u (need 8)",
btintel_pcie_tlv_str(tlv->type),
tlv_len);
break;
@@ -1221,8 +1721,7 @@ static int btintel_parse_mbox_tlv(struct btintel_pcie_data *data)
bt_dev_dbg(data->hdev, "SDS TLV: skipped, hw_variant not yet known");
break;
}
- if (tlv_len == 16 &&
- hw_variant > BTINTEL_HWID_BZRI) {
+ if (tlv_len == 16 && hw_variant > BTINTEL_HWID_BZRI) {
data->dump_info.sds_start_addr_start =
get_unaligned_le32(&tlv->val[0]);
data->dump_info.sds_start_addr_end =
@@ -1255,7 +1754,7 @@ static int btintel_parse_mbox_tlv(struct btintel_pcie_data *data)
case BTINTEL_PCIE_TLV_TYPE_ECL_MEM_ADDRESS:
if (tlv_len < 8) {
bt_dev_err(data->hdev,
- "TLV %s too short: %u",
+ "TLV %s too short: len=%u (need 8)",
btintel_pcie_tlv_str(tlv->type),
tlv_len);
break;
@@ -1268,7 +1767,7 @@ static int btintel_parse_mbox_tlv(struct btintel_pcie_data *data)
case BTINTEL_PCIE_TLV_TYPE_SMEM_ADDRESS:
if (tlv_len < 8) {
bt_dev_err(data->hdev,
- "TLV %s too short: %u",
+ "TLV %s too short: len=%u (need 8)",
btintel_pcie_tlv_str(tlv->type),
tlv_len);
break;
@@ -1279,8 +1778,7 @@ static int btintel_parse_mbox_tlv(struct btintel_pcie_data *data)
get_unaligned_le32(&tlv->val[4]);
break;
default:
- bt_dev_dbg(data->hdev,
- "Unknown TLV type: %u length: %u",
+ bt_dev_dbg(data->hdev, "Unknown TLV type: %u length: %u",
tlv->type, tlv_len);
break;
}
@@ -1871,11 +2369,8 @@ static int btintel_pcie_dump_fwtrigger_event(struct btintel_pcie_data *data)
goto exit_on_error;
evt = (void *)buf;
- data->dmp_hdr.event_type = evt->event_type;
- data->dmp_hdr.event_id = le16_to_cpu(evt->event_id);
-
bt_dev_dbg(data->hdev, "event type: 0x%2.2x event id: 0x%4.4x len: %u",
- data->dmp_hdr.event_type, data->dmp_hdr.event_id, len);
+ evt->event_type, le16_to_cpu(evt->event_id), len);
skb = bt_skb_alloc(len, GFP_KERNEL);
if (!skb) {
@@ -1901,8 +2396,8 @@ static int btintel_pcie_dump_fwtrigger_event(struct btintel_pcie_data *data)
*
* Always queue this AFTER any companion event-reader work (hwexp /
* fwtrigger) so that, on the ordered @dump_workqueue, the event reader
- * runs first and populates dmp_hdr.event_type / event_id before
- * dump_traces consumes them.
+ * runs first and the trigger metadata is populated before dump_traces
+ * consumes it.
*/
static bool btintel_pcie_queue_coredump(struct btintel_pcie_data *data,
u16 trigger_reason)
diff --git a/drivers/bluetooth/btintel_pcie.h b/drivers/bluetooth/btintel_pcie.h
index 5c35e65d3e81..7feb13f7e7d0 100644
--- a/drivers/bluetooth/btintel_pcie.h
+++ b/drivers/bluetooth/btintel_pcie.h
@@ -75,10 +75,14 @@
#define BTINTEL_PCIE_DBGC_CUR_DBGBUFF_STATUS_SCP (BTINTEL_PCIE_DBGC_BASE_ADDR_SCP + 0x1C)
#define BTINTEL_PCIE_DBGC_DBGBUFF_WRAP_ARND_SCP (BTINTEL_PCIE_DBGC_BASE_ADDR_SCP + 0x2C)
+#define BTINTEL_PCIE_SMEM_MAX_SIZE (16 * 1024)
+#define BTINTEL_PCIE_REGION_MAX_SIZE (16 * 1024 * 1024)
#define BTINTEL_PCIE_DBG_IDX_BIT_MASK 0x0F
#define BTINTEL_PCIE_DBGC_DBG_BUF_IDX(data) (((data) >> 24) & BTINTEL_PCIE_DBG_IDX_BIT_MASK)
#define BTINTEL_PCIE_DBG_OFFSET_BIT_MASK 0xFFFFFF
+#define BTINTEL_PCIE_TARGET_ACCESS_FRAG_OFFSET 4
+
/* The DRAM buffer count, each buffer size, and
* fragment buffer size
*/
@@ -242,7 +246,7 @@ enum {
#define BTINTEL_PCIE_TLV_TYPE_DCCM_MEM_ADDRESS 0x05
#define BTINTEL_PCIE_TLV_TYPE_SDS_MEM_ADDRESS 0x06
#define BTINTEL_PCIE_TLV_TYPE_ECL_MEM_ADDRESS 0x07
-#define BTINTEL_PCIE_TLV_TYPE_SMEM_ADDRESS 0x08
+#define BTINTEL_PCIE_TLV_TYPE_SMEM_ADDRESS 0x08
/*
* Struct for Context Information (v2)
@@ -527,10 +531,147 @@ struct btintel_pcie_dump_header {
u32 wrap_ctr;
u16 trigger_reason;
int state;
- u8 event_type;
- u16 event_id;
};
+/* Per-fragment range descriptor for dump regions.
+ * Binary-compatible with iwl_fw_ini_error_dump_range.
+ */
+struct btintel_pcie_dump_range {
+ __le32 range_data_size;
+ union {
+ __le32 internal_base_addr;
+ __le64 dram_base_addr;
+ __le32 page_num;
+ };
+ __le32 data[];
+} __packed;
+
+/*
+ * INI region types for ini_dump_data.type field.
+ * The unified decoder dispatches parsing logic based on these values.
+ */
+#define BTINTEL_PCIE_INI_REGION_INTERNAL_BUFFER 2
+#define BTINTEL_PCIE_INI_REGION_DRAM_BUFFER 3
+#define BTINTEL_PCIE_INI_REGION_DEVICE_MEMORY 9
+
+/* INI region IDs - used in dump header region_id field and regions_mask */
+#define BTINTEL_PCIE_INI_ID_EXCEPTION_EVT 7
+#define BTINTEL_PCIE_INI_ID_SMEM 15
+#define BTINTEL_PCIE_INI_ID_DCCM 39
+#define BTINTEL_PCIE_INI_ID_SDS 40
+#define BTINTEL_PCIE_INI_ID_SDS_IOSF 41
+#define BTINTEL_PCIE_INI_ID_ECL 42
+#define BTINTEL_PCIE_INI_ID_DRAM_MONITOR3 61
+#define BTINTEL_PCIE_INI_ID_DRAM_MONITOR2 62
+#define BTINTEL_PCIE_INI_ID_DRAM_MONITOR1 63
+
+/*
+ * INI (Intel INI debug infrastructure) style dump data TLV - wraps each dump
+ * region. INI is the iwlwifi firmware debug format used by the unified decoder.
+ * Compatible with iwl_fw_ini_error_dump_data.
+ */
+struct btintel_pcie_ini_dump_data {
+ u8 type;
+ u8 sub_type;
+ u8 sub_type_ver;
+ u8 reserved;
+ __le32 len;
+ u8 data[];
+} __packed;
+
+/*
+ * INI-style region dump header.
+ * Compatible with iwl_fw_ini_error_dump_header.
+ */
+#define BTINTEL_PCIE_INI_MAX_NAME 32
+#define BTINTEL_PCIE_INI_DUMP_VER 1
+
+struct btintel_pcie_ini_dump_header {
+ __le32 version;
+ __le32 region_id;
+ __le32 num_of_ranges;
+ __le32 name_len;
+ u8 name[BTINTEL_PCIE_INI_MAX_NAME];
+};
+
+/*
+ * INI-style monitor dump - region header + monitor state.
+ * Compatible with iwl_fw_ini_monitor_dump.
+ */
+struct btintel_pcie_ini_monitor_dump {
+ struct btintel_pcie_ini_dump_header header;
+ __le32 write_ptr;
+ __le32 cycle_cnt;
+ __le32 cur_frag;
+ u8 data[];
+} __packed;
+
+/* Linked list entry for modular dump collection */
+struct btintel_pcie_dump_entry {
+ struct list_head list;
+ u32 size;
+ u8 data[];
+};
+
+/* File-level header for coredump output.
+ * Compatible with iwl_fw_ini_dump_file_hdr.
+ * Uses IWL_FW_INI_ERROR_DUMP_BARKER (0x14789633) for decoder compatibility.
+ */
+#define BTINTEL_PCIE_INI_ERROR_DUMP_BARKER 0x14789633
+
+struct btintel_pcie_dump_file_hdr {
+ __le32 barker;
+ __le32 file_len;
+} __packed;
+
+/*
+ * Legacy-style dump data wrapper for dump info TLV.
+ * Compatible with iwl_fw_error_dump_data.
+ * Used only for the dump info entry (type=BTINTEL_PCIE_INI_DUMP_INFO_TYPE).
+ */
+struct btintel_pcie_error_dump_data {
+ __le32 type;
+ __le32 len;
+ u8 data[];
+} __packed;
+
+/* Use bit 31 as dump info type, matching IWL_INI_DUMP_INFO_TYPE */
+#define BTINTEL_PCIE_INI_DUMP_INFO_TYPE BIT(31)
+
+/* Time point values matching iwl_fw_ini_time_point for fwdump parser */
+#define BTINTEL_PCIE_TIME_POINT_FW_ASSERT 4
+#define BTINTEL_PCIE_TIME_POINT_USER_TRIGGER 9
+
+/*
+ * Dump info struct - single TLV containing all metadata.
+ * Compatible with iwl_fw_ini_dump_info.
+ * Packs all device/firmware info that the decoder needs.
+ */
+struct btintel_pcie_ini_dump_info {
+ __le32 version;
+ __le32 time_point;
+ __le32 trigger_reason;
+ __le32 external_cfg_state;
+ __le32 ver_type;
+ __le32 ver_subtype;
+ __le32 hw_step;
+ __le32 hw_type;
+ __le32 rf_id_flavor;
+ __le32 rf_id_dash;
+ __le32 rf_id_step;
+ __le32 rf_id_type;
+ __le32 lmac_major;
+ __le32 lmac_minor;
+ __le32 umac_major;
+ __le32 umac_minor;
+ __le32 fw_mon_mode;
+ __le64 regions_mask;
+ __le32 build_tag_len;
+ u8 build_tag[64];
+ __le32 num_of_cfg_names;
+ /* no cfg_names for BT - keep zero-length */
+} __packed;
+
/* struct btintel_pcie_data
* @pdev: pci device
* @hdev: hdev device
--
2.43.0
^ permalink raw reply related [flat|nested] 15+ messages in thread* RE: [v1,1/4] Bluetooth: btintel_pcie: remove duplicate BTINTEL_PCIE_MAGIC_NUM definition
2026-08-28 14:27 [PATCH v1 1/4] Bluetooth: btintel_pcie: remove duplicate BTINTEL_PCIE_MAGIC_NUM definition Chandrashekar Devegowda
` (2 preceding siblings ...)
2026-08-28 14:27 ` [PATCH v1 4/4] Bluetooth: btintel_pcie: unified decoder coredump format Chandrashekar Devegowda
@ 2026-08-28 22:15 ` bluez.test.bot
2026-09-04 0:29 ` [PATCH v2 1/3] " Chandrashekar Devegowda
` (3 subsequent siblings)
7 siblings, 0 replies; 15+ messages in thread
From: bluez.test.bot @ 2026-08-28 22:15 UTC (permalink / raw)
To: linux-bluetooth, chandrashekar.devegowda
[-- Attachment #1: Type: text/plain, Size: 1679 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/project/bluetooth/list/?series=1153294
---Test result---
Test Summary:
CheckPatch PASS 3.53 seconds
VerifyFixes PASS 0.08 seconds
VerifySignedoff PASS 0.09 seconds
GitLint FAIL 0.86 seconds
SubjectPrefix PASS 0.29 seconds
BuildKernel PASS 27.83 seconds
CheckAllWarning PASS 30.31 seconds
CheckSparse PASS 29.13 seconds
BuildKernel32 PASS 26.64 seconds
CheckKernelLLVM SKIP 0.00 seconds
TestRunnerSetup PASS 504.06 seconds
IncrementalBuild PASS 32.39 seconds
Details
##############################
Test: GitLint - FAIL
Desc: Run gitlint
Output:
[v1,1/4] Bluetooth: btintel_pcie: remove duplicate BTINTEL_PCIE_MAGIC_NUM definition
1: T1 Title exceeds max length (84>80): "[v1,1/4] Bluetooth: btintel_pcie: remove duplicate BTINTEL_PCIE_MAGIC_NUM definition"
[v1,3/4] Bluetooth: btintel_pcie: log mailbox handler latency in msix_gp1_handler
1: T1 Title exceeds max length (81>80): "[v1,3/4] Bluetooth: btintel_pcie: log mailbox handler latency in msix_gp1_handler"
##############################
Test: CheckKernelLLVM - SKIP
Desc: Build kernel with LLVM + context analysis
Output:
Clang not found
https://github.com/bluez/bluetooth-next/pull/661
---
Regards,
Linux Bluetooth
^ permalink raw reply [flat|nested] 15+ messages in thread* [PATCH v2 1/3] Bluetooth: btintel_pcie: remove duplicate BTINTEL_PCIE_MAGIC_NUM definition
2026-08-28 14:27 [PATCH v1 1/4] Bluetooth: btintel_pcie: remove duplicate BTINTEL_PCIE_MAGIC_NUM definition Chandrashekar Devegowda
` (3 preceding siblings ...)
2026-08-28 22:15 ` [v1,1/4] Bluetooth: btintel_pcie: remove duplicate BTINTEL_PCIE_MAGIC_NUM definition bluez.test.bot
@ 2026-09-04 0:29 ` Chandrashekar Devegowda
2026-09-04 1:10 ` [v2,1/3] " bluez.test.bot
2026-09-04 16:56 ` [PATCH v2 1/3] " patchwork-bot+bluetooth
2026-09-04 0:29 ` [PATCH v2 2/3] Bluetooth: btintel_pcie: add MDBGC multi-buffer DBGC allocation Chandrashekar Devegowda
` (2 subsequent siblings)
7 siblings, 2 replies; 15+ messages in thread
From: Chandrashekar Devegowda @ 2026-09-04 0:29 UTC (permalink / raw)
To: linux-bluetooth
Cc: ravishankar.srivatsa, chethan.tumkur.narayan, kiran.k,
Chandrashekar Devegowda
Two identical definitions of BTINTEL_PCIE_MAGIC_NUM were present in
btintel_pcie.c, one indented with spaces and one with a tab. Remove the
space-indented duplicate and the surrounding blank lines. Kernel coding
style requires tabs for indentation, so keep the tab-indented copy.
No functional change.
Assisted-by: Copilot:claude-opus-4.7 sparse
Fixes: 6ed83047389c ("Bluetooth: btintel_pcie: Setup buffers for firmware traces")
Signed-off-by: Chandrashekar Devegowda <chandrashekar.devegowda@intel.com>
---
Changes in v2:
- No changes; re-sent as part of the v2 series.
drivers/bluetooth/btintel_pcie.c | 5 -----
1 file changed, 5 deletions(-)
diff --git a/drivers/bluetooth/btintel_pcie.c b/drivers/bluetooth/btintel_pcie.c
index 30923eaabed7..b90f87e8368d 100644
--- a/drivers/bluetooth/btintel_pcie.c
+++ b/drivers/bluetooth/btintel_pcie.c
@@ -73,11 +73,6 @@ struct btintel_pcie_dev_recovery {
#define BTINTEL_PCIE_HCI_EVT_PKT 0x00000004
#define BTINTEL_PCIE_HCI_ISO_PKT 0x00000005
-#define BTINTEL_PCIE_MAGIC_NUM 0xA5A5A5A5
-
-
-
-
#define BTINTEL_PCIE_MAGIC_NUM 0xA5A5A5A5
#define BTINTEL_PCIE_TRIGGER_REASON_USER_TRIGGER 0x17A2
--
2.43.0
^ permalink raw reply related [flat|nested] 15+ messages in thread* RE: [v2,1/3] Bluetooth: btintel_pcie: remove duplicate BTINTEL_PCIE_MAGIC_NUM definition
2026-09-04 0:29 ` [PATCH v2 1/3] " Chandrashekar Devegowda
@ 2026-09-04 1:10 ` bluez.test.bot
2026-09-04 16:56 ` [PATCH v2 1/3] " patchwork-bot+bluetooth
1 sibling, 0 replies; 15+ messages in thread
From: bluez.test.bot @ 2026-09-04 1:10 UTC (permalink / raw)
To: linux-bluetooth, chandrashekar.devegowda
[-- Attachment #1: Type: text/plain, Size: 1472 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/project/bluetooth/list/?series=1157479
---Test result---
Test Summary:
CheckPatch PASS 0.75 seconds
VerifyFixes PASS 0.13 seconds
VerifySignedoff PASS 0.10 seconds
GitLint FAIL 0.25 seconds
SubjectPrefix PASS 0.09 seconds
BuildKernel PASS 26.18 seconds
CheckAllWarning PASS 28.61 seconds
CheckSparse PASS 27.17 seconds
BuildKernel32 PASS 25.03 seconds
CheckKernelLLVM SKIP 0.00 seconds
TestRunnerSetup PASS 463.98 seconds
IncrementalBuild PASS 24.55 seconds
Details
##############################
Test: GitLint - FAIL
Desc: Run gitlint
Output:
[v2,1/3] Bluetooth: btintel_pcie: remove duplicate BTINTEL_PCIE_MAGIC_NUM definition
1: T1 Title exceeds max length (84>80): "[v2,1/3] Bluetooth: btintel_pcie: remove duplicate BTINTEL_PCIE_MAGIC_NUM definition"
##############################
Test: CheckKernelLLVM - SKIP
Desc: Build kernel with LLVM + context analysis
Output:
Clang not found
https://github.com/bluez/bluetooth-next/pull/700
---
Regards,
Linux Bluetooth
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH v2 1/3] Bluetooth: btintel_pcie: remove duplicate BTINTEL_PCIE_MAGIC_NUM definition
2026-09-04 0:29 ` [PATCH v2 1/3] " Chandrashekar Devegowda
2026-09-04 1:10 ` [v2,1/3] " bluez.test.bot
@ 2026-09-04 16:56 ` patchwork-bot+bluetooth
1 sibling, 0 replies; 15+ messages in thread
From: patchwork-bot+bluetooth @ 2026-09-04 16:56 UTC (permalink / raw)
To: Chandrashekar Devegowda
Cc: linux-bluetooth, ravishankar.srivatsa, chethan.tumkur.narayan,
kiran.k
Hello:
This patch was applied to bluetooth/bluetooth-next.git (master)
by Luiz Augusto von Dentz <luiz.von.dentz@intel.com>:
On Fri, 4 Sep 2026 05:59:36 +0530 you wrote:
> Two identical definitions of BTINTEL_PCIE_MAGIC_NUM were present in
> btintel_pcie.c, one indented with spaces and one with a tab. Remove the
> space-indented duplicate and the surrounding blank lines. Kernel coding
> style requires tabs for indentation, so keep the tab-indented copy.
>
> No functional change.
>
> [...]
Here is the summary with links:
- [v2,1/3] Bluetooth: btintel_pcie: remove duplicate BTINTEL_PCIE_MAGIC_NUM definition
https://git.kernel.org/bluetooth/bluetooth-next/c/1cdf710cc2a1
You are awesome, thank you!
--
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH v2 2/3] Bluetooth: btintel_pcie: add MDBGC multi-buffer DBGC allocation
2026-08-28 14:27 [PATCH v1 1/4] Bluetooth: btintel_pcie: remove duplicate BTINTEL_PCIE_MAGIC_NUM definition Chandrashekar Devegowda
` (4 preceding siblings ...)
2026-09-04 0:29 ` [PATCH v2 1/3] " Chandrashekar Devegowda
@ 2026-09-04 0:29 ` Chandrashekar Devegowda
2026-09-04 17:50 ` patchwork-bot+bluetooth
2026-09-04 0:29 ` [PATCH v2 3/3] Bluetooth: btintel_pcie: unified decoder coredump format Chandrashekar Devegowda
2026-09-04 16:56 ` [PATCH v1 1/4] Bluetooth: btintel_pcie: remove duplicate BTINTEL_PCIE_MAGIC_NUM definition patchwork-bot+bluetooth
7 siblings, 1 reply; 15+ messages in thread
From: Chandrashekar Devegowda @ 2026-09-04 0:29 UTC (permalink / raw)
To: linux-bluetooth
Cc: ravishankar.srivatsa, chethan.tumkur.narayan, kiran.k,
Chandrashekar Devegowda
Newer Intel BT PCIe variants (Nova Lake SCP2 and PTL FMP2) require
three independent DRAM debug (DBGC) buffer pools instead of the
single pool used by existing controllers.
Factor the per-pool buffer allocation into a common helper and add
a multi-DBGC (MDBGC) setup path that allocates three pools using
the fragment context format. MDBGC is used when the device reports
an MDBGC-capable variant; otherwise the existing single-pool DBGC
path is retained.
Assisted-by: Copilot:claude-opus-4.7 sparse
Signed-off-by: Chandrashekar Devegowda <chandrashekar.devegowda@intel.com>
---
Changes in v2:
- Reworded the commit message for brevity.
- Skip coredump collection in btintel_pcie_coredump_worker() when
the controller reports MDBGC support, since the MDBGC dump format
is not handled by this patch. The unified decoder path added in
patch 3/3 replaces this skip with proper multi-DBGC collection.
drivers/bluetooth/btintel_pcie.c | 184 ++++++++++++++++++++++++++-----
drivers/bluetooth/btintel_pcie.h | 30 +++++
2 files changed, 186 insertions(+), 28 deletions(-)
diff --git a/drivers/bluetooth/btintel_pcie.c b/drivers/bluetooth/btintel_pcie.c
index b90f87e8368d..233cb8a3cbd4 100644
--- a/drivers/bluetooth/btintel_pcie.c
+++ b/drivers/bluetooth/btintel_pcie.c
@@ -134,6 +134,20 @@ struct btintel_pcie_dbgc_ctxt {
struct btintel_pcie_dbgc_ctxt_buf bufs[BTINTEL_PCIE_DBGC_BUFFER_COUNT];
};
+struct btintel_pcie_mdbgc_ctxt {
+ u32 magic_num;
+ u32 ver;
+ u32 buf1_index;
+ u32 buf1_count;
+ struct btintel_pcie_dbgc_ctxt_buf buf1[BTINTEL_PCIE_DBGC_BUFFER_COUNT];
+ u32 buf2_index;
+ u32 buf2_count;
+ struct btintel_pcie_dbgc_ctxt_buf buf2[BTINTEL_PCIE_DBGC_BUFFER_COUNT];
+ u32 buf3_index;
+ u32 buf3_count;
+ struct btintel_pcie_dbgc_ctxt_buf buf3[BTINTEL_PCIE_DBGC_BUFFER_COUNT];
+};
+
struct btintel_pcie_trigger_evt {
u8 type;
u8 len;
@@ -184,57 +198,159 @@ static inline bool btintel_pcie_dbg_to_wifi(struct btintel_pcie_data *data)
return data->dbg_path_cache != BTINTEL_PCIE_DRAM;
}
-/* This function initializes the memory for DBGC buffers and formats the
- * DBGC fragment which consists header info and DBGC buffer's LSB, MSB and
- * size as the payload
- */
-static int btintel_pcie_setup_dbgc(struct btintel_pcie_data *data)
+/* Helper function to allocate and setup a debug buffer group */
+static int btintel_pcie_alloc_dbgc_buf(struct btintel_pcie_data *data,
+ struct data_buf **buf,
+ dma_addr_t *p_addr,
+ void **v_addr,
+ struct btintel_pcie_dbgc_ctxt_buf *frag,
+ u32 buf_index,
+ u32 buf_count)
{
- struct btintel_pcie_dbgc_ctxt db_frag;
- struct data_buf *buf;
+ struct data_buf *b;
int i;
- data->dbgc.count = BTINTEL_PCIE_DBGC_BUFFER_COUNT;
- data->dbgc.bufs = devm_kcalloc(&data->pdev->dev, data->dbgc.count,
- sizeof(*buf), GFP_KERNEL);
- if (!data->dbgc.bufs)
+ *buf = devm_kcalloc(&data->pdev->dev, buf_count,
+ sizeof(**buf), GFP_KERNEL);
+ if (!*buf) {
+ BT_ERR("Failed to allocate dbgc buf: %u",
+ buf_index + 1);
return -ENOMEM;
+ }
- data->dbgc.buf_v_addr = dmam_alloc_coherent(&data->pdev->dev,
- data->dbgc.count *
- BTINTEL_PCIE_DBGC_BUFFER_SIZE,
- &data->dbgc.buf_p_addr,
- GFP_KERNEL | __GFP_NOWARN);
- if (!data->dbgc.buf_v_addr)
+ *v_addr = dmam_alloc_coherent(&data->pdev->dev,
+ buf_count *
+ BTINTEL_PCIE_DBGC_BUFFER_SIZE,
+ p_addr,
+ GFP_KERNEL | __GFP_NOWARN);
+ if (!*v_addr) {
+ BT_ERR("Failed to allocate dbgc buf: %u DMA",
+ buf_index + 1);
return -ENOMEM;
+ }
+
+ for (i = 0; i < buf_count; i++) {
+ b = &(*buf)[i];
+ b->data_p_addr = *p_addr +
+ i * BTINTEL_PCIE_DBGC_BUFFER_SIZE;
+ b->data = *v_addr +
+ i * BTINTEL_PCIE_DBGC_BUFFER_SIZE;
+ frag[i].buf_addr_lsb =
+ lower_32_bits(b->data_p_addr);
+ frag[i].buf_addr_msb =
+ upper_32_bits(b->data_p_addr);
+ frag[i].buf_size = BTINTEL_PCIE_DBGC_BUFFER_SIZE;
+ }
+
+ return 0;
+}
+
+/* This function initializes the memory for MDBGC buffers */
+static int btintel_pcie_setup_mdbgc(struct btintel_pcie_data *data)
+{
+ struct btintel_pcie_mdbgc_ctxt db_frag;
+ u32 frag_size = sizeof(db_frag);
+ void *frag_v_addr;
+ int err;
+
+ data->mdbgc.count = BTINTEL_PCIE_DBGC_BUFFER_COUNT;
+
+ frag_v_addr = dmam_alloc_coherent(&data->pdev->dev, frag_size,
+ &data->mdbgc.frag_p_addr,
+ GFP_KERNEL | __GFP_NOWARN);
+ if (!frag_v_addr)
+ return -ENOMEM;
+
+ data->mdbgc.frag_v_addr = frag_v_addr;
+ data->mdbgc.frag_size = frag_size;
+
+ memset(&db_frag, 0, sizeof(db_frag));
+ db_frag.magic_num = BTINTEL_PCIE_MAGIC_NUM;
+ db_frag.ver = BTINTEL_PCIE_MDBGC_FRAG_VERSION;
+
+ /* Allocate DBGC buffer 1 */
+ db_frag.buf1_index = BTINTEL_PCIE_MDBGC_ALLOCATIONID_1;
+ db_frag.buf1_count = data->mdbgc.count;
+ err = btintel_pcie_alloc_dbgc_buf(data,
+ &data->mdbgc.buf1,
+ &data->mdbgc.buf1_p_addr,
+ &data->mdbgc.buf1_v_addr,
+ db_frag.buf1, 0,
+ data->mdbgc.count);
+ if (err)
+ return err;
+
+ /* Allocate DBGC buffer 2 */
+ db_frag.buf2_index = BTINTEL_PCIE_MDBGC_ALLOCATIONID_2;
+ db_frag.buf2_count = data->mdbgc.count;
+ err = btintel_pcie_alloc_dbgc_buf(data,
+ &data->mdbgc.buf2,
+ &data->mdbgc.buf2_p_addr,
+ &data->mdbgc.buf2_v_addr,
+ db_frag.buf2, 1,
+ data->mdbgc.count);
+ if (err)
+ return err;
+
+ /* Allocate DBGC buffer 3 */
+ db_frag.buf3_index = BTINTEL_PCIE_MDBGC_ALLOCATIONID_3;
+ db_frag.buf3_count = data->mdbgc.count;
+ err = btintel_pcie_alloc_dbgc_buf(data,
+ &data->mdbgc.buf3,
+ &data->mdbgc.buf3_p_addr,
+ &data->mdbgc.buf3_v_addr,
+ db_frag.buf3, 2,
+ data->mdbgc.count);
+ if (err)
+ return err;
+
+ memcpy(data->mdbgc.frag_v_addr, &db_frag, sizeof(db_frag));
+ return 0;
+}
+
+/* This function initializes the memory for DBGC buffers */
+static int btintel_pcie_setup_dbgc(struct btintel_pcie_data *data)
+{
+ struct btintel_pcie_dbgc_ctxt db_frag;
+ int err;
+
+ data->dbgc.count = BTINTEL_PCIE_DBGC_BUFFER_COUNT;
data->dbgc.frag_v_addr = dmam_alloc_coherent(&data->pdev->dev,
- sizeof(struct btintel_pcie_dbgc_ctxt),
- &data->dbgc.frag_p_addr,
- GFP_KERNEL | __GFP_NOWARN);
+ sizeof(struct btintel_pcie_dbgc_ctxt),
+ &data->dbgc.frag_p_addr,
+ GFP_KERNEL | __GFP_NOWARN);
if (!data->dbgc.frag_v_addr)
return -ENOMEM;
data->dbgc.frag_size = sizeof(struct btintel_pcie_dbgc_ctxt);
+ memset(&db_frag, 0, sizeof(db_frag));
db_frag.magic_num = BTINTEL_PCIE_MAGIC_NUM;
db_frag.ver = BTINTEL_PCIE_DBGC_FRAG_VERSION;
db_frag.total_size = BTINTEL_PCIE_DBGC_FRAG_PAYLOAD_SIZE;
db_frag.num_buf = BTINTEL_PCIE_DBGC_FRAG_BUFFER_COUNT;
- for (i = 0; i < data->dbgc.count; i++) {
- buf = &data->dbgc.bufs[i];
- buf->data_p_addr = data->dbgc.buf_p_addr + i * BTINTEL_PCIE_DBGC_BUFFER_SIZE;
- buf->data = data->dbgc.buf_v_addr + i * BTINTEL_PCIE_DBGC_BUFFER_SIZE;
- db_frag.bufs[i].buf_addr_lsb = lower_32_bits(buf->data_p_addr);
- db_frag.bufs[i].buf_addr_msb = upper_32_bits(buf->data_p_addr);
- db_frag.bufs[i].buf_size = BTINTEL_PCIE_DBGC_BUFFER_SIZE;
- }
+ err = btintel_pcie_alloc_dbgc_buf(data,
+ &data->dbgc.bufs,
+ &data->dbgc.buf_p_addr,
+ &data->dbgc.buf_v_addr,
+ db_frag.bufs, 0,
+ data->dbgc.count);
+ if (err)
+ return err;
memcpy(data->dbgc.frag_v_addr, &db_frag, sizeof(db_frag));
return 0;
}
+static bool btintel_pcie_is_mdbgc_supported(struct btintel_pcie_data *data)
+{
+ return data->pdev->device == BTINTEL_PCIE_DEVICE_ID_NVL_S_SCP2 ||
+ data->pdev->device == BTINTEL_PCIE_DEVICE_ID_NVL_Hx_SCP2 ||
+ data->pdev->device == BTINTEL_PCIE_DEVICE_ID_PTL_FMP2;
+}
+
static inline void ipc_print_ia_ring(struct hci_dev *hdev, struct ia *ia,
u16 queue_num)
{
@@ -1866,6 +1982,12 @@ static void btintel_pcie_coredump_worker(struct work_struct *work)
goto out;
}
+ if (btintel_pcie_is_mdbgc_supported(data)) {
+ bt_dev_info(data->hdev,
+ "Skipping coredump: MDBGC dump format not supported yet");
+ goto out;
+ }
+
btintel_pcie_dump_traces(data->hdev);
out:
/* Release guard last so a new trigger can run only after this
@@ -2247,10 +2369,14 @@ static void btintel_pcie_init_ci(struct btintel_pcie_data *data,
*/
ci->dbgc_addr = 0;
ci->dbgc_size = 0;
+ } else if (btintel_pcie_is_mdbgc_supported(data)) {
+ ci->dbgc_addr = data->mdbgc.frag_p_addr;
+ ci->dbgc_size = data->mdbgc.frag_size;
} else {
ci->dbgc_addr = data->dbgc.frag_p_addr;
ci->dbgc_size = data->dbgc.frag_size;
}
+
ci->dbg_preset = 0x00;
}
@@ -2483,6 +2609,8 @@ static int btintel_pcie_alloc(struct btintel_pcie_data *data)
* buffer allocation entirely.
*/
err = 0;
+ } else if (btintel_pcie_is_mdbgc_supported(data)) {
+ err = btintel_pcie_setup_mdbgc(data);
} else {
err = btintel_pcie_setup_dbgc(data);
}
diff --git a/drivers/bluetooth/btintel_pcie.h b/drivers/bluetooth/btintel_pcie.h
index 9baa214d9bbe..5c35e65d3e81 100644
--- a/drivers/bluetooth/btintel_pcie.h
+++ b/drivers/bluetooth/btintel_pcie.h
@@ -106,8 +106,19 @@
* Bits[2:3] DBGI O/P : 01 = WiFi DBGC
*/
#define BTINTEL_PCIE_DRAM 0x01
+#define BTINTEL_PCIE_FW_MON_MODE_DRAM 0x02
#define BTINTEL_PCIE_WIFI_DBGC 0x06
+#define BTINTEL_PCIE_MDBGC_FRAG_VERSION 2
+
+#define BTINTEL_PCIE_MDBGC_ALLOCATIONID_1 0
+#define BTINTEL_PCIE_MDBGC_ALLOCATIONID_2 1
+#define BTINTEL_PCIE_MDBGC_ALLOCATIONID_3 2
+
+#define BTINTEL_PCIE_DEVICE_ID_NVL_S_SCP2 0x6E74
+#define BTINTEL_PCIE_DEVICE_ID_NVL_Hx_SCP2 0xD346
+#define BTINTEL_PCIE_DEVICE_ID_PTL_FMP2 0xE476
+
/* Causes for the FH register interrupts */
enum msix_fh_int_causes {
BTINTEL_PCIE_MSIX_FH_INT_CAUSES_0 = BIT(0), /* cause 0 */
@@ -459,6 +470,24 @@ struct btintel_pcie_dbgc {
struct data_buf *bufs;
};
+struct btintel_pcie_mdbgc {
+ u32 count;
+
+ void *frag_v_addr;
+ dma_addr_t frag_p_addr;
+ u32 frag_size;
+
+ dma_addr_t buf1_p_addr;
+ void *buf1_v_addr;
+ dma_addr_t buf2_p_addr;
+ void *buf2_v_addr;
+ dma_addr_t buf3_p_addr;
+ void *buf3_v_addr;
+ struct data_buf *buf1;
+ struct data_buf *buf2;
+ struct data_buf *buf3;
+};
+
struct btintel_pcie_dump_mem_info {
u32 exception_dump_addr;
u32 exception_dump_len;
@@ -599,6 +628,7 @@ struct btintel_pcie_data {
u32 alive_intr_ctxt;
enum btintel_pcie_reset_type reset_type;
struct btintel_pcie_dbgc dbgc;
+ struct btintel_pcie_mdbgc mdbgc;
struct btintel_pcie_dump_header dmp_hdr;
u8 pm_sx_event;
u32 debug_evt_addr;
--
2.43.0
^ permalink raw reply related [flat|nested] 15+ messages in thread* [PATCH v2 3/3] Bluetooth: btintel_pcie: unified decoder coredump format
2026-08-28 14:27 [PATCH v1 1/4] Bluetooth: btintel_pcie: remove duplicate BTINTEL_PCIE_MAGIC_NUM definition Chandrashekar Devegowda
` (5 preceding siblings ...)
2026-09-04 0:29 ` [PATCH v2 2/3] Bluetooth: btintel_pcie: add MDBGC multi-buffer DBGC allocation Chandrashekar Devegowda
@ 2026-09-04 0:29 ` Chandrashekar Devegowda
2026-09-04 0:46 ` [v2,3/3] " bluez.test.bot
` (2 more replies)
2026-09-04 16:56 ` [PATCH v1 1/4] Bluetooth: btintel_pcie: remove duplicate BTINTEL_PCIE_MAGIC_NUM definition patchwork-bot+bluetooth
7 siblings, 3 replies; 15+ messages in thread
From: Chandrashekar Devegowda @ 2026-09-04 0:29 UTC (permalink / raw)
To: linux-bluetooth
Cc: ravishankar.srivatsa, chethan.tumkur.narayan, kiran.k,
Chandrashekar Devegowda
Replace the legacy TLV-based coredump with a unified INI-format dump
compatible with the iwlwifi decoder so a single decoder can parse
coredumps across all Intel BT PCIe controller variants.
The dump is emitted via dev_coredumpsg() as structured regions built
from firmware-advertised addresses: DRAM monitor buffers (single or
MDBGC multi-buffer), SMEM monitor, exception event buffer, DCCM, SDS,
SDS IOSF, ECL and dump info metadata. Regions are collected only when
firmware advertises a valid address and size, and only collected
regions are advertised in regions_mask.
Add INTEL_CNVX_TOP_DASH() and INTEL_CNVX_TOP_FLAVOR() helpers to
btintel.h for RfIdDash/RfIdFlavor extraction.
Assisted-by: Copilot:gemini-3.6-flash
Signed-off-by: Chandrashekar Devegowda <chandrashekar.devegowda@intel.com>
---
Changes in v2:
- Reworded the commit message for brevity.
- Add btintel_pcie_dump_dram_monitor() helper that reads the
per-allocation status/wrap registers (0x100 * allocation_id
offset) for each MDBGC pool, stores the per-fragment DWORD write
offset in mon_dump->write_ptr, and stores the active fragment
index in mon_dump->cur_frag, replacing the previously flattened
write_ptr / hard-coded cur_frag = 0 encoding.
- btintel_pcie_dump_target_region() now returns a skip indicator
(leaving regions_mask untouched) when the firmware does not
advertise a valid region, avoiding a spurious
"Skipping empty dump region: EXCEPTION_EVT_BUFFER" warning when
the exception dump TLV is absent. It also skips when the size
rounded down to 4-byte alignment collapses to zero.
- Log via bt_dev_dbg (instead of bt_dev_err) when firmware does
not advertise an SMEM monitor region, so the absence of an
optional TLV is not reported as an error.
drivers/bluetooth/btintel.h | 2 +
drivers/bluetooth/btintel_pcie.c | 887 ++++++++++++++++++++++++-------
drivers/bluetooth/btintel_pcie.h | 149 +++++-
3 files changed, 846 insertions(+), 192 deletions(-)
diff --git a/drivers/bluetooth/btintel.h b/drivers/bluetooth/btintel.h
index ef232820c31b..c00f793d9dc8 100644
--- a/drivers/bluetooth/btintel.h
+++ b/drivers/bluetooth/btintel.h
@@ -225,7 +225,9 @@ struct btintel_sar_rev2 {
#define INTEL_HW_PLATFORM(cnvx_bt) ((u8)(((cnvx_bt) & 0x0000ff00) >> 8))
#define INTEL_HW_VARIANT(cnvx_bt) ((u8)(((cnvx_bt) & 0x003f0000) >> 16))
#define INTEL_CNVX_TOP_TYPE(cnvx_top) ((cnvx_top) & 0x00000fff)
+#define INTEL_CNVX_TOP_DASH(cnvx_top) (((cnvx_top) & 0x00f00000) >> 20)
#define INTEL_CNVX_TOP_STEP(cnvx_top) (((cnvx_top) & 0x0f000000) >> 24)
+#define INTEL_CNVX_TOP_FLAVOR(cnvx_top) (((cnvx_top) & 0xf0000000) >> 28)
#define INTEL_CNVX_TOP_PACK_SWAB(t, s) __swab16(((__u16)(((t) << 4) | (s))))
enum {
diff --git a/drivers/bluetooth/btintel_pcie.c b/drivers/bluetooth/btintel_pcie.c
index 233cb8a3cbd4..38a6dbd04dcf 100644
--- a/drivers/bluetooth/btintel_pcie.c
+++ b/drivers/bluetooth/btintel_pcie.c
@@ -19,6 +19,7 @@
#include <linux/unaligned.h>
#include <linux/devcoredump.h>
+#include <linux/scatterlist.h>
#include <net/bluetooth/bluetooth.h>
#include <net/bluetooth/hci_core.h>
@@ -198,7 +199,15 @@ static inline bool btintel_pcie_dbg_to_wifi(struct btintel_pcie_data *data)
return data->dbg_path_cache != BTINTEL_PCIE_DRAM;
}
-/* Helper function to allocate and setup a debug buffer group */
+/* Helper function to allocate and setup a debug buffer group
+ * @data: driver data structure
+ * @buf: pointer to data_buf array pointer
+ * @p_addr: pointer to physical DMA address
+ * @v_addr: pointer to virtual address
+ * @frag: pointer to fragment buffer array
+ * @buf_index: buffer index (for error messages)
+ * @buf_count: number of buffers to allocate
+ */
static int btintel_pcie_alloc_dbgc_buf(struct btintel_pcie_data *data,
struct data_buf **buf,
dma_addr_t *p_addr,
@@ -213,39 +222,36 @@ static int btintel_pcie_alloc_dbgc_buf(struct btintel_pcie_data *data,
*buf = devm_kcalloc(&data->pdev->dev, buf_count,
sizeof(**buf), GFP_KERNEL);
if (!*buf) {
- BT_ERR("Failed to allocate dbgc buf: %u",
- buf_index + 1);
+ BT_ERR("Failed to allocate dbgc buf: %u", buf_index + 1);
return -ENOMEM;
}
*v_addr = dmam_alloc_coherent(&data->pdev->dev,
buf_count *
- BTINTEL_PCIE_DBGC_BUFFER_SIZE,
- p_addr,
- GFP_KERNEL | __GFP_NOWARN);
+ BTINTEL_PCIE_DBGC_BUFFER_SIZE,
+ p_addr,
+ GFP_KERNEL | __GFP_NOWARN);
if (!*v_addr) {
- BT_ERR("Failed to allocate dbgc buf: %u DMA",
- buf_index + 1);
+ BT_ERR("Failed to allocate dbgc buf: %u DMA", buf_index + 1);
return -ENOMEM;
}
for (i = 0; i < buf_count; i++) {
b = &(*buf)[i];
- b->data_p_addr = *p_addr +
- i * BTINTEL_PCIE_DBGC_BUFFER_SIZE;
- b->data = *v_addr +
- i * BTINTEL_PCIE_DBGC_BUFFER_SIZE;
- frag[i].buf_addr_lsb =
- lower_32_bits(b->data_p_addr);
- frag[i].buf_addr_msb =
- upper_32_bits(b->data_p_addr);
+ b->data_p_addr = *p_addr + i * BTINTEL_PCIE_DBGC_BUFFER_SIZE;
+ b->data = *v_addr + i * BTINTEL_PCIE_DBGC_BUFFER_SIZE;
+ frag[i].buf_addr_lsb = lower_32_bits(b->data_p_addr);
+ frag[i].buf_addr_msb = upper_32_bits(b->data_p_addr);
frag[i].buf_size = BTINTEL_PCIE_DBGC_BUFFER_SIZE;
}
return 0;
}
-/* This function initializes the memory for MDBGC buffers */
+/* This function initializes the memory for MDBGC buffers and formats the
+ * DBGC fragment which consists header info and DBGC buffer's LSB, MSB and
+ * size as the payload
+ */
static int btintel_pcie_setup_mdbgc(struct btintel_pcie_data *data)
{
struct btintel_pcie_mdbgc_ctxt db_frag;
@@ -255,15 +261,19 @@ static int btintel_pcie_setup_mdbgc(struct btintel_pcie_data *data)
data->mdbgc.count = BTINTEL_PCIE_DBGC_BUFFER_COUNT;
+ /* Allocate fragment context structure */
frag_v_addr = dmam_alloc_coherent(&data->pdev->dev, frag_size,
&data->mdbgc.frag_p_addr,
GFP_KERNEL | __GFP_NOWARN);
- if (!frag_v_addr)
+ if (!frag_v_addr) {
+ BT_ERR("Failed to allocate mdbgc context");
return -ENOMEM;
+ }
data->mdbgc.frag_v_addr = frag_v_addr;
data->mdbgc.frag_size = frag_size;
+ /* Initialize fragment header */
memset(&db_frag, 0, sizeof(db_frag));
db_frag.magic_num = BTINTEL_PCIE_MAGIC_NUM;
db_frag.ver = BTINTEL_PCIE_MDBGC_FRAG_VERSION;
@@ -275,7 +285,8 @@ static int btintel_pcie_setup_mdbgc(struct btintel_pcie_data *data)
&data->mdbgc.buf1,
&data->mdbgc.buf1_p_addr,
&data->mdbgc.buf1_v_addr,
- db_frag.buf1, 0,
+ db_frag.buf1,
+ 0,
data->mdbgc.count);
if (err)
return err;
@@ -287,7 +298,8 @@ static int btintel_pcie_setup_mdbgc(struct btintel_pcie_data *data)
&data->mdbgc.buf2,
&data->mdbgc.buf2_p_addr,
&data->mdbgc.buf2_v_addr,
- db_frag.buf2, 1,
+ db_frag.buf2,
+ 1,
data->mdbgc.count);
if (err)
return err;
@@ -299,58 +311,64 @@ static int btintel_pcie_setup_mdbgc(struct btintel_pcie_data *data)
&data->mdbgc.buf3,
&data->mdbgc.buf3_p_addr,
&data->mdbgc.buf3_v_addr,
- db_frag.buf3, 2,
+ db_frag.buf3,
+ 2,
data->mdbgc.count);
if (err)
return err;
+ /* Copy fragment to DMA coherent memory */
memcpy(data->mdbgc.frag_v_addr, &db_frag, sizeof(db_frag));
return 0;
}
-/* This function initializes the memory for DBGC buffers */
+/* This function initializes the memory for DBGC buffers and formats the
+ * DBGC fragment which consists header info and DBGC buffer's LSB, MSB and
+ * size as the payload
+ */
static int btintel_pcie_setup_dbgc(struct btintel_pcie_data *data)
{
struct btintel_pcie_dbgc_ctxt db_frag;
+ u32 frag_size = sizeof(db_frag);
int err;
data->dbgc.count = BTINTEL_PCIE_DBGC_BUFFER_COUNT;
+ /* Allocate fragment context structure */
data->dbgc.frag_v_addr = dmam_alloc_coherent(&data->pdev->dev,
- sizeof(struct btintel_pcie_dbgc_ctxt),
- &data->dbgc.frag_p_addr,
- GFP_KERNEL | __GFP_NOWARN);
- if (!data->dbgc.frag_v_addr)
+ frag_size,
+ &data->dbgc.frag_p_addr,
+ GFP_KERNEL | __GFP_NOWARN);
+ if (!data->dbgc.frag_v_addr) {
+ BT_ERR("Failed to allocate dbgc context");
return -ENOMEM;
+ }
data->dbgc.frag_size = sizeof(struct btintel_pcie_dbgc_ctxt);
+ /* Initialize fragment header */
memset(&db_frag, 0, sizeof(db_frag));
db_frag.magic_num = BTINTEL_PCIE_MAGIC_NUM;
db_frag.ver = BTINTEL_PCIE_DBGC_FRAG_VERSION;
db_frag.total_size = BTINTEL_PCIE_DBGC_FRAG_PAYLOAD_SIZE;
db_frag.num_buf = BTINTEL_PCIE_DBGC_FRAG_BUFFER_COUNT;
+ /* Allocate DBGC buffers */
err = btintel_pcie_alloc_dbgc_buf(data,
&data->dbgc.bufs,
&data->dbgc.buf_p_addr,
&data->dbgc.buf_v_addr,
- db_frag.bufs, 0,
+ db_frag.bufs,
+ 0,
data->dbgc.count);
if (err)
return err;
+ /* Copy fragment to DMA coherent memory */
memcpy(data->dbgc.frag_v_addr, &db_frag, sizeof(db_frag));
return 0;
}
-static bool btintel_pcie_is_mdbgc_supported(struct btintel_pcie_data *data)
-{
- return data->pdev->device == BTINTEL_PCIE_DEVICE_ID_NVL_S_SCP2 ||
- data->pdev->device == BTINTEL_PCIE_DEVICE_ID_NVL_Hx_SCP2 ||
- data->pdev->device == BTINTEL_PCIE_DEVICE_ID_PTL_FMP2;
-}
-
static inline void ipc_print_ia_ring(struct hci_dev *hdev, struct ia *ia,
u16 queue_num)
{
@@ -753,169 +771,673 @@ static void btintel_pcie_release_mac_access(struct btintel_pcie_data *data)
}
}
-static void *btintel_pcie_copy_tlv(void *dest, enum btintel_pcie_tlv_type type,
- void *data, size_t size)
+static struct scatterlist *btintel_pcie_alloc_sgtable(ssize_t size)
{
- struct intel_tlv *tlv;
+ int nents, i;
+ struct page *page;
+ struct scatterlist *sg, *result;
+
+ if (size <= 0)
+ return NULL;
+
+ nents = DIV_ROUND_UP(size, PAGE_SIZE);
+ result = kcalloc(nents, sizeof(*result), GFP_KERNEL);
+ if (!result)
+ return NULL;
+
+ sg_init_table(result, nents);
+ sg = result;
- tlv = dest;
- tlv->type = type;
- tlv->len = size;
- memcpy(tlv->val, data, tlv->len);
- return dest + sizeof(*tlv) + size;
+ for (i = 0; size > 0; i++) {
+ ssize_t bytes = min_t(ssize_t, size, PAGE_SIZE);
+
+ page = alloc_page(GFP_KERNEL | __GFP_ZERO);
+ if (!page)
+ goto err_free;
+
+ sg_set_page(sg, page, bytes, 0);
+ sg = sg_next(sg);
+ size -= bytes;
+ }
+ return result;
+
+err_free:
+ for (sg = result; sg; sg = sg_next(sg)) {
+ page = sg_page(sg);
+ if (page)
+ __free_page(page);
+ }
+ kfree(result);
+ return NULL;
}
-static int btintel_pcie_read_dram_buffers(struct btintel_pcie_data *data)
+static struct btintel_pcie_dump_entry *
+btintel_pcie_dump_entry_alloc(u32 data_size)
{
- u32 offset, prev_size, wr_ptr_status, dump_size, data_len;
- u32 status_reg, wrap_reg;
- struct btintel_pcie_dbgc *dbgc = &data->dbgc;
+ struct btintel_pcie_dump_entry *entry;
+
+ entry = vzalloc(sizeof(*entry) + data_size);
+ if (!entry)
+ return NULL;
+
+ entry->size = data_size;
+ INIT_LIST_HEAD(&entry->list);
+ return entry;
+}
+
+static void btintel_pcie_dump_list_free(struct list_head *list)
+{
+ struct btintel_pcie_dump_entry *entry, *tmp;
+
+ list_for_each_entry_safe(entry, tmp, list, list) {
+ list_del(&entry->list);
+ vfree(entry);
+ }
+}
+
+static u32 btintel_pcie_dump_list_total_size(struct list_head *list)
+{
+ struct btintel_pcie_dump_entry *entry;
+ u32 total = 0;
+
+ list_for_each_entry(entry, list, list)
+ total += entry->size;
+
+ return total;
+}
+
+static int btintel_pcie_dump_dram(struct list_head *list,
+ u8 count, struct data_buf *bufs,
+ u32 write_ptr, u32 wrap_ctr, u32 cur_frag,
+ u32 region_id, const char *name)
+{
+ struct btintel_pcie_dump_entry *entry;
+ struct btintel_pcie_ini_dump_data *dump_data;
+ struct btintel_pcie_ini_monitor_dump *mon_dump;
+ struct btintel_pcie_dump_range *range;
+ u32 mon_hdr_size, ranges_size, payload_size, total_size;
+ int i;
+
+ mon_hdr_size = sizeof(*mon_dump);
+ ranges_size = count * (sizeof(*range) + BTINTEL_PCIE_DBGC_BUFFER_SIZE);
+ payload_size = mon_hdr_size + ranges_size;
+ total_size = sizeof(*dump_data) + payload_size;
+
+ entry = btintel_pcie_dump_entry_alloc(total_size);
+ if (!entry)
+ return -ENOMEM;
+
+ dump_data = (void *)entry->data;
+ dump_data->type = BTINTEL_PCIE_INI_REGION_DRAM_BUFFER;
+ dump_data->sub_type = 0;
+ dump_data->sub_type_ver = 0;
+ dump_data->reserved = 0;
+ dump_data->len = cpu_to_le32(payload_size);
+
+ mon_dump = (void *)dump_data->data;
+ mon_dump->header.version = cpu_to_le32(BTINTEL_PCIE_INI_DUMP_VER);
+ mon_dump->header.region_id = cpu_to_le32(region_id);
+ mon_dump->header.num_of_ranges = cpu_to_le32(count);
+ mon_dump->header.name_len = cpu_to_le32(BTINTEL_PCIE_INI_MAX_NAME);
+ memset(mon_dump->header.name, 0, BTINTEL_PCIE_INI_MAX_NAME);
+ strscpy(mon_dump->header.name, name, BTINTEL_PCIE_INI_MAX_NAME);
+
+ mon_dump->write_ptr = cpu_to_le32(write_ptr);
+ mon_dump->cycle_cnt = cpu_to_le32(wrap_ctr);
+ mon_dump->cur_frag = cpu_to_le32(cur_frag);
+
+ range = (void *)mon_dump->data;
+ for (i = 0; i < count; i++) {
+ range->range_data_size =
+ cpu_to_le32(BTINTEL_PCIE_DBGC_BUFFER_SIZE);
+ range->dram_base_addr = cpu_to_le64(bufs[i].data_p_addr);
+ memcpy(range->data, bufs[i].data,
+ BTINTEL_PCIE_DBGC_BUFFER_SIZE);
+ range = (void *)range->data + BTINTEL_PCIE_DBGC_BUFFER_SIZE;
+ }
+
+ list_add_tail(&entry->list, list);
+ return 0;
+}
+
+static int
+btintel_pcie_dump_dram_monitor(struct btintel_pcie_data *data,
+ struct list_head *list, u8 count,
+ struct data_buf *bufs, u32 status_reg,
+ u32 wrap_reg, u8 allocation_id, u32 region_id,
+ const char *name)
+{
+ u32 allocation_offset, write_ptr, wrap_ctr, wr_ptr_status;
+ u8 cur_frag;
+
+ allocation_offset = allocation_id *
+ BTINTEL_PCIE_DBGC_ALLOCATION_OFFSET;
+ wr_ptr_status = btintel_pcie_rd_dev_mem(data, status_reg +
+ allocation_offset);
+ wrap_ctr = btintel_pcie_rd_dev_mem(data, wrap_reg +
+ allocation_offset);
+
+ write_ptr = wr_ptr_status & BTINTEL_PCIE_DBG_OFFSET_BIT_MASK;
+ write_ptr >>= 2;
+ cur_frag = BTINTEL_PCIE_DBGC_DBG_BUF_IDX(wr_ptr_status);
+ if (cur_frag >= count) {
+ bt_dev_warn(data->hdev,
+ "Invalid DRAM monitor fragment %u for allocation %u",
+ cur_frag, allocation_id);
+ return -EINVAL;
+ }
+
+ bt_dev_dbg(data->hdev,
+ "allocation=%u wr_ptr_status=0x%08x write_ptr=0x%06x cur_frag=%u wrap_ctr=0x%08x",
+ allocation_id, wr_ptr_status, write_ptr, cur_frag, wrap_ctr);
+
+ return btintel_pcie_dump_dram(list, count, bufs, write_ptr, wrap_ctr,
+ cur_frag, region_id, name);
+}
+
+static int btintel_pcie_dump_target_region(struct btintel_pcie_data *data,
+ struct list_head *list,
+ u32 region_id, const char *name,
+ u32 addr_start, u32 size)
+{
+ struct btintel_pcie_dump_entry *entry;
+ struct btintel_pcie_ini_dump_data *dump_data;
+ struct btintel_pcie_ini_dump_header *hdr;
+ struct btintel_pcie_dump_range *range;
+ u32 payload_size, total_size, target_mem_offset, tempdata;
+ u8 *dest;
+ int i;
+
+ if (!size) {
+ bt_dev_warn(data->hdev, "Skipping empty dump region: %s", name);
+ return 1;
+ }
+
+ if (!addr_start) {
+ bt_dev_warn(data->hdev,
+ "Skipping dump region with zero address: %s",
+ name);
+ return 1;
+ }
+
+ if (size > BTINTEL_PCIE_REGION_MAX_SIZE) {
+ bt_dev_warn(data->hdev,
+ "Skipping dump region %s: size %u exceeds max %u",
+ name, size, BTINTEL_PCIE_REGION_MAX_SIZE);
+ return 1;
+ }
+
+ if (addr_start > U32_MAX - size) {
+ bt_dev_warn(data->hdev,
+ "Skipping dump region %s: addr_start 0x%08x + size %u would overflow",
+ name, addr_start, size);
+ return 1;
+ }
+
+ /* Align to 4 bytes - target access reads 32-bit words */
+ size = round_down(size, sizeof(u32));
+ if (!size) {
+ bt_dev_warn(data->hdev,
+ "Skipping dump region %s: size is smaller than 4-byte access",
+ name);
+ return 1;
+ }
+
+ bt_dev_dbg(data->hdev,
+ "Target access: region=%s start=0x%08x size=%u",
+ name, addr_start, size);
+
+ payload_size = sizeof(*hdr) + sizeof(*range) + size;
+ total_size = sizeof(*dump_data) + payload_size;
+ entry = btintel_pcie_dump_entry_alloc(total_size);
+ if (!entry)
+ return -ENOMEM;
+
+ dump_data = (void *)entry->data;
+ dump_data->type = BTINTEL_PCIE_INI_REGION_DEVICE_MEMORY;
+ dump_data->sub_type = 0;
+ dump_data->sub_type_ver = 0;
+ dump_data->reserved = 0;
+ dump_data->len = cpu_to_le32(payload_size);
+
+ hdr = (void *)dump_data->data;
+ hdr->version = cpu_to_le32(BTINTEL_PCIE_INI_DUMP_VER);
+ hdr->region_id = cpu_to_le32(region_id);
+ hdr->num_of_ranges = cpu_to_le32(1);
+ hdr->name_len = cpu_to_le32(BTINTEL_PCIE_INI_MAX_NAME);
+ memset(hdr->name, 0, BTINTEL_PCIE_INI_MAX_NAME);
+ strscpy(hdr->name, name, BTINTEL_PCIE_INI_MAX_NAME);
+
+ range = (void *)(hdr + 1);
+ range->range_data_size = cpu_to_le32(size);
+ range->internal_base_addr = cpu_to_le32(addr_start);
+
+ dest = (u8 *)range->data;
+ target_mem_offset = size / sizeof(u32);
+ for (i = 0; i < target_mem_offset; i++) {
+ u32 offset = BTINTEL_PCIE_TARGET_ACCESS_FRAG_OFFSET * i;
+
+ tempdata = btintel_pcie_rd_dev_mem(data,
+ addr_start + offset);
+ memcpy(dest, &tempdata, sizeof(tempdata));
+ dest += sizeof(tempdata);
+ }
+
+ list_add_tail(&entry->list, list);
+ return 0;
+}
+
+static int btintel_pcie_dump_smem_monitor_region(struct btintel_pcie_data *data,
+ struct list_head *list,
+ u32 region_id,
+ const char *name,
+ u32 addr_start, u32 size)
+{
+ struct btintel_pcie_dump_entry *entry;
+ struct btintel_pcie_ini_dump_data *dump_data;
+ struct btintel_pcie_ini_monitor_dump *mon;
+ struct btintel_pcie_dump_range *range;
+ u32 payload_size, total_size, target_mem_offset, tempdata;
+ u8 *dest;
+ int i;
+
+ if (!size || !addr_start) {
+ bt_dev_err(data->hdev, "Skipping smem dump: size = %u addr = %8.8x",
+ size, addr_start);
+ return -EINVAL;
+ }
+
+ payload_size = sizeof(*mon) + sizeof(*range) + size;
+ total_size = sizeof(*dump_data) + payload_size;
+ entry = btintel_pcie_dump_entry_alloc(total_size);
+ if (!entry)
+ return -ENOMEM;
+
+ dump_data = (void *)entry->data;
+ dump_data->type = BTINTEL_PCIE_INI_REGION_INTERNAL_BUFFER;
+ dump_data->sub_type = 0;
+ dump_data->sub_type_ver = 0;
+ dump_data->reserved = 0;
+ dump_data->len = cpu_to_le32(payload_size);
+
+ mon = (void *)dump_data->data;
+ mon->header.version = cpu_to_le32(BTINTEL_PCIE_INI_DUMP_VER);
+ mon->header.region_id = cpu_to_le32(region_id);
+ mon->header.num_of_ranges = cpu_to_le32(1);
+ mon->header.name_len = cpu_to_le32(BTINTEL_PCIE_INI_MAX_NAME);
+ memset(mon->header.name, 0, BTINTEL_PCIE_INI_MAX_NAME);
+ strscpy(mon->header.name, name, BTINTEL_PCIE_INI_MAX_NAME);
+
+ mon->write_ptr = cpu_to_le32(0);
+ mon->cycle_cnt = cpu_to_le32(0);
+ mon->cur_frag = cpu_to_le32(0);
+
+ range = (void *)mon->data;
+ range->range_data_size = cpu_to_le32(size);
+ range->internal_base_addr = cpu_to_le32(addr_start);
+
+ dest = (u8 *)range->data;
+ target_mem_offset = size / sizeof(u32);
+ for (i = 0; i < target_mem_offset; i++) {
+ u32 offset = BTINTEL_PCIE_TARGET_ACCESS_FRAG_OFFSET * i;
+
+ tempdata = btintel_pcie_rd_dev_mem(data,
+ addr_start + offset);
+ memcpy(dest, &tempdata, sizeof(tempdata));
+ dest += sizeof(tempdata);
+ }
+
+ list_add_tail(&entry->list, list);
+ return 0;
+}
+
+static int btintel_pcie_dump_info(struct btintel_pcie_data *data,
+ struct list_head *list,
+ u64 regions_mask)
+{
+ struct btintel_pcie_dump_entry *entry;
+ struct btintel_pcie_error_dump_data *tlv;
+ struct btintel_pcie_ini_dump_info *dump;
+ u32 size = sizeof(*tlv) + sizeof(*dump);
+ char build_tag[64];
+
+ entry = btintel_pcie_dump_entry_alloc(size);
+ if (!entry)
+ return -ENOMEM;
+
+ tlv = (void *)entry->data;
+ tlv->type = cpu_to_le32(BTINTEL_PCIE_INI_DUMP_INFO_TYPE);
+ tlv->len = cpu_to_le32(sizeof(*dump));
+
+ dump = (void *)tlv->data;
+ memset(dump, 0, sizeof(*dump));
+
+ dump->version = cpu_to_le32(BTINTEL_PCIE_INI_DUMP_VER);
+ dump->trigger_reason = cpu_to_le32(data->dmp_hdr.trigger_reason);
+
+ if (data->dmp_hdr.trigger_reason ==
+ BTINTEL_PCIE_TRIGGER_REASON_FW_ASSERT)
+ dump->time_point =
+ cpu_to_le32(BTINTEL_PCIE_TIME_POINT_FW_ASSERT);
+ else if (data->dmp_hdr.trigger_reason ==
+ BTINTEL_PCIE_TRIGGER_REASON_USER_TRIGGER)
+ dump->time_point =
+ cpu_to_le32(BTINTEL_PCIE_TIME_POINT_USER_TRIGGER);
+
+ dump->hw_type = cpu_to_le32(INTEL_CNVX_TOP_TYPE(data->dmp_hdr.cnvi_top));
+ dump->hw_step = cpu_to_le32(INTEL_CNVX_TOP_STEP(data->dmp_hdr.cnvi_top));
+ bt_dev_dbg(data->hdev, "hw_type=0x%x hw_step=0x%x (cnvi_top=0x%x)",
+ le32_to_cpu(dump->hw_type), le32_to_cpu(dump->hw_step),
+ data->dmp_hdr.cnvi_top);
+
+ dump->ver_type = cpu_to_le32(data->dmp_hdr.cnvi_bt);
+ dump->ver_subtype = cpu_to_le32(data->dmp_hdr.fw_sha);
+ dump->rf_id_type = cpu_to_le32(INTEL_CNVX_TOP_TYPE(data->dmp_hdr.cnvr_top));
+ dump->rf_id_dash = cpu_to_le32(INTEL_CNVX_TOP_DASH(data->dmp_hdr.cnvr_top));
+ dump->rf_id_step = cpu_to_le32(INTEL_CNVX_TOP_STEP(data->dmp_hdr.cnvr_top));
+ dump->rf_id_flavor = cpu_to_le32(INTEL_CNVX_TOP_FLAVOR(data->dmp_hdr.cnvr_top));
+ bt_dev_dbg(data->hdev,
+ "rf_id_type=0x%x rf_id_dash=0x%x rf_id_step=0x%x rf_id_flavor=0x%x (cnvr_top=0x%x)",
+ le32_to_cpu(dump->rf_id_type),
+ le32_to_cpu(dump->rf_id_dash),
+ le32_to_cpu(dump->rf_id_step),
+ le32_to_cpu(dump->rf_id_flavor),
+ data->dmp_hdr.cnvr_top);
+ dump->lmac_major = cpu_to_le32(0);
+ dump->lmac_minor = cpu_to_le32(0);
+ dump->umac_major = cpu_to_le32(0);
+ dump->umac_minor = cpu_to_le32(0);
+ dump->fw_mon_mode = cpu_to_le32(BTINTEL_PCIE_FW_MON_MODE_DRAM);
+
+ dump->regions_mask = cpu_to_le64(regions_mask);
+
+ bt_dev_dbg(data->hdev, "ExpectedRegionIDs regions_mask=0x%016llx",
+ le64_to_cpu(dump->regions_mask));
+
+ snprintf(build_tag, sizeof(build_tag), "%08X", data->dmp_hdr.fw_sha);
+
+ dump->build_tag_len = cpu_to_le32(strlen(build_tag));
+ memcpy(dump->build_tag, build_tag, min(strlen(build_tag),
+ sizeof(dump->build_tag)));
+
+ dump->num_of_cfg_names = cpu_to_le32(0);
+
+ bt_dev_dbg(data->hdev,
+ "HwType=0x%08x HwStep=0x%08x RfIdType=0x%08x RfIdDash=0x%08x RfIdStep=0x%08x RfIdFlavor=0x%08x",
+ le32_to_cpu(dump->hw_type), le32_to_cpu(dump->hw_step),
+ le32_to_cpu(dump->rf_id_type),
+ le32_to_cpu(dump->rf_id_dash),
+ le32_to_cpu(dump->rf_id_step),
+ le32_to_cpu(dump->rf_id_flavor));
+ bt_dev_dbg(data->hdev, "VerType=0x%08x VerSubType=0x%08x",
+ le32_to_cpu(dump->ver_type), le32_to_cpu(dump->ver_subtype));
+ bt_dev_dbg(data->hdev,
+ "LmacMajor=0x%08x LmacMinor=0x%08x UmacMajor=0x%08x UmacMinor=0x%08x",
+ le32_to_cpu(dump->lmac_major), le32_to_cpu(dump->lmac_minor),
+ le32_to_cpu(dump->umac_major),
+ le32_to_cpu(dump->umac_minor));
+ bt_dev_dbg(data->hdev, "TriggerReason=0x%04x MonMode=%u BuildTag=%.64s",
+ le32_to_cpu(dump->trigger_reason),
+ le32_to_cpu(dump->fw_mon_mode),
+ dump->build_tag);
+
+ list_add(&entry->list, list);
+
+ return 0;
+}
+
+static bool btintel_pcie_is_mdbgc_supported(struct btintel_pcie_data *data)
+{
+ return data->pdev->device == BTINTEL_PCIE_DEVICE_ID_NVL_S_SCP2 ||
+ data->pdev->device == BTINTEL_PCIE_DEVICE_ID_NVL_Hx_SCP2 ||
+ data->pdev->device == BTINTEL_PCIE_DEVICE_ID_PTL_FMP2;
+}
+
+static void btintel_pcie_dump_mem_range(struct btintel_pcie_data *data,
+ struct list_head *list, u32 region_id,
+ const char *name, u32 addr_start,
+ u32 addr_end, u64 *regions_mask)
+{
+ u32 region_size;
+
+ if (!addr_start || !addr_end || addr_end < addr_start)
+ return;
+
+ if (addr_end > U32_MAX - 0x04) {
+ bt_dev_warn(data->hdev,
+ "Skipping dump region %s: addr_end 0x%08x would overflow",
+ name, addr_end);
+ return;
+ }
+
+ region_size = (addr_end + 0x04) - addr_start;
+ if (region_size > BTINTEL_PCIE_REGION_MAX_SIZE) {
+ bt_dev_warn(data->hdev,
+ "Skipping dump region %s: size %u exceeds max %u",
+ name, region_size,
+ BTINTEL_PCIE_REGION_MAX_SIZE);
+ return;
+ }
+
+ if (!btintel_pcie_dump_target_region(data, list, region_id, name,
+ addr_start, region_size))
+ *regions_mask |= BIT_ULL(region_id);
+}
+
+static int btintel_pcie_read_debug_regions(struct btintel_pcie_data *data)
+{
+ struct btintel_pcie_dbgc *dbgc = NULL;
+ struct btintel_pcie_mdbgc *mdbgc = NULL;
struct hci_dev *hdev = data->hdev;
- u8 *pdata, *p, buf_idx, hw_variant;
- struct intel_tlv *tlv;
- struct timespec64 now;
- struct tm tm_now;
- char fw_build[128];
- char ts[128];
- char vendor[64];
- char driver[64];
+ struct btintel_pcie_dump_entry *entry;
+ struct btintel_pcie_dump_file_hdr *file_hdr;
+ struct scatterlist *sg_dump_data;
+ u32 status_reg, wrap_reg;
+ u32 exception_dump_len;
+ u32 exc_addr;
+ u64 regions_mask = 0;
+ u8 hw_variant;
+ u32 smem_rd_addr = 0, smem_rd_size = 0;
+ u32 file_len;
+ u8 count;
+ int ret;
+ LIST_HEAD(dump_list);
if (!IS_ENABLED(CONFIG_DEV_COREDUMP))
return -EOPNOTSUPP;
+ if (btintel_pcie_is_mdbgc_supported(data)) {
+ mdbgc = &data->mdbgc;
+ count = mdbgc->count;
+ } else {
+ dbgc = &data->dbgc;
+ count = dbgc->count;
+ }
hw_variant = INTEL_HW_VARIANT(data->cnvi);
- switch (hw_variant) {
- case BTINTEL_HWID_BZRI:
- case BTINTEL_HWID_BZRIW:
+
+ if (hw_variant == BTINTEL_HWID_BZRI ||
+ hw_variant == BTINTEL_HWID_BZRIW) {
status_reg = BTINTEL_PCIE_DBGC_CUR_DBGBUFF_STATUS;
wrap_reg = BTINTEL_PCIE_DBGC_DBGBUFF_WRAP_ARND;
- break;
- case BTINTEL_HWID_SCP:
- case BTINTEL_HWID_SCP2:
- case BTINTEL_HWID_SCP2F:
+ } else if (hw_variant >= BTINTEL_HWID_SCP) {
status_reg = BTINTEL_PCIE_DBGC_CUR_DBGBUFF_STATUS_SCP;
wrap_reg = BTINTEL_PCIE_DBGC_DBGBUFF_WRAP_ARND_SCP;
- break;
- default:
- bt_dev_err(hdev, "Unsupported Intel hardware variant (0x%2.2x)",
+ } else {
+ bt_dev_err(hdev,
+ "Unsupported Intel hardware variant (0x%2.2x)",
hw_variant);
return -EINVAL;
}
- wr_ptr_status = btintel_pcie_rd_dev_mem(data, status_reg);
- data->dmp_hdr.wrap_ctr = btintel_pcie_rd_dev_mem(data, wrap_reg);
+ smem_rd_addr = data->dump_info.smem_addr_start;
+ smem_rd_size = 0;
- offset = wr_ptr_status & BTINTEL_PCIE_DBG_OFFSET_BIT_MASK;
+ if (!smem_rd_addr && !data->dump_info.smem_addr_end) {
+ bt_dev_dbg(hdev, "smem region not advertised by firmware");
+ } else if (data->dump_info.smem_addr_end < smem_rd_addr ||
+ data->dump_info.smem_addr_end > U32_MAX - 0x04) {
+ bt_dev_err(hdev,
+ "Invalid smem region: start=0x%08x end=0x%08x",
+ smem_rd_addr, data->dump_info.smem_addr_end);
+ } else {
+ smem_rd_size = (data->dump_info.smem_addr_end + 0x04) -
+ smem_rd_addr;
- buf_idx = BTINTEL_PCIE_DBGC_DBG_BUF_IDX(wr_ptr_status);
- if (buf_idx > dbgc->count) {
- bt_dev_warn(hdev, "Buffer index is invalid");
- return -EINVAL;
+ bt_dev_dbg(hdev,
+ "smem_region: smem_start_addr=0x%08x smem_end_addr=0x%08x smem_rd_size=%u",
+ smem_rd_addr, data->dump_info.smem_addr_end,
+ smem_rd_size);
+
+ if (smem_rd_size == 0 ||
+ smem_rd_size > BTINTEL_PCIE_SMEM_MAX_SIZE) {
+ bt_dev_err(hdev,
+ "Invalid smem region: smem_rd_addr 0x%08x size %u (max %u)",
+ smem_rd_addr, smem_rd_size,
+ BTINTEL_PCIE_SMEM_MAX_SIZE);
+ smem_rd_size = 0;
+ }
}
- prev_size = buf_idx * BTINTEL_PCIE_DBGC_BUFFER_SIZE;
- if (prev_size + offset >= prev_size)
- data->dmp_hdr.write_ptr = prev_size + offset;
- else
- return -EINVAL;
+ if (btintel_pcie_is_mdbgc_supported(data)) {
+ ret = btintel_pcie_dump_dram_monitor(data, &dump_list, count,
+ mdbgc->buf1, status_reg,
+ wrap_reg,
+ BTINTEL_PCIE_MDBGC_ALLOCATIONID_1,
+ BTINTEL_PCIE_INI_ID_DRAM_MONITOR1,
+ "monitor");
+ if (!ret)
+ regions_mask |=
+ BIT_ULL(BTINTEL_PCIE_INI_ID_DRAM_MONITOR1);
+ else
+ bt_dev_warn(hdev, "Failed to dump DRAM buf1: %d", ret);
+
+ ret = btintel_pcie_dump_dram_monitor(data, &dump_list, count,
+ mdbgc->buf2, status_reg,
+ wrap_reg,
+ BTINTEL_PCIE_MDBGC_ALLOCATIONID_2,
+ BTINTEL_PCIE_INI_ID_DRAM_MONITOR2,
+ "monitor2");
+ if (!ret)
+ regions_mask |=
+ BIT_ULL(BTINTEL_PCIE_INI_ID_DRAM_MONITOR2);
+ else
+ bt_dev_warn(hdev, "Failed to dump DRAM buf2: %d", ret);
+
+ ret = btintel_pcie_dump_dram_monitor(data, &dump_list, count,
+ mdbgc->buf3, status_reg,
+ wrap_reg,
+ BTINTEL_PCIE_MDBGC_ALLOCATIONID_3,
+ BTINTEL_PCIE_INI_ID_DRAM_MONITOR3,
+ "monitor3");
+ if (!ret)
+ regions_mask |=
+ BIT_ULL(BTINTEL_PCIE_INI_ID_DRAM_MONITOR3);
+ else
+ bt_dev_warn(hdev, "Failed to dump DRAM buf3: %d", ret);
+ } else {
+ ret = btintel_pcie_dump_dram_monitor(data, &dump_list, count,
+ dbgc->bufs, status_reg,
+ wrap_reg,
+ BTINTEL_PCIE_MDBGC_ALLOCATIONID_1,
+ BTINTEL_PCIE_INI_ID_DRAM_MONITOR1,
+ "monitor");
+ if (!ret)
+ regions_mask |=
+ BIT_ULL(BTINTEL_PCIE_INI_ID_DRAM_MONITOR1);
+ else
+ bt_dev_warn(hdev,
+ "Failed to dump DRAM region: %d", ret);
+ }
- strscpy(vendor, "Vendor: Intel\n");
- snprintf(driver, sizeof(driver), "Driver: %s\n",
- data->dmp_hdr.driver_name);
-
- ktime_get_real_ts64(&now);
- time64_to_tm(now.tv_sec, 0, &tm_now);
- snprintf(ts, sizeof(ts), "Dump Time: %02d-%02d-%04ld %02d:%02d:%02d",
- tm_now.tm_mday, tm_now.tm_mon + 1, tm_now.tm_year + 1900,
- tm_now.tm_hour, tm_now.tm_min, tm_now.tm_sec);
-
- snprintf(fw_build, sizeof(fw_build),
- "Firmware Timestamp: Year %u WW %02u buildtype %u build %u",
- 2000 + (data->dmp_hdr.fw_timestamp >> 8),
- data->dmp_hdr.fw_timestamp & 0xff, data->dmp_hdr.fw_build_type,
- data->dmp_hdr.fw_build_num);
-
- data_len = sizeof(*tlv) + sizeof(data->dmp_hdr.cnvi_bt) +
- sizeof(*tlv) + sizeof(data->dmp_hdr.write_ptr) +
- sizeof(*tlv) + sizeof(data->dmp_hdr.wrap_ctr) +
- sizeof(*tlv) + sizeof(data->dmp_hdr.trigger_reason) +
- sizeof(*tlv) + sizeof(data->dmp_hdr.fw_sha) +
- sizeof(*tlv) + sizeof(data->dmp_hdr.cnvr_top) +
- sizeof(*tlv) + sizeof(data->dmp_hdr.cnvi_top) +
- sizeof(*tlv) + strlen(ts) +
- sizeof(*tlv) + strlen(fw_build) +
- sizeof(*tlv) + strlen(vendor) +
- sizeof(*tlv) + strlen(driver);
-
- if (data->dmp_hdr.event_type && data->dmp_hdr.event_id) {
- data_len += sizeof(*tlv) + sizeof(data->dmp_hdr.event_type);
- data_len += sizeof(*tlv) + sizeof(data->dmp_hdr.event_id);
+ if (smem_rd_size &&
+ !btintel_pcie_dump_smem_monitor_region(data, &dump_list,
+ BTINTEL_PCIE_INI_ID_SMEM,
+ "monitor_smem",
+ smem_rd_addr,
+ smem_rd_size))
+ regions_mask |= BIT_ULL(BTINTEL_PCIE_INI_ID_SMEM);
+
+ exc_addr = data->dump_info.exception_dump_addr;
+ exception_dump_len = data->dump_info.exception_dump_len;
+ if (exc_addr && exception_dump_len) {
+ ret = btintel_pcie_dump_target_region(data, &dump_list,
+ BTINTEL_PCIE_INI_ID_EXCEPTION_EVT,
+ "EXCEPTION_EVT_BUFFER", exc_addr,
+ exception_dump_len);
+ if (!ret)
+ regions_mask |= BIT_ULL(BTINTEL_PCIE_INI_ID_EXCEPTION_EVT);
}
- /*
- * sizeof(u32) - signature
- * sizeof(data_len) - to store tlv data size
- * data_len - TLV data
- */
- dump_size = sizeof(u32) + sizeof(data_len) + data_len;
+ btintel_pcie_dump_mem_range(data, &dump_list,
+ BTINTEL_PCIE_INI_ID_DCCM, "DCCM",
+ data->dump_info.dccm_addr_start,
+ data->dump_info.dccm_addr_end,
+ ®ions_mask);
+
+ btintel_pcie_dump_mem_range(data, &dump_list,
+ BTINTEL_PCIE_INI_ID_SDS, "SDS",
+ data->dump_info.sds_start_addr_start,
+ data->dump_info.sds_start_addr_end,
+ ®ions_mask);
+
+ btintel_pcie_dump_mem_range(data, &dump_list,
+ BTINTEL_PCIE_INI_ID_SDS_IOSF, "SDS_IOSF",
+ data->dump_info.sds_iosf_data_addr_start,
+ data->dump_info.sds_iosf_data_addr_end,
+ ®ions_mask);
+
+ btintel_pcie_dump_mem_range(data, &dump_list,
+ BTINTEL_PCIE_INI_ID_ECL, "ECL_REGION",
+ data->dump_info.ecl_addr_start,
+ data->dump_info.ecl_addr_end,
+ ®ions_mask);
+
+ ret = btintel_pcie_dump_info(data, &dump_list, regions_mask);
+ if (ret) {
+ btintel_pcie_dump_list_free(&dump_list);
+ return ret;
+ }
+ file_len = sizeof(*file_hdr) +
+ btintel_pcie_dump_list_total_size(&dump_list);
+ entry = btintel_pcie_dump_entry_alloc(sizeof(*file_hdr));
+ if (!entry) {
+ btintel_pcie_dump_list_free(&dump_list);
+ return -ENOMEM;
+ }
- /* Add debug buffers data length to dump size */
- dump_size += BTINTEL_PCIE_DBGC_BUFFER_SIZE * dbgc->count;
+ file_hdr = (void *)entry->data;
+ file_hdr->barker = cpu_to_le32(BTINTEL_PCIE_INI_ERROR_DUMP_BARKER);
+ file_hdr->file_len = cpu_to_le32(file_len);
+ list_add(&entry->list, &dump_list);
- pdata = vmalloc(dump_size);
- if (!pdata)
- return -ENOMEM;
- p = pdata;
-
- *(u32 *)p = BTINTEL_PCIE_MAGIC_NUM;
- p += sizeof(u32);
-
- *(u32 *)p = data_len;
- p += sizeof(u32);
-
-
- p = btintel_pcie_copy_tlv(p, BTINTEL_VENDOR, vendor, strlen(vendor));
- p = btintel_pcie_copy_tlv(p, BTINTEL_DRIVER, driver, strlen(driver));
- p = btintel_pcie_copy_tlv(p, BTINTEL_DUMP_TIME, ts, strlen(ts));
- p = btintel_pcie_copy_tlv(p, BTINTEL_FW_BUILD, fw_build,
- strlen(fw_build));
- p = btintel_pcie_copy_tlv(p, BTINTEL_CNVI_BT, &data->dmp_hdr.cnvi_bt,
- sizeof(data->dmp_hdr.cnvi_bt));
- p = btintel_pcie_copy_tlv(p, BTINTEL_WRITE_PTR, &data->dmp_hdr.write_ptr,
- sizeof(data->dmp_hdr.write_ptr));
- p = btintel_pcie_copy_tlv(p, BTINTEL_WRAP_CTR, &data->dmp_hdr.wrap_ctr,
- sizeof(data->dmp_hdr.wrap_ctr));
- p = btintel_pcie_copy_tlv(p, BTINTEL_TRIGGER_REASON, &data->dmp_hdr.trigger_reason,
- sizeof(data->dmp_hdr.trigger_reason));
- p = btintel_pcie_copy_tlv(p, BTINTEL_FW_SHA, &data->dmp_hdr.fw_sha,
- sizeof(data->dmp_hdr.fw_sha));
- p = btintel_pcie_copy_tlv(p, BTINTEL_CNVR_TOP, &data->dmp_hdr.cnvr_top,
- sizeof(data->dmp_hdr.cnvr_top));
- p = btintel_pcie_copy_tlv(p, BTINTEL_CNVI_TOP, &data->dmp_hdr.cnvi_top,
- sizeof(data->dmp_hdr.cnvi_top));
-
- if (data->dmp_hdr.event_type && data->dmp_hdr.event_id) {
- p = btintel_pcie_copy_tlv(p, BTINTEL_EVENT_TYPE,
- &data->dmp_hdr.event_type,
- sizeof(data->dmp_hdr.event_type));
- p = btintel_pcie_copy_tlv(p, BTINTEL_EVENT_ID,
- &data->dmp_hdr.event_id,
- sizeof(data->dmp_hdr.event_id));
- data->dmp_hdr.event_type = 0;
- data->dmp_hdr.event_id = 0;
- }
-
- memcpy(p, dbgc->bufs[0].data, dbgc->count * BTINTEL_PCIE_DBGC_BUFFER_SIZE);
- dev_coredumpv(&hdev->dev, pdata, dump_size, GFP_KERNEL);
- return 0;
+ sg_dump_data = btintel_pcie_alloc_sgtable(file_len);
+ if (sg_dump_data) {
+ int sg_entries = sg_nents(sg_dump_data);
+ u32 offs = 0;
+
+ list_for_each_entry(entry, &dump_list, list) {
+ sg_pcopy_from_buffer(sg_dump_data, sg_entries,
+ entry->data, entry->size, offs);
+ offs += entry->size;
+ }
+
+ bt_dev_dbg(hdev, "triggering dev_coredumpsg()");
+ dev_coredumpsg(&hdev->dev, sg_dump_data, file_len, GFP_KERNEL);
+ } else {
+ bt_dev_err(hdev, "Failed to allocate scatter-gather table for coredump");
+ ret = -ENOMEM;
+ }
+
+ btintel_pcie_dump_list_free(&dump_list);
+ return ret;
}
static void btintel_pcie_dump_traces(struct hci_dev *hdev)
{
struct btintel_pcie_data *data = hci_get_drvdata(hdev);
- int ret = 0;
+ int ret;
ret = btintel_pcie_get_mac_access(data);
if (ret) {
@@ -923,7 +1445,7 @@ static void btintel_pcie_dump_traces(struct hci_dev *hdev)
return;
}
- ret = btintel_pcie_read_dram_buffers(data);
+ ret = btintel_pcie_read_debug_regions(data);
btintel_pcie_release_mac_access(data);
@@ -1168,19 +1690,19 @@ static int btintel_parse_mbox_tlv(struct btintel_pcie_data *data)
ptr = buffer;
remaining = buffer_len;
- /* Parse TLV structures: type(1) + length(2) + value */
+ /* Parse TLV structures: 1 byte type + 2 bytes length +
+ * variable value
+ */
while (remaining >= sizeof(struct mbox_tlv)) {
u16 tlv_len;
u32 tlv_total;
tlv = (struct mbox_tlv *)ptr;
tlv_len = le16_to_cpu(tlv->len);
- tlv_total = sizeof(tlv->type) +
- sizeof(tlv->len) + tlv_len;
+ tlv_total = sizeof(tlv->type) + sizeof(tlv->len) + tlv_len;
if (tlv_total > remaining) {
- bt_dev_err(data->hdev,
- "TLV parse error: type=%u, len=%u",
+ bt_dev_err(data->hdev, "TLV parse error: not enough data for TLV value (type=%u, len=%u)",
tlv->type, tlv_len);
break;
}
@@ -1189,7 +1711,7 @@ static int btintel_parse_mbox_tlv(struct btintel_pcie_data *data)
case BTINTEL_PCIE_TLV_TYPE_EXCEPTION_DUMP_ADDRESS:
if (tlv_len < 8) {
bt_dev_err(data->hdev,
- "TLV %s too short: %u",
+ "TLV %s too short: len=%u (need 8)",
btintel_pcie_tlv_str(tlv->type),
tlv_len);
break;
@@ -1202,7 +1724,7 @@ static int btintel_parse_mbox_tlv(struct btintel_pcie_data *data)
case BTINTEL_PCIE_TLV_TYPE_DCCM_MEM_ADDRESS:
if (tlv_len < 8) {
bt_dev_err(data->hdev,
- "TLV %s too short: %u",
+ "TLV %s too short: len=%u (need 8)",
btintel_pcie_tlv_str(tlv->type),
tlv_len);
break;
@@ -1221,8 +1743,7 @@ static int btintel_parse_mbox_tlv(struct btintel_pcie_data *data)
bt_dev_dbg(data->hdev, "SDS TLV: skipped, hw_variant not yet known");
break;
}
- if (tlv_len == 16 &&
- hw_variant > BTINTEL_HWID_BZRI) {
+ if (tlv_len == 16 && hw_variant > BTINTEL_HWID_BZRI) {
data->dump_info.sds_start_addr_start =
get_unaligned_le32(&tlv->val[0]);
data->dump_info.sds_start_addr_end =
@@ -1255,7 +1776,7 @@ static int btintel_parse_mbox_tlv(struct btintel_pcie_data *data)
case BTINTEL_PCIE_TLV_TYPE_ECL_MEM_ADDRESS:
if (tlv_len < 8) {
bt_dev_err(data->hdev,
- "TLV %s too short: %u",
+ "TLV %s too short: len=%u (need 8)",
btintel_pcie_tlv_str(tlv->type),
tlv_len);
break;
@@ -1268,7 +1789,7 @@ static int btintel_parse_mbox_tlv(struct btintel_pcie_data *data)
case BTINTEL_PCIE_TLV_TYPE_SMEM_ADDRESS:
if (tlv_len < 8) {
bt_dev_err(data->hdev,
- "TLV %s too short: %u",
+ "TLV %s too short: len=%u (need 8)",
btintel_pcie_tlv_str(tlv->type),
tlv_len);
break;
@@ -1279,8 +1800,7 @@ static int btintel_parse_mbox_tlv(struct btintel_pcie_data *data)
get_unaligned_le32(&tlv->val[4]);
break;
default:
- bt_dev_dbg(data->hdev,
- "Unknown TLV type: %u length: %u",
+ bt_dev_dbg(data->hdev, "Unknown TLV type: %u length: %u",
tlv->type, tlv_len);
break;
}
@@ -1868,11 +2388,8 @@ static int btintel_pcie_dump_fwtrigger_event(struct btintel_pcie_data *data)
goto exit_on_error;
evt = (void *)buf;
- data->dmp_hdr.event_type = evt->event_type;
- data->dmp_hdr.event_id = le16_to_cpu(evt->event_id);
-
bt_dev_dbg(data->hdev, "event type: 0x%2.2x event id: 0x%4.4x len: %u",
- data->dmp_hdr.event_type, data->dmp_hdr.event_id, len);
+ evt->event_type, le16_to_cpu(evt->event_id), len);
skb = bt_skb_alloc(len, GFP_KERNEL);
if (!skb) {
@@ -1898,8 +2415,8 @@ static int btintel_pcie_dump_fwtrigger_event(struct btintel_pcie_data *data)
*
* Always queue this AFTER any companion event-reader work (hwexp /
* fwtrigger) so that, on the ordered @dump_workqueue, the event reader
- * runs first and populates dmp_hdr.event_type / event_id before
- * dump_traces consumes them.
+ * runs first and the trigger metadata is populated before dump_traces
+ * consumes it.
*/
static bool btintel_pcie_queue_coredump(struct btintel_pcie_data *data,
u16 trigger_reason)
@@ -1982,12 +2499,6 @@ static void btintel_pcie_coredump_worker(struct work_struct *work)
goto out;
}
- if (btintel_pcie_is_mdbgc_supported(data)) {
- bt_dev_info(data->hdev,
- "Skipping coredump: MDBGC dump format not supported yet");
- goto out;
- }
-
btintel_pcie_dump_traces(data->hdev);
out:
/* Release guard last so a new trigger can run only after this
diff --git a/drivers/bluetooth/btintel_pcie.h b/drivers/bluetooth/btintel_pcie.h
index 5c35e65d3e81..9d84db48a8e5 100644
--- a/drivers/bluetooth/btintel_pcie.h
+++ b/drivers/bluetooth/btintel_pcie.h
@@ -74,11 +74,16 @@
#define BTINTEL_PCIE_DBGC_BASE_ADDR_SCP (0xf0d5d500)
#define BTINTEL_PCIE_DBGC_CUR_DBGBUFF_STATUS_SCP (BTINTEL_PCIE_DBGC_BASE_ADDR_SCP + 0x1C)
#define BTINTEL_PCIE_DBGC_DBGBUFF_WRAP_ARND_SCP (BTINTEL_PCIE_DBGC_BASE_ADDR_SCP + 0x2C)
+#define BTINTEL_PCIE_DBGC_ALLOCATION_OFFSET 0x100
+#define BTINTEL_PCIE_SMEM_MAX_SIZE (16 * 1024)
+#define BTINTEL_PCIE_REGION_MAX_SIZE (16 * 1024 * 1024)
#define BTINTEL_PCIE_DBG_IDX_BIT_MASK 0x0F
#define BTINTEL_PCIE_DBGC_DBG_BUF_IDX(data) (((data) >> 24) & BTINTEL_PCIE_DBG_IDX_BIT_MASK)
#define BTINTEL_PCIE_DBG_OFFSET_BIT_MASK 0xFFFFFF
+#define BTINTEL_PCIE_TARGET_ACCESS_FRAG_OFFSET 4
+
/* The DRAM buffer count, each buffer size, and
* fragment buffer size
*/
@@ -242,7 +247,7 @@ enum {
#define BTINTEL_PCIE_TLV_TYPE_DCCM_MEM_ADDRESS 0x05
#define BTINTEL_PCIE_TLV_TYPE_SDS_MEM_ADDRESS 0x06
#define BTINTEL_PCIE_TLV_TYPE_ECL_MEM_ADDRESS 0x07
-#define BTINTEL_PCIE_TLV_TYPE_SMEM_ADDRESS 0x08
+#define BTINTEL_PCIE_TLV_TYPE_SMEM_ADDRESS 0x08
/*
* Struct for Context Information (v2)
@@ -527,10 +532,147 @@ struct btintel_pcie_dump_header {
u32 wrap_ctr;
u16 trigger_reason;
int state;
- u8 event_type;
- u16 event_id;
};
+/* Per-fragment range descriptor for dump regions.
+ * Binary-compatible with iwl_fw_ini_error_dump_range.
+ */
+struct btintel_pcie_dump_range {
+ __le32 range_data_size;
+ union {
+ __le32 internal_base_addr;
+ __le64 dram_base_addr;
+ __le32 page_num;
+ };
+ __le32 data[];
+} __packed;
+
+/*
+ * INI region types for ini_dump_data.type field.
+ * The unified decoder dispatches parsing logic based on these values.
+ */
+#define BTINTEL_PCIE_INI_REGION_INTERNAL_BUFFER 2
+#define BTINTEL_PCIE_INI_REGION_DRAM_BUFFER 3
+#define BTINTEL_PCIE_INI_REGION_DEVICE_MEMORY 9
+
+/* INI region IDs - used in dump header region_id field and regions_mask */
+#define BTINTEL_PCIE_INI_ID_EXCEPTION_EVT 7
+#define BTINTEL_PCIE_INI_ID_SMEM 15
+#define BTINTEL_PCIE_INI_ID_DCCM 39
+#define BTINTEL_PCIE_INI_ID_SDS 40
+#define BTINTEL_PCIE_INI_ID_SDS_IOSF 41
+#define BTINTEL_PCIE_INI_ID_ECL 42
+#define BTINTEL_PCIE_INI_ID_DRAM_MONITOR3 61
+#define BTINTEL_PCIE_INI_ID_DRAM_MONITOR2 62
+#define BTINTEL_PCIE_INI_ID_DRAM_MONITOR1 63
+
+/*
+ * INI (Intel INI debug infrastructure) style dump data TLV - wraps each dump
+ * region. INI is the iwlwifi firmware debug format used by the unified decoder.
+ * Compatible with iwl_fw_ini_error_dump_data.
+ */
+struct btintel_pcie_ini_dump_data {
+ u8 type;
+ u8 sub_type;
+ u8 sub_type_ver;
+ u8 reserved;
+ __le32 len;
+ u8 data[];
+} __packed;
+
+/*
+ * INI-style region dump header.
+ * Compatible with iwl_fw_ini_error_dump_header.
+ */
+#define BTINTEL_PCIE_INI_MAX_NAME 32
+#define BTINTEL_PCIE_INI_DUMP_VER 1
+
+struct btintel_pcie_ini_dump_header {
+ __le32 version;
+ __le32 region_id;
+ __le32 num_of_ranges;
+ __le32 name_len;
+ u8 name[BTINTEL_PCIE_INI_MAX_NAME];
+};
+
+/*
+ * INI-style monitor dump - region header + monitor state.
+ * Compatible with iwl_fw_ini_monitor_dump.
+ */
+struct btintel_pcie_ini_monitor_dump {
+ struct btintel_pcie_ini_dump_header header;
+ __le32 write_ptr;
+ __le32 cycle_cnt;
+ __le32 cur_frag;
+ u8 data[];
+} __packed;
+
+/* Linked list entry for modular dump collection */
+struct btintel_pcie_dump_entry {
+ struct list_head list;
+ u32 size;
+ u8 data[];
+};
+
+/* File-level header for coredump output.
+ * Compatible with iwl_fw_ini_dump_file_hdr.
+ * Uses IWL_FW_INI_ERROR_DUMP_BARKER (0x14789633) for decoder compatibility.
+ */
+#define BTINTEL_PCIE_INI_ERROR_DUMP_BARKER 0x14789633
+
+struct btintel_pcie_dump_file_hdr {
+ __le32 barker;
+ __le32 file_len;
+} __packed;
+
+/*
+ * Legacy-style dump data wrapper for dump info TLV.
+ * Compatible with iwl_fw_error_dump_data.
+ * Used only for the dump info entry (type=BTINTEL_PCIE_INI_DUMP_INFO_TYPE).
+ */
+struct btintel_pcie_error_dump_data {
+ __le32 type;
+ __le32 len;
+ u8 data[];
+} __packed;
+
+/* Use bit 31 as dump info type, matching IWL_INI_DUMP_INFO_TYPE */
+#define BTINTEL_PCIE_INI_DUMP_INFO_TYPE BIT(31)
+
+/* Time point values matching iwl_fw_ini_time_point for fwdump parser */
+#define BTINTEL_PCIE_TIME_POINT_FW_ASSERT 4
+#define BTINTEL_PCIE_TIME_POINT_USER_TRIGGER 9
+
+/*
+ * Dump info struct - single TLV containing all metadata.
+ * Compatible with iwl_fw_ini_dump_info.
+ * Packs all device/firmware info that the decoder needs.
+ */
+struct btintel_pcie_ini_dump_info {
+ __le32 version;
+ __le32 time_point;
+ __le32 trigger_reason;
+ __le32 external_cfg_state;
+ __le32 ver_type;
+ __le32 ver_subtype;
+ __le32 hw_step;
+ __le32 hw_type;
+ __le32 rf_id_flavor;
+ __le32 rf_id_dash;
+ __le32 rf_id_step;
+ __le32 rf_id_type;
+ __le32 lmac_major;
+ __le32 lmac_minor;
+ __le32 umac_major;
+ __le32 umac_minor;
+ __le32 fw_mon_mode;
+ __le64 regions_mask;
+ __le32 build_tag_len;
+ u8 build_tag[64];
+ __le32 num_of_cfg_names;
+ /* no cfg_names for BT - keep zero-length */
+} __packed;
+
/* struct btintel_pcie_data
* @pdev: pci device
* @hdev: hdev device
@@ -688,4 +830,3 @@ static inline u32 btintel_pcie_rd_dev_mem(struct btintel_pcie_data *data,
btintel_pcie_wr_reg32(data, BTINTEL_PCIE_PRPH_DEV_ADDR_REG, addr);
return btintel_pcie_rd_reg32(data, BTINTEL_PCIE_PRPH_DEV_RD_REG);
}
-
--
2.43.0
^ permalink raw reply related [flat|nested] 15+ messages in thread* RE: [v2,3/3] Bluetooth: btintel_pcie: unified decoder coredump format
2026-09-04 0:29 ` [PATCH v2 3/3] Bluetooth: btintel_pcie: unified decoder coredump format Chandrashekar Devegowda
@ 2026-09-04 0:46 ` bluez.test.bot
2026-09-04 9:49 ` [PATCH v2 3/3] " Paul Menzel
2026-09-04 17:50 ` patchwork-bot+bluetooth
2 siblings, 0 replies; 15+ messages in thread
From: bluez.test.bot @ 2026-09-04 0:46 UTC (permalink / raw)
To: linux-bluetooth, chandrashekar.devegowda
[-- Attachment #1: Type: text/plain, Size: 566 bytes --]
This is an automated email and please do not reply to this email.
Dear Submitter,
Thank you for submitting the patches to the linux bluetooth mailing list.
While preparing the CI tests, the patches you submitted couldn't be applied to the current HEAD of the repository.
----- Output -----
error: patch failed: drivers/bluetooth/btintel_pcie.c:198
error: drivers/bluetooth/btintel_pcie.c: patch does not apply
hint: Use 'git am --show-current-patch' to see the failed patch
Please resolve the issue and submit the patches again.
---
Regards,
Linux Bluetooth
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH v2 3/3] Bluetooth: btintel_pcie: unified decoder coredump format
2026-09-04 0:29 ` [PATCH v2 3/3] Bluetooth: btintel_pcie: unified decoder coredump format Chandrashekar Devegowda
2026-09-04 0:46 ` [v2,3/3] " bluez.test.bot
@ 2026-09-04 9:49 ` Paul Menzel
2026-09-04 17:50 ` patchwork-bot+bluetooth
2 siblings, 0 replies; 15+ messages in thread
From: Paul Menzel @ 2026-09-04 9:49 UTC (permalink / raw)
To: Chandrashekar Devegowda
Cc: ravishankar.srivatsa, chethan.tumkur.narayan, kiran.k,
linux-bluetooth
Dear Chandrashekar,
Thank you for your patch.
Am 04.09.26 um 02:29 schrieb Chandrashekar Devegowda:
> Replace the legacy TLV-based coredump with a unified INI-format dump
Please mention *Intel INI debug infrastructure*, and most importantly,
where the format is described. What does INI abbreviate?
The commit message summary/title could also be more specific, and be
made a statement. Maybe:
Bluetooth: btintel_pcie: Convert to unified INI decoder coredump format
> compatible with the iwlwifi decoder so a single decoder can parse
> coredumps across all Intel BT PCIe controller variants.
>
> The dump is emitted via dev_coredumpsg() as structured regions built
> from firmware-advertised addresses: DRAM monitor buffers (single or
> MDBGC multi-buffer), SMEM monitor, exception event buffer, DCCM, SDS,
> SDS IOSF, ECL and dump info metadata. Regions are collected only when
> firmware advertises a valid address and size, and only collected
> regions are advertised in regions_mask.
>
> Add INTEL_CNVX_TOP_DASH() and INTEL_CNVX_TOP_FLAVOR() helpers to
> btintel.h for RfIdDash/RfIdFlavor extraction.
Please document how this can be tested.
> Assisted-by: Copilot:gemini-3.6-flash
> Signed-off-by: Chandrashekar Devegowda <chandrashekar.devegowda@intel.com>
> ---
> Changes in v2:
> - Reworded the commit message for brevity.
> - Add btintel_pcie_dump_dram_monitor() helper that reads the
> per-allocation status/wrap registers (0x100 * allocation_id
> offset) for each MDBGC pool, stores the per-fragment DWORD write
> offset in mon_dump->write_ptr, and stores the active fragment
> index in mon_dump->cur_frag, replacing the previously flattened
> write_ptr / hard-coded cur_frag = 0 encoding.
> - btintel_pcie_dump_target_region() now returns a skip indicator
> (leaving regions_mask untouched) when the firmware does not
> advertise a valid region, avoiding a spurious
> "Skipping empty dump region: EXCEPTION_EVT_BUFFER" warning when
> the exception dump TLV is absent. It also skips when the size
> rounded down to 4-byte alignment collapses to zero.
> - Log via bt_dev_dbg (instead of bt_dev_err) when firmware does
> not advertise an SMEM monitor region, so the absence of an
> optional TLV is not reported as an error.
>
> drivers/bluetooth/btintel.h | 2 +
> drivers/bluetooth/btintel_pcie.c | 887 ++++++++++++++++++++++++-------
> drivers/bluetooth/btintel_pcie.h | 149 +++++-
> 3 files changed, 846 insertions(+), 192 deletions(-)
>
> diff --git a/drivers/bluetooth/btintel.h b/drivers/bluetooth/btintel.h
> index ef232820c31b..c00f793d9dc8 100644
> --- a/drivers/bluetooth/btintel.h
> +++ b/drivers/bluetooth/btintel.h
> @@ -225,7 +225,9 @@ struct btintel_sar_rev2 {
> #define INTEL_HW_PLATFORM(cnvx_bt) ((u8)(((cnvx_bt) & 0x0000ff00) >> 8))
> #define INTEL_HW_VARIANT(cnvx_bt) ((u8)(((cnvx_bt) & 0x003f0000) >> 16))
> #define INTEL_CNVX_TOP_TYPE(cnvx_top) ((cnvx_top) & 0x00000fff)
> +#define INTEL_CNVX_TOP_DASH(cnvx_top) (((cnvx_top) & 0x00f00000) >> 20)
> #define INTEL_CNVX_TOP_STEP(cnvx_top) (((cnvx_top) & 0x0f000000) >> 24)
> +#define INTEL_CNVX_TOP_FLAVOR(cnvx_top) (((cnvx_top) & 0xf0000000) >> 28)
> #define INTEL_CNVX_TOP_PACK_SWAB(t, s) __swab16(((__u16)(((t) << 4) | (s))))
>
> enum {
> diff --git a/drivers/bluetooth/btintel_pcie.c b/drivers/bluetooth/btintel_pcie.c
> index 233cb8a3cbd4..38a6dbd04dcf 100644
> --- a/drivers/bluetooth/btintel_pcie.c
> +++ b/drivers/bluetooth/btintel_pcie.c
> @@ -19,6 +19,7 @@
>
> #include <linux/unaligned.h>
> #include <linux/devcoredump.h>
> +#include <linux/scatterlist.h>
>
> #include <net/bluetooth/bluetooth.h>
> #include <net/bluetooth/hci_core.h>
> @@ -198,7 +199,15 @@ static inline bool btintel_pcie_dbg_to_wifi(struct btintel_pcie_data *data)
> return data->dbg_path_cache != BTINTEL_PCIE_DRAM;
> }
>
> -/* Helper function to allocate and setup a debug buffer group */
> +/* Helper function to allocate and setup a debug buffer group
> + * @data: driver data structure
> + * @buf: pointer to data_buf array pointer
> + * @p_addr: pointer to physical DMA address
> + * @v_addr: pointer to virtual address
> + * @frag: pointer to fragment buffer array
> + * @buf_index: buffer index (for error messages)
> + * @buf_count: number of buffers to allocate
> + */
> static int btintel_pcie_alloc_dbgc_buf(struct btintel_pcie_data *data,
> struct data_buf **buf,
> dma_addr_t *p_addr,
> @@ -213,39 +222,36 @@ static int btintel_pcie_alloc_dbgc_buf(struct btintel_pcie_data *data,
> *buf = devm_kcalloc(&data->pdev->dev, buf_count,
> sizeof(**buf), GFP_KERNEL);
> if (!*buf) {
> - BT_ERR("Failed to allocate dbgc buf: %u",
> - buf_index + 1);
> + BT_ERR("Failed to allocate dbgc buf: %u", buf_index + 1);
> return -ENOMEM;
> }
>
> *v_addr = dmam_alloc_coherent(&data->pdev->dev,
> buf_count *
> - BTINTEL_PCIE_DBGC_BUFFER_SIZE,
> - p_addr,
> - GFP_KERNEL | __GFP_NOWARN);
> + BTINTEL_PCIE_DBGC_BUFFER_SIZE,
> + p_addr,
> + GFP_KERNEL | __GFP_NOWARN);
> if (!*v_addr) {
> - BT_ERR("Failed to allocate dbgc buf: %u DMA",
> - buf_index + 1);
> + BT_ERR("Failed to allocate dbgc buf: %u DMA", buf_index + 1);
> return -ENOMEM;
> }
>
> for (i = 0; i < buf_count; i++) {
> b = &(*buf)[i];
> - b->data_p_addr = *p_addr +
> - i * BTINTEL_PCIE_DBGC_BUFFER_SIZE;
> - b->data = *v_addr +
> - i * BTINTEL_PCIE_DBGC_BUFFER_SIZE;
> - frag[i].buf_addr_lsb =
> - lower_32_bits(b->data_p_addr);
> - frag[i].buf_addr_msb =
> - upper_32_bits(b->data_p_addr);
> + b->data_p_addr = *p_addr + i * BTINTEL_PCIE_DBGC_BUFFER_SIZE;
> + b->data = *v_addr + i * BTINTEL_PCIE_DBGC_BUFFER_SIZE;
> + frag[i].buf_addr_lsb = lower_32_bits(b->data_p_addr);
> + frag[i].buf_addr_msb = upper_32_bits(b->data_p_addr);
> frag[i].buf_size = BTINTEL_PCIE_DBGC_BUFFER_SIZE;
> }
>
> return 0;
> }
>
> -/* This function initializes the memory for MDBGC buffers */
> +/* This function initializes the memory for MDBGC buffers and formats the
> + * DBGC fragment which consists header info and DBGC buffer's LSB, MSB and
> + * size as the payload
> + */
> static int btintel_pcie_setup_mdbgc(struct btintel_pcie_data *data)
> {
> struct btintel_pcie_mdbgc_ctxt db_frag;
> @@ -255,15 +261,19 @@ static int btintel_pcie_setup_mdbgc(struct btintel_pcie_data *data)
>
> data->mdbgc.count = BTINTEL_PCIE_DBGC_BUFFER_COUNT;
>
> + /* Allocate fragment context structure */
I find the comment redundant. If it should be added, to reduce the
diffstat size, it’d be great to move the addition into a separate commit.
> frag_v_addr = dmam_alloc_coherent(&data->pdev->dev, frag_size,
> &data->mdbgc.frag_p_addr,
> GFP_KERNEL | __GFP_NOWARN);
> - if (!frag_v_addr)
> + if (!frag_v_addr) {
> + BT_ERR("Failed to allocate mdbgc context");
> return -ENOMEM;
> + }
Ditto regarding a separate commit to make review easier. (A possible
revert would also loose the newly added messages.)
>
> data->mdbgc.frag_v_addr = frag_v_addr;
> data->mdbgc.frag_size = frag_size;
>
> + /* Initialize fragment header */
> memset(&db_frag, 0, sizeof(db_frag));
> db_frag.magic_num = BTINTEL_PCIE_MAGIC_NUM;
> db_frag.ver = BTINTEL_PCIE_MDBGC_FRAG_VERSION;
> @@ -275,7 +285,8 @@ static int btintel_pcie_setup_mdbgc(struct btintel_pcie_data *data)
> &data->mdbgc.buf1,
> &data->mdbgc.buf1_p_addr,
> &data->mdbgc.buf1_v_addr,
> - db_frag.buf1, 0,
> + db_frag.buf1,
> + 0,
> data->mdbgc.count);
> if (err)
> return err;
> @@ -287,7 +298,8 @@ static int btintel_pcie_setup_mdbgc(struct btintel_pcie_data *data)
> &data->mdbgc.buf2,
> &data->mdbgc.buf2_p_addr,
> &data->mdbgc.buf2_v_addr,
> - db_frag.buf2, 1,
> + db_frag.buf2,
> + 1,
> data->mdbgc.count);
> if (err)
> return err;
> @@ -299,58 +311,64 @@ static int btintel_pcie_setup_mdbgc(struct btintel_pcie_data *data)
> &data->mdbgc.buf3,
> &data->mdbgc.buf3_p_addr,
> &data->mdbgc.buf3_v_addr,
> - db_frag.buf3, 2,
> + db_frag.buf3,
> + 2,
> data->mdbgc.count);
> if (err)
> return err;
>
> + /* Copy fragment to DMA coherent memory */
> memcpy(data->mdbgc.frag_v_addr, &db_frag, sizeof(db_frag));
> return 0;
> }
>
> -/* This function initializes the memory for DBGC buffers */
> +/* This function initializes the memory for DBGC buffers and formats the
> + * DBGC fragment which consists header info and DBGC buffer's LSB, MSB and
> + * size as the payload
> + */
> static int btintel_pcie_setup_dbgc(struct btintel_pcie_data *data)
> {
> struct btintel_pcie_dbgc_ctxt db_frag;
> + u32 frag_size = sizeof(db_frag);
> int err;
>
> data->dbgc.count = BTINTEL_PCIE_DBGC_BUFFER_COUNT;
>
> + /* Allocate fragment context structure */
> data->dbgc.frag_v_addr = dmam_alloc_coherent(&data->pdev->dev,
> - sizeof(struct btintel_pcie_dbgc_ctxt),
> - &data->dbgc.frag_p_addr,
> - GFP_KERNEL | __GFP_NOWARN);
> - if (!data->dbgc.frag_v_addr)
> + frag_size,
> + &data->dbgc.frag_p_addr,
> + GFP_KERNEL | __GFP_NOWARN);
> + if (!data->dbgc.frag_v_addr) {
> + BT_ERR("Failed to allocate dbgc context");
> return -ENOMEM;
> + }
>
> data->dbgc.frag_size = sizeof(struct btintel_pcie_dbgc_ctxt);
>
> + /* Initialize fragment header */
> memset(&db_frag, 0, sizeof(db_frag));
> db_frag.magic_num = BTINTEL_PCIE_MAGIC_NUM;
> db_frag.ver = BTINTEL_PCIE_DBGC_FRAG_VERSION;
> db_frag.total_size = BTINTEL_PCIE_DBGC_FRAG_PAYLOAD_SIZE;
> db_frag.num_buf = BTINTEL_PCIE_DBGC_FRAG_BUFFER_COUNT;
>
> + /* Allocate DBGC buffers */
> err = btintel_pcie_alloc_dbgc_buf(data,
> &data->dbgc.bufs,
> &data->dbgc.buf_p_addr,
> &data->dbgc.buf_v_addr,
> - db_frag.bufs, 0,
> + db_frag.bufs,
> + 0,
> data->dbgc.count);
> if (err)
> return err;
>
> + /* Copy fragment to DMA coherent memory */
> memcpy(data->dbgc.frag_v_addr, &db_frag, sizeof(db_frag));
> return 0;
> }
>
> -static bool btintel_pcie_is_mdbgc_supported(struct btintel_pcie_data *data)
> -{
> - return data->pdev->device == BTINTEL_PCIE_DEVICE_ID_NVL_S_SCP2 ||
> - data->pdev->device == BTINTEL_PCIE_DEVICE_ID_NVL_Hx_SCP2 ||
> - data->pdev->device == BTINTEL_PCIE_DEVICE_ID_PTL_FMP2;
> -}
> -
> static inline void ipc_print_ia_ring(struct hci_dev *hdev, struct ia *ia,
> u16 queue_num)
> {
> @@ -753,169 +771,673 @@ static void btintel_pcie_release_mac_access(struct btintel_pcie_data *data)
> }
> }
>
> -static void *btintel_pcie_copy_tlv(void *dest, enum btintel_pcie_tlv_type type,
> - void *data, size_t size)
> +static struct scatterlist *btintel_pcie_alloc_sgtable(ssize_t size)
> {
> - struct intel_tlv *tlv;
> + int nents, i;
> + struct page *page;
> + struct scatterlist *sg, *result;
> +
> + if (size <= 0)
> + return NULL;
> +
> + nents = DIV_ROUND_UP(size, PAGE_SIZE);
> + result = kcalloc(nents, sizeof(*result), GFP_KERNEL);
> + if (!result)
> + return NULL;
> +
> + sg_init_table(result, nents);
> + sg = result;
>
> - tlv = dest;
> - tlv->type = type;
> - tlv->len = size;
> - memcpy(tlv->val, data, tlv->len);
> - return dest + sizeof(*tlv) + size;
> + for (i = 0; size > 0; i++) {
> + ssize_t bytes = min_t(ssize_t, size, PAGE_SIZE);
> +
> + page = alloc_page(GFP_KERNEL | __GFP_ZERO);
> + if (!page)
> + goto err_free;
> +
> + sg_set_page(sg, page, bytes, 0);
> + sg = sg_next(sg);
> + size -= bytes;
> + }
> + return result;
> +
> +err_free:
> + for (sg = result; sg; sg = sg_next(sg)) {
> + page = sg_page(sg);
> + if (page)
> + __free_page(page);
> + }
> + kfree(result);
> + return NULL;
> }
>
> -static int btintel_pcie_read_dram_buffers(struct btintel_pcie_data *data)
> +static struct btintel_pcie_dump_entry *
> +btintel_pcie_dump_entry_alloc(u32 data_size)
> {
> - u32 offset, prev_size, wr_ptr_status, dump_size, data_len;
> - u32 status_reg, wrap_reg;
> - struct btintel_pcie_dbgc *dbgc = &data->dbgc;
> + struct btintel_pcie_dump_entry *entry;
> +
> + entry = vzalloc(sizeof(*entry) + data_size);
> + if (!entry)
> + return NULL;
> +
> + entry->size = data_size;
> + INIT_LIST_HEAD(&entry->list);
> + return entry;
> +}
> +
> +static void btintel_pcie_dump_list_free(struct list_head *list)
> +{
> + struct btintel_pcie_dump_entry *entry, *tmp;
> +
> + list_for_each_entry_safe(entry, tmp, list, list) {
> + list_del(&entry->list);
> + vfree(entry);
> + }
> +}
> +
> +static u32 btintel_pcie_dump_list_total_size(struct list_head *list)
> +{
> + struct btintel_pcie_dump_entry *entry;
> + u32 total = 0;
> +
> + list_for_each_entry(entry, list, list)
> + total += entry->size;
> +
> + return total;
> +}
> +
> +static int btintel_pcie_dump_dram(struct list_head *list,
> + u8 count, struct data_buf *bufs,
> + u32 write_ptr, u32 wrap_ctr, u32 cur_frag,
> + u32 region_id, const char *name)
> +{
> + struct btintel_pcie_dump_entry *entry;
> + struct btintel_pcie_ini_dump_data *dump_data;
> + struct btintel_pcie_ini_monitor_dump *mon_dump;
> + struct btintel_pcie_dump_range *range;
> + u32 mon_hdr_size, ranges_size, payload_size, total_size;
> + int i;
> +
> + mon_hdr_size = sizeof(*mon_dump);
> + ranges_size = count * (sizeof(*range) + BTINTEL_PCIE_DBGC_BUFFER_SIZE);
> + payload_size = mon_hdr_size + ranges_size;
> + total_size = sizeof(*dump_data) + payload_size;
> +
> + entry = btintel_pcie_dump_entry_alloc(total_size);
> + if (!entry)
> + return -ENOMEM;
> +
> + dump_data = (void *)entry->data;
> + dump_data->type = BTINTEL_PCIE_INI_REGION_DRAM_BUFFER;
> + dump_data->sub_type = 0;
> + dump_data->sub_type_ver = 0;
> + dump_data->reserved = 0;
> + dump_data->len = cpu_to_le32(payload_size);
> +
> + mon_dump = (void *)dump_data->data;
> + mon_dump->header.version = cpu_to_le32(BTINTEL_PCIE_INI_DUMP_VER);
> + mon_dump->header.region_id = cpu_to_le32(region_id);
> + mon_dump->header.num_of_ranges = cpu_to_le32(count);
> + mon_dump->header.name_len = cpu_to_le32(BTINTEL_PCIE_INI_MAX_NAME);
> + memset(mon_dump->header.name, 0, BTINTEL_PCIE_INI_MAX_NAME);
> + strscpy(mon_dump->header.name, name, BTINTEL_PCIE_INI_MAX_NAME);
> +
> + mon_dump->write_ptr = cpu_to_le32(write_ptr);
> + mon_dump->cycle_cnt = cpu_to_le32(wrap_ctr);
> + mon_dump->cur_frag = cpu_to_le32(cur_frag);
> +
> + range = (void *)mon_dump->data;
> + for (i = 0; i < count; i++) {
> + range->range_data_size =
> + cpu_to_le32(BTINTEL_PCIE_DBGC_BUFFER_SIZE);
> + range->dram_base_addr = cpu_to_le64(bufs[i].data_p_addr);
> + memcpy(range->data, bufs[i].data,
> + BTINTEL_PCIE_DBGC_BUFFER_SIZE);
> + range = (void *)range->data + BTINTEL_PCIE_DBGC_BUFFER_SIZE;
> + }
> +
> + list_add_tail(&entry->list, list);
> + return 0;
> +}
> +
> +static int
> +btintel_pcie_dump_dram_monitor(struct btintel_pcie_data *data,
> + struct list_head *list, u8 count,
> + struct data_buf *bufs, u32 status_reg,
> + u32 wrap_reg, u8 allocation_id, u32 region_id,
> + const char *name)
> +{
> + u32 allocation_offset, write_ptr, wrap_ctr, wr_ptr_status;
> + u8 cur_frag;
> +
> + allocation_offset = allocation_id *
> + BTINTEL_PCIE_DBGC_ALLOCATION_OFFSET;
> + wr_ptr_status = btintel_pcie_rd_dev_mem(data, status_reg +
> + allocation_offset);
> + wrap_ctr = btintel_pcie_rd_dev_mem(data, wrap_reg +
> + allocation_offset);
> +
> + write_ptr = wr_ptr_status & BTINTEL_PCIE_DBG_OFFSET_BIT_MASK;
> + write_ptr >>= 2;
> + cur_frag = BTINTEL_PCIE_DBGC_DBG_BUF_IDX(wr_ptr_status);
> + if (cur_frag >= count) {
> + bt_dev_warn(data->hdev,
> + "Invalid DRAM monitor fragment %u for allocation %u",
> + cur_frag, allocation_id);
> + return -EINVAL;
> + }
> +
> + bt_dev_dbg(data->hdev,
> + "allocation=%u wr_ptr_status=0x%08x write_ptr=0x%06x cur_frag=%u wrap_ctr=0x%08x",
> + allocation_id, wr_ptr_status, write_ptr, cur_frag, wrap_ctr);
> +
> + return btintel_pcie_dump_dram(list, count, bufs, write_ptr, wrap_ctr,
> + cur_frag, region_id, name);
> +}
> +
> +static int btintel_pcie_dump_target_region(struct btintel_pcie_data *data,
> + struct list_head *list,
> + u32 region_id, const char *name,
> + u32 addr_start, u32 size)
> +{
> + struct btintel_pcie_dump_entry *entry;
> + struct btintel_pcie_ini_dump_data *dump_data;
> + struct btintel_pcie_ini_dump_header *hdr;
> + struct btintel_pcie_dump_range *range;
> + u32 payload_size, total_size, target_mem_offset, tempdata;
> + u8 *dest;
> + int i;
> +
> + if (!size) {
> + bt_dev_warn(data->hdev, "Skipping empty dump region: %s", name);
> + return 1;
> + }
> +
> + if (!addr_start) {
> + bt_dev_warn(data->hdev,
> + "Skipping dump region with zero address: %s",
> + name);
> + return 1;
> + }
> +
> + if (size > BTINTEL_PCIE_REGION_MAX_SIZE) {
> + bt_dev_warn(data->hdev,
> + "Skipping dump region %s: size %u exceeds max %u",
> + name, size, BTINTEL_PCIE_REGION_MAX_SIZE);
> + return 1;
> + }
> +
> + if (addr_start > U32_MAX - size) {
> + bt_dev_warn(data->hdev,
> + "Skipping dump region %s: addr_start 0x%08x + size %u would overflow",
> + name, addr_start, size);
> + return 1;
> + }
> +
> + /* Align to 4 bytes - target access reads 32-bit words */
> + size = round_down(size, sizeof(u32));
> + if (!size) {
> + bt_dev_warn(data->hdev,
> + "Skipping dump region %s: size is smaller than 4-byte access",
> + name);
> + return 1;
> + }
> +
> + bt_dev_dbg(data->hdev,
> + "Target access: region=%s start=0x%08x size=%u",
> + name, addr_start, size);
> +
> + payload_size = sizeof(*hdr) + sizeof(*range) + size;
> + total_size = sizeof(*dump_data) + payload_size;
> + entry = btintel_pcie_dump_entry_alloc(total_size);
> + if (!entry)
> + return -ENOMEM;
> +
> + dump_data = (void *)entry->data;
> + dump_data->type = BTINTEL_PCIE_INI_REGION_DEVICE_MEMORY;
> + dump_data->sub_type = 0;
> + dump_data->sub_type_ver = 0;
> + dump_data->reserved = 0;
> + dump_data->len = cpu_to_le32(payload_size);
> +
> + hdr = (void *)dump_data->data;
> + hdr->version = cpu_to_le32(BTINTEL_PCIE_INI_DUMP_VER);
> + hdr->region_id = cpu_to_le32(region_id);
> + hdr->num_of_ranges = cpu_to_le32(1);
> + hdr->name_len = cpu_to_le32(BTINTEL_PCIE_INI_MAX_NAME);
> + memset(hdr->name, 0, BTINTEL_PCIE_INI_MAX_NAME);
> + strscpy(hdr->name, name, BTINTEL_PCIE_INI_MAX_NAME);
> +
> + range = (void *)(hdr + 1);
> + range->range_data_size = cpu_to_le32(size);
> + range->internal_base_addr = cpu_to_le32(addr_start);
> +
> + dest = (u8 *)range->data;
> + target_mem_offset = size / sizeof(u32);
> + for (i = 0; i < target_mem_offset; i++) {
> + u32 offset = BTINTEL_PCIE_TARGET_ACCESS_FRAG_OFFSET * i;
> +
> + tempdata = btintel_pcie_rd_dev_mem(data,
> + addr_start + offset);
> + memcpy(dest, &tempdata, sizeof(tempdata));
> + dest += sizeof(tempdata);
> + }
> +
> + list_add_tail(&entry->list, list);
> + return 0;
> +}
> +
> +static int btintel_pcie_dump_smem_monitor_region(struct btintel_pcie_data *data,
> + struct list_head *list,
> + u32 region_id,
> + const char *name,
> + u32 addr_start, u32 size)
> +{
> + struct btintel_pcie_dump_entry *entry;
> + struct btintel_pcie_ini_dump_data *dump_data;
> + struct btintel_pcie_ini_monitor_dump *mon;
> + struct btintel_pcie_dump_range *range;
> + u32 payload_size, total_size, target_mem_offset, tempdata;
> + u8 *dest;
> + int i;
> +
> + if (!size || !addr_start) {
> + bt_dev_err(data->hdev, "Skipping smem dump: size = %u addr = %8.8x",
> + size, addr_start);
> + return -EINVAL;
> + }
> +
> + payload_size = sizeof(*mon) + sizeof(*range) + size;
> + total_size = sizeof(*dump_data) + payload_size;
> + entry = btintel_pcie_dump_entry_alloc(total_size);
> + if (!entry)
> + return -ENOMEM;
> +
> + dump_data = (void *)entry->data;
> + dump_data->type = BTINTEL_PCIE_INI_REGION_INTERNAL_BUFFER;
> + dump_data->sub_type = 0;
> + dump_data->sub_type_ver = 0;
> + dump_data->reserved = 0;
> + dump_data->len = cpu_to_le32(payload_size);
> +
> + mon = (void *)dump_data->data;
> + mon->header.version = cpu_to_le32(BTINTEL_PCIE_INI_DUMP_VER);
> + mon->header.region_id = cpu_to_le32(region_id);
> + mon->header.num_of_ranges = cpu_to_le32(1);
> + mon->header.name_len = cpu_to_le32(BTINTEL_PCIE_INI_MAX_NAME);
> + memset(mon->header.name, 0, BTINTEL_PCIE_INI_MAX_NAME);
> + strscpy(mon->header.name, name, BTINTEL_PCIE_INI_MAX_NAME);
> +
> + mon->write_ptr = cpu_to_le32(0);
> + mon->cycle_cnt = cpu_to_le32(0);
> + mon->cur_frag = cpu_to_le32(0);
> +
> + range = (void *)mon->data;
> + range->range_data_size = cpu_to_le32(size);
> + range->internal_base_addr = cpu_to_le32(addr_start);
> +
> + dest = (u8 *)range->data;
> + target_mem_offset = size / sizeof(u32);
> + for (i = 0; i < target_mem_offset; i++) {
> + u32 offset = BTINTEL_PCIE_TARGET_ACCESS_FRAG_OFFSET * i;
> +
> + tempdata = btintel_pcie_rd_dev_mem(data,
> + addr_start + offset);
> + memcpy(dest, &tempdata, sizeof(tempdata));
> + dest += sizeof(tempdata);
> + }
> +
> + list_add_tail(&entry->list, list);
> + return 0;
> +}
> +
> +static int btintel_pcie_dump_info(struct btintel_pcie_data *data,
> + struct list_head *list,
> + u64 regions_mask)
> +{
> + struct btintel_pcie_dump_entry *entry;
> + struct btintel_pcie_error_dump_data *tlv;
> + struct btintel_pcie_ini_dump_info *dump;
> + u32 size = sizeof(*tlv) + sizeof(*dump);
> + char build_tag[64];
> +
> + entry = btintel_pcie_dump_entry_alloc(size);
> + if (!entry)
> + return -ENOMEM;
> +
> + tlv = (void *)entry->data;
> + tlv->type = cpu_to_le32(BTINTEL_PCIE_INI_DUMP_INFO_TYPE);
> + tlv->len = cpu_to_le32(sizeof(*dump));
> +
> + dump = (void *)tlv->data;
> + memset(dump, 0, sizeof(*dump));
> +
> + dump->version = cpu_to_le32(BTINTEL_PCIE_INI_DUMP_VER);
> + dump->trigger_reason = cpu_to_le32(data->dmp_hdr.trigger_reason);
> +
> + if (data->dmp_hdr.trigger_reason ==
> + BTINTEL_PCIE_TRIGGER_REASON_FW_ASSERT)
> + dump->time_point =
> + cpu_to_le32(BTINTEL_PCIE_TIME_POINT_FW_ASSERT);
> + else if (data->dmp_hdr.trigger_reason ==
> + BTINTEL_PCIE_TRIGGER_REASON_USER_TRIGGER)
> + dump->time_point =
> + cpu_to_le32(BTINTEL_PCIE_TIME_POINT_USER_TRIGGER);
> +
> + dump->hw_type = cpu_to_le32(INTEL_CNVX_TOP_TYPE(data->dmp_hdr.cnvi_top));
> + dump->hw_step = cpu_to_le32(INTEL_CNVX_TOP_STEP(data->dmp_hdr.cnvi_top));
> + bt_dev_dbg(data->hdev, "hw_type=0x%x hw_step=0x%x (cnvi_top=0x%x)",
> + le32_to_cpu(dump->hw_type), le32_to_cpu(dump->hw_step),
> + data->dmp_hdr.cnvi_top);
> +
> + dump->ver_type = cpu_to_le32(data->dmp_hdr.cnvi_bt);
> + dump->ver_subtype = cpu_to_le32(data->dmp_hdr.fw_sha);
> + dump->rf_id_type = cpu_to_le32(INTEL_CNVX_TOP_TYPE(data->dmp_hdr.cnvr_top));
> + dump->rf_id_dash = cpu_to_le32(INTEL_CNVX_TOP_DASH(data->dmp_hdr.cnvr_top));
> + dump->rf_id_step = cpu_to_le32(INTEL_CNVX_TOP_STEP(data->dmp_hdr.cnvr_top));
> + dump->rf_id_flavor = cpu_to_le32(INTEL_CNVX_TOP_FLAVOR(data->dmp_hdr.cnvr_top));
> + bt_dev_dbg(data->hdev,
> + "rf_id_type=0x%x rf_id_dash=0x%x rf_id_step=0x%x rf_id_flavor=0x%x (cnvr_top=0x%x)",
> + le32_to_cpu(dump->rf_id_type),
> + le32_to_cpu(dump->rf_id_dash),
> + le32_to_cpu(dump->rf_id_step),
> + le32_to_cpu(dump->rf_id_flavor),
> + data->dmp_hdr.cnvr_top);
> + dump->lmac_major = cpu_to_le32(0);
> + dump->lmac_minor = cpu_to_le32(0);
> + dump->umac_major = cpu_to_le32(0);
> + dump->umac_minor = cpu_to_le32(0);
> + dump->fw_mon_mode = cpu_to_le32(BTINTEL_PCIE_FW_MON_MODE_DRAM);
> +
> + dump->regions_mask = cpu_to_le64(regions_mask);
> +
> + bt_dev_dbg(data->hdev, "ExpectedRegionIDs regions_mask=0x%016llx",
> + le64_to_cpu(dump->regions_mask));
> +
> + snprintf(build_tag, sizeof(build_tag), "%08X", data->dmp_hdr.fw_sha);
> +
> + dump->build_tag_len = cpu_to_le32(strlen(build_tag));
> + memcpy(dump->build_tag, build_tag, min(strlen(build_tag),
> + sizeof(dump->build_tag)));
> +
> + dump->num_of_cfg_names = cpu_to_le32(0);
> +
> + bt_dev_dbg(data->hdev,
> + "HwType=0x%08x HwStep=0x%08x RfIdType=0x%08x RfIdDash=0x%08x RfIdStep=0x%08x RfIdFlavor=0x%08x",
> + le32_to_cpu(dump->hw_type), le32_to_cpu(dump->hw_step),
> + le32_to_cpu(dump->rf_id_type),
> + le32_to_cpu(dump->rf_id_dash),
> + le32_to_cpu(dump->rf_id_step),
> + le32_to_cpu(dump->rf_id_flavor));
> + bt_dev_dbg(data->hdev, "VerType=0x%08x VerSubType=0x%08x",
> + le32_to_cpu(dump->ver_type), le32_to_cpu(dump->ver_subtype));
> + bt_dev_dbg(data->hdev,
> + "LmacMajor=0x%08x LmacMinor=0x%08x UmacMajor=0x%08x UmacMinor=0x%08x",
> + le32_to_cpu(dump->lmac_major), le32_to_cpu(dump->lmac_minor),
> + le32_to_cpu(dump->umac_major),
> + le32_to_cpu(dump->umac_minor));
> + bt_dev_dbg(data->hdev, "TriggerReason=0x%04x MonMode=%u BuildTag=%.64s",
> + le32_to_cpu(dump->trigger_reason),
> + le32_to_cpu(dump->fw_mon_mode),
> + dump->build_tag);
> +
> + list_add(&entry->list, list);
> +
> + return 0;
> +}
> +
> +static bool btintel_pcie_is_mdbgc_supported(struct btintel_pcie_data *data)
> +{
> + return data->pdev->device == BTINTEL_PCIE_DEVICE_ID_NVL_S_SCP2 ||
> + data->pdev->device == BTINTEL_PCIE_DEVICE_ID_NVL_Hx_SCP2 ||
> + data->pdev->device == BTINTEL_PCIE_DEVICE_ID_PTL_FMP2;
> +}
> +
> +static void btintel_pcie_dump_mem_range(struct btintel_pcie_data *data,
> + struct list_head *list, u32 region_id,
> + const char *name, u32 addr_start,
> + u32 addr_end, u64 *regions_mask)
> +{
> + u32 region_size;
> +
> + if (!addr_start || !addr_end || addr_end < addr_start)
> + return;
> +
> + if (addr_end > U32_MAX - 0x04) {
> + bt_dev_warn(data->hdev,
> + "Skipping dump region %s: addr_end 0x%08x would overflow",
> + name, addr_end);
> + return;
> + }
> +
> + region_size = (addr_end + 0x04) - addr_start;
> + if (region_size > BTINTEL_PCIE_REGION_MAX_SIZE) {
> + bt_dev_warn(data->hdev,
> + "Skipping dump region %s: size %u exceeds max %u",
> + name, region_size,
> + BTINTEL_PCIE_REGION_MAX_SIZE);
> + return;
> + }
> +
> + if (!btintel_pcie_dump_target_region(data, list, region_id, name,
> + addr_start, region_size))
> + *regions_mask |= BIT_ULL(region_id);
> +}
> +
> +static int btintel_pcie_read_debug_regions(struct btintel_pcie_data *data)
> +{
> + struct btintel_pcie_dbgc *dbgc = NULL;
> + struct btintel_pcie_mdbgc *mdbgc = NULL;
> struct hci_dev *hdev = data->hdev;
> - u8 *pdata, *p, buf_idx, hw_variant;
> - struct intel_tlv *tlv;
> - struct timespec64 now;
> - struct tm tm_now;
> - char fw_build[128];
> - char ts[128];
> - char vendor[64];
> - char driver[64];
> + struct btintel_pcie_dump_entry *entry;
> + struct btintel_pcie_dump_file_hdr *file_hdr;
> + struct scatterlist *sg_dump_data;
> + u32 status_reg, wrap_reg;
> + u32 exception_dump_len;
> + u32 exc_addr;
> + u64 regions_mask = 0;
> + u8 hw_variant;
> + u32 smem_rd_addr = 0, smem_rd_size = 0;
> + u32 file_len;
> + u8 count;
> + int ret;
> + LIST_HEAD(dump_list);
>
> if (!IS_ENABLED(CONFIG_DEV_COREDUMP))
> return -EOPNOTSUPP;
>
> + if (btintel_pcie_is_mdbgc_supported(data)) {
> + mdbgc = &data->mdbgc;
> + count = mdbgc->count;
> + } else {
> + dbgc = &data->dbgc;
> + count = dbgc->count;
> + }
>
> hw_variant = INTEL_HW_VARIANT(data->cnvi);
> - switch (hw_variant) {
> - case BTINTEL_HWID_BZRI:
> - case BTINTEL_HWID_BZRIW:
> +
> + if (hw_variant == BTINTEL_HWID_BZRI ||
> + hw_variant == BTINTEL_HWID_BZRIW) {
> status_reg = BTINTEL_PCIE_DBGC_CUR_DBGBUFF_STATUS;
> wrap_reg = BTINTEL_PCIE_DBGC_DBGBUFF_WRAP_ARND;
> - break;
> - case BTINTEL_HWID_SCP:
> - case BTINTEL_HWID_SCP2:
> - case BTINTEL_HWID_SCP2F:
> + } else if (hw_variant >= BTINTEL_HWID_SCP) {
> status_reg = BTINTEL_PCIE_DBGC_CUR_DBGBUFF_STATUS_SCP;
> wrap_reg = BTINTEL_PCIE_DBGC_DBGBUFF_WRAP_ARND_SCP;
> - break;
> - default:
> - bt_dev_err(hdev, "Unsupported Intel hardware variant (0x%2.2x)",
> + } else {
> + bt_dev_err(hdev,
> + "Unsupported Intel hardware variant (0x%2.2x)",
> hw_variant);
> return -EINVAL;
> }
>
> - wr_ptr_status = btintel_pcie_rd_dev_mem(data, status_reg);
> - data->dmp_hdr.wrap_ctr = btintel_pcie_rd_dev_mem(data, wrap_reg);
> + smem_rd_addr = data->dump_info.smem_addr_start;
> + smem_rd_size = 0;
>
> - offset = wr_ptr_status & BTINTEL_PCIE_DBG_OFFSET_BIT_MASK;
> + if (!smem_rd_addr && !data->dump_info.smem_addr_end) {
> + bt_dev_dbg(hdev, "smem region not advertised by firmware");
> + } else if (data->dump_info.smem_addr_end < smem_rd_addr ||
> + data->dump_info.smem_addr_end > U32_MAX - 0x04) {
> + bt_dev_err(hdev,
> + "Invalid smem region: start=0x%08x end=0x%08x",
> + smem_rd_addr, data->dump_info.smem_addr_end);
> + } else {
> + smem_rd_size = (data->dump_info.smem_addr_end + 0x04) -
> + smem_rd_addr;
>
> - buf_idx = BTINTEL_PCIE_DBGC_DBG_BUF_IDX(wr_ptr_status);
> - if (buf_idx > dbgc->count) {
> - bt_dev_warn(hdev, "Buffer index is invalid");
> - return -EINVAL;
> + bt_dev_dbg(hdev,
> + "smem_region: smem_start_addr=0x%08x smem_end_addr=0x%08x smem_rd_size=%u",
> + smem_rd_addr, data->dump_info.smem_addr_end,
> + smem_rd_size);
> +
> + if (smem_rd_size == 0 ||
> + smem_rd_size > BTINTEL_PCIE_SMEM_MAX_SIZE) {
> + bt_dev_err(hdev,
> + "Invalid smem region: smem_rd_addr 0x%08x size %u (max %u)",
> + smem_rd_addr, smem_rd_size,
> + BTINTEL_PCIE_SMEM_MAX_SIZE);
> + smem_rd_size = 0;
> + }
> }
>
> - prev_size = buf_idx * BTINTEL_PCIE_DBGC_BUFFER_SIZE;
> - if (prev_size + offset >= prev_size)
> - data->dmp_hdr.write_ptr = prev_size + offset;
> - else
> - return -EINVAL;
> + if (btintel_pcie_is_mdbgc_supported(data)) {
> + ret = btintel_pcie_dump_dram_monitor(data, &dump_list, count,
> + mdbgc->buf1, status_reg,
> + wrap_reg,
> + BTINTEL_PCIE_MDBGC_ALLOCATIONID_1,
> + BTINTEL_PCIE_INI_ID_DRAM_MONITOR1,
> + "monitor");
> + if (!ret)
> + regions_mask |=
> + BIT_ULL(BTINTEL_PCIE_INI_ID_DRAM_MONITOR1);
> + else
> + bt_dev_warn(hdev, "Failed to dump DRAM buf1: %d", ret);
> +
> + ret = btintel_pcie_dump_dram_monitor(data, &dump_list, count,
> + mdbgc->buf2, status_reg,
> + wrap_reg,
> + BTINTEL_PCIE_MDBGC_ALLOCATIONID_2,
> + BTINTEL_PCIE_INI_ID_DRAM_MONITOR2,
> + "monitor2");
> + if (!ret)
> + regions_mask |=
> + BIT_ULL(BTINTEL_PCIE_INI_ID_DRAM_MONITOR2);
> + else
> + bt_dev_warn(hdev, "Failed to dump DRAM buf2: %d", ret);
> +
> + ret = btintel_pcie_dump_dram_monitor(data, &dump_list, count,
> + mdbgc->buf3, status_reg,
> + wrap_reg,
> + BTINTEL_PCIE_MDBGC_ALLOCATIONID_3,
> + BTINTEL_PCIE_INI_ID_DRAM_MONITOR3,
> + "monitor3");
> + if (!ret)
> + regions_mask |=
> + BIT_ULL(BTINTEL_PCIE_INI_ID_DRAM_MONITOR3);
> + else
> + bt_dev_warn(hdev, "Failed to dump DRAM buf3: %d", ret);
> + } else {
> + ret = btintel_pcie_dump_dram_monitor(data, &dump_list, count,
> + dbgc->bufs, status_reg,
> + wrap_reg,
> + BTINTEL_PCIE_MDBGC_ALLOCATIONID_1,
> + BTINTEL_PCIE_INI_ID_DRAM_MONITOR1,
> + "monitor");
> + if (!ret)
> + regions_mask |=
> + BIT_ULL(BTINTEL_PCIE_INI_ID_DRAM_MONITOR1);
> + else
> + bt_dev_warn(hdev,
> + "Failed to dump DRAM region: %d", ret);
> + }
>
> - strscpy(vendor, "Vendor: Intel\n");
> - snprintf(driver, sizeof(driver), "Driver: %s\n",
> - data->dmp_hdr.driver_name);
> -
> - ktime_get_real_ts64(&now);
> - time64_to_tm(now.tv_sec, 0, &tm_now);
> - snprintf(ts, sizeof(ts), "Dump Time: %02d-%02d-%04ld %02d:%02d:%02d",
> - tm_now.tm_mday, tm_now.tm_mon + 1, tm_now.tm_year + 1900,
> - tm_now.tm_hour, tm_now.tm_min, tm_now.tm_sec);
> -
> - snprintf(fw_build, sizeof(fw_build),
> - "Firmware Timestamp: Year %u WW %02u buildtype %u build %u",
> - 2000 + (data->dmp_hdr.fw_timestamp >> 8),
> - data->dmp_hdr.fw_timestamp & 0xff, data->dmp_hdr.fw_build_type,
> - data->dmp_hdr.fw_build_num);
> -
> - data_len = sizeof(*tlv) + sizeof(data->dmp_hdr.cnvi_bt) +
> - sizeof(*tlv) + sizeof(data->dmp_hdr.write_ptr) +
> - sizeof(*tlv) + sizeof(data->dmp_hdr.wrap_ctr) +
> - sizeof(*tlv) + sizeof(data->dmp_hdr.trigger_reason) +
> - sizeof(*tlv) + sizeof(data->dmp_hdr.fw_sha) +
> - sizeof(*tlv) + sizeof(data->dmp_hdr.cnvr_top) +
> - sizeof(*tlv) + sizeof(data->dmp_hdr.cnvi_top) +
> - sizeof(*tlv) + strlen(ts) +
> - sizeof(*tlv) + strlen(fw_build) +
> - sizeof(*tlv) + strlen(vendor) +
> - sizeof(*tlv) + strlen(driver);
> -
> - if (data->dmp_hdr.event_type && data->dmp_hdr.event_id) {
> - data_len += sizeof(*tlv) + sizeof(data->dmp_hdr.event_type);
> - data_len += sizeof(*tlv) + sizeof(data->dmp_hdr.event_id);
> + if (smem_rd_size &&
> + !btintel_pcie_dump_smem_monitor_region(data, &dump_list,
> + BTINTEL_PCIE_INI_ID_SMEM,
> + "monitor_smem",
> + smem_rd_addr,
> + smem_rd_size))
> + regions_mask |= BIT_ULL(BTINTEL_PCIE_INI_ID_SMEM);
> +
> + exc_addr = data->dump_info.exception_dump_addr;
> + exception_dump_len = data->dump_info.exception_dump_len;
> + if (exc_addr && exception_dump_len) {
> + ret = btintel_pcie_dump_target_region(data, &dump_list,
> + BTINTEL_PCIE_INI_ID_EXCEPTION_EVT,
> + "EXCEPTION_EVT_BUFFER", exc_addr,
> + exception_dump_len);
> + if (!ret)
> + regions_mask |= BIT_ULL(BTINTEL_PCIE_INI_ID_EXCEPTION_EVT);
> }
>
> - /*
> - * sizeof(u32) - signature
> - * sizeof(data_len) - to store tlv data size
> - * data_len - TLV data
> - */
> - dump_size = sizeof(u32) + sizeof(data_len) + data_len;
> + btintel_pcie_dump_mem_range(data, &dump_list,
> + BTINTEL_PCIE_INI_ID_DCCM, "DCCM",
> + data->dump_info.dccm_addr_start,
> + data->dump_info.dccm_addr_end,
> + ®ions_mask);
> +
> + btintel_pcie_dump_mem_range(data, &dump_list,
> + BTINTEL_PCIE_INI_ID_SDS, "SDS",
> + data->dump_info.sds_start_addr_start,
> + data->dump_info.sds_start_addr_end,
> + ®ions_mask);
> +
> + btintel_pcie_dump_mem_range(data, &dump_list,
> + BTINTEL_PCIE_INI_ID_SDS_IOSF, "SDS_IOSF",
> + data->dump_info.sds_iosf_data_addr_start,
> + data->dump_info.sds_iosf_data_addr_end,
> + ®ions_mask);
> +
> + btintel_pcie_dump_mem_range(data, &dump_list,
> + BTINTEL_PCIE_INI_ID_ECL, "ECL_REGION",
> + data->dump_info.ecl_addr_start,
> + data->dump_info.ecl_addr_end,
> + ®ions_mask);
> +
> + ret = btintel_pcie_dump_info(data, &dump_list, regions_mask);
> + if (ret) {
> + btintel_pcie_dump_list_free(&dump_list);
> + return ret;
> + }
>
> + file_len = sizeof(*file_hdr) +
> + btintel_pcie_dump_list_total_size(&dump_list);
> + entry = btintel_pcie_dump_entry_alloc(sizeof(*file_hdr));
> + if (!entry) {
> + btintel_pcie_dump_list_free(&dump_list);
> + return -ENOMEM;
> + }
>
> - /* Add debug buffers data length to dump size */
> - dump_size += BTINTEL_PCIE_DBGC_BUFFER_SIZE * dbgc->count;
> + file_hdr = (void *)entry->data;
> + file_hdr->barker = cpu_to_le32(BTINTEL_PCIE_INI_ERROR_DUMP_BARKER);
> + file_hdr->file_len = cpu_to_le32(file_len);
> + list_add(&entry->list, &dump_list);
>
> - pdata = vmalloc(dump_size);
> - if (!pdata)
> - return -ENOMEM;
> - p = pdata;
> -
> - *(u32 *)p = BTINTEL_PCIE_MAGIC_NUM;
> - p += sizeof(u32);
> -
> - *(u32 *)p = data_len;
> - p += sizeof(u32);
> -
> -
> - p = btintel_pcie_copy_tlv(p, BTINTEL_VENDOR, vendor, strlen(vendor));
> - p = btintel_pcie_copy_tlv(p, BTINTEL_DRIVER, driver, strlen(driver));
> - p = btintel_pcie_copy_tlv(p, BTINTEL_DUMP_TIME, ts, strlen(ts));
> - p = btintel_pcie_copy_tlv(p, BTINTEL_FW_BUILD, fw_build,
> - strlen(fw_build));
> - p = btintel_pcie_copy_tlv(p, BTINTEL_CNVI_BT, &data->dmp_hdr.cnvi_bt,
> - sizeof(data->dmp_hdr.cnvi_bt));
> - p = btintel_pcie_copy_tlv(p, BTINTEL_WRITE_PTR, &data->dmp_hdr.write_ptr,
> - sizeof(data->dmp_hdr.write_ptr));
> - p = btintel_pcie_copy_tlv(p, BTINTEL_WRAP_CTR, &data->dmp_hdr.wrap_ctr,
> - sizeof(data->dmp_hdr.wrap_ctr));
> - p = btintel_pcie_copy_tlv(p, BTINTEL_TRIGGER_REASON, &data->dmp_hdr.trigger_reason,
> - sizeof(data->dmp_hdr.trigger_reason));
> - p = btintel_pcie_copy_tlv(p, BTINTEL_FW_SHA, &data->dmp_hdr.fw_sha,
> - sizeof(data->dmp_hdr.fw_sha));
> - p = btintel_pcie_copy_tlv(p, BTINTEL_CNVR_TOP, &data->dmp_hdr.cnvr_top,
> - sizeof(data->dmp_hdr.cnvr_top));
> - p = btintel_pcie_copy_tlv(p, BTINTEL_CNVI_TOP, &data->dmp_hdr.cnvi_top,
> - sizeof(data->dmp_hdr.cnvi_top));
> -
> - if (data->dmp_hdr.event_type && data->dmp_hdr.event_id) {
> - p = btintel_pcie_copy_tlv(p, BTINTEL_EVENT_TYPE,
> - &data->dmp_hdr.event_type,
> - sizeof(data->dmp_hdr.event_type));
> - p = btintel_pcie_copy_tlv(p, BTINTEL_EVENT_ID,
> - &data->dmp_hdr.event_id,
> - sizeof(data->dmp_hdr.event_id));
> - data->dmp_hdr.event_type = 0;
> - data->dmp_hdr.event_id = 0;
> - }
> -
> - memcpy(p, dbgc->bufs[0].data, dbgc->count * BTINTEL_PCIE_DBGC_BUFFER_SIZE);
> - dev_coredumpv(&hdev->dev, pdata, dump_size, GFP_KERNEL);
> - return 0;
> + sg_dump_data = btintel_pcie_alloc_sgtable(file_len);
> + if (sg_dump_data) {
> + int sg_entries = sg_nents(sg_dump_data);
> + u32 offs = 0;
> +
> + list_for_each_entry(entry, &dump_list, list) {
> + sg_pcopy_from_buffer(sg_dump_data, sg_entries,
> + entry->data, entry->size, offs);
> + offs += entry->size;
> + }
> +
> + bt_dev_dbg(hdev, "triggering dev_coredumpsg()");
> + dev_coredumpsg(&hdev->dev, sg_dump_data, file_len, GFP_KERNEL);
> + } else {
> + bt_dev_err(hdev, "Failed to allocate scatter-gather table for coredump");
> + ret = -ENOMEM;
> + }
> +
> + btintel_pcie_dump_list_free(&dump_list);
> + return ret;
> }
>
> static void btintel_pcie_dump_traces(struct hci_dev *hdev)
> {
> struct btintel_pcie_data *data = hci_get_drvdata(hdev);
> - int ret = 0;
> + int ret;
>
> ret = btintel_pcie_get_mac_access(data);
> if (ret) {
> @@ -923,7 +1445,7 @@ static void btintel_pcie_dump_traces(struct hci_dev *hdev)
> return;
> }
>
> - ret = btintel_pcie_read_dram_buffers(data);
> + ret = btintel_pcie_read_debug_regions(data);
>
> btintel_pcie_release_mac_access(data);
>
> @@ -1168,19 +1690,19 @@ static int btintel_parse_mbox_tlv(struct btintel_pcie_data *data)
> ptr = buffer;
> remaining = buffer_len;
>
> - /* Parse TLV structures: type(1) + length(2) + value */
> + /* Parse TLV structures: 1 byte type + 2 bytes length +
> + * variable value
> + */
> while (remaining >= sizeof(struct mbox_tlv)) {
> u16 tlv_len;
> u32 tlv_total;
>
> tlv = (struct mbox_tlv *)ptr;
> tlv_len = le16_to_cpu(tlv->len);
> - tlv_total = sizeof(tlv->type) +
> - sizeof(tlv->len) + tlv_len;
> + tlv_total = sizeof(tlv->type) + sizeof(tlv->len) + tlv_len;
>
> if (tlv_total > remaining) {
> - bt_dev_err(data->hdev,
> - "TLV parse error: type=%u, len=%u",
> + bt_dev_err(data->hdev, "TLV parse error: not enough data for TLV value (type=%u, len=%u)",
> tlv->type, tlv_len);
> break;
> }
> @@ -1189,7 +1711,7 @@ static int btintel_parse_mbox_tlv(struct btintel_pcie_data *data)
> case BTINTEL_PCIE_TLV_TYPE_EXCEPTION_DUMP_ADDRESS:
> if (tlv_len < 8) {
> bt_dev_err(data->hdev,
> - "TLV %s too short: %u",
> + "TLV %s too short: len=%u (need 8)",
> btintel_pcie_tlv_str(tlv->type),
> tlv_len);
> break;
> @@ -1202,7 +1724,7 @@ static int btintel_parse_mbox_tlv(struct btintel_pcie_data *data)
> case BTINTEL_PCIE_TLV_TYPE_DCCM_MEM_ADDRESS:
> if (tlv_len < 8) {
> bt_dev_err(data->hdev,
> - "TLV %s too short: %u",
> + "TLV %s too short: len=%u (need 8)",
> btintel_pcie_tlv_str(tlv->type),
> tlv_len);
> break;
> @@ -1221,8 +1743,7 @@ static int btintel_parse_mbox_tlv(struct btintel_pcie_data *data)
> bt_dev_dbg(data->hdev, "SDS TLV: skipped, hw_variant not yet known");
> break;
> }
> - if (tlv_len == 16 &&
> - hw_variant > BTINTEL_HWID_BZRI) {
> + if (tlv_len == 16 && hw_variant > BTINTEL_HWID_BZRI) {
> data->dump_info.sds_start_addr_start =
> get_unaligned_le32(&tlv->val[0]);
> data->dump_info.sds_start_addr_end =
> @@ -1255,7 +1776,7 @@ static int btintel_parse_mbox_tlv(struct btintel_pcie_data *data)
> case BTINTEL_PCIE_TLV_TYPE_ECL_MEM_ADDRESS:
> if (tlv_len < 8) {
> bt_dev_err(data->hdev,
> - "TLV %s too short: %u",
> + "TLV %s too short: len=%u (need 8)",
> btintel_pcie_tlv_str(tlv->type),
> tlv_len);
> break;
> @@ -1268,7 +1789,7 @@ static int btintel_parse_mbox_tlv(struct btintel_pcie_data *data)
> case BTINTEL_PCIE_TLV_TYPE_SMEM_ADDRESS:
> if (tlv_len < 8) {
> bt_dev_err(data->hdev,
> - "TLV %s too short: %u",
> + "TLV %s too short: len=%u (need 8)",
> btintel_pcie_tlv_str(tlv->type),
> tlv_len);
> break;
> @@ -1279,8 +1800,7 @@ static int btintel_parse_mbox_tlv(struct btintel_pcie_data *data)
> get_unaligned_le32(&tlv->val[4]);
> break;
> default:
> - bt_dev_dbg(data->hdev,
> - "Unknown TLV type: %u length: %u",
> + bt_dev_dbg(data->hdev, "Unknown TLV type: %u length: %u",
> tlv->type, tlv_len);
> break;
> }
> @@ -1868,11 +2388,8 @@ static int btintel_pcie_dump_fwtrigger_event(struct btintel_pcie_data *data)
> goto exit_on_error;
>
> evt = (void *)buf;
> - data->dmp_hdr.event_type = evt->event_type;
> - data->dmp_hdr.event_id = le16_to_cpu(evt->event_id);
> -
> bt_dev_dbg(data->hdev, "event type: 0x%2.2x event id: 0x%4.4x len: %u",
> - data->dmp_hdr.event_type, data->dmp_hdr.event_id, len);
> + evt->event_type, le16_to_cpu(evt->event_id), len);
>
> skb = bt_skb_alloc(len, GFP_KERNEL);
> if (!skb) {
> @@ -1898,8 +2415,8 @@ static int btintel_pcie_dump_fwtrigger_event(struct btintel_pcie_data *data)
> *
> * Always queue this AFTER any companion event-reader work (hwexp /
> * fwtrigger) so that, on the ordered @dump_workqueue, the event reader
> - * runs first and populates dmp_hdr.event_type / event_id before
> - * dump_traces consumes them.
> + * runs first and the trigger metadata is populated before dump_traces
> + * consumes it.
> */
> static bool btintel_pcie_queue_coredump(struct btintel_pcie_data *data,
> u16 trigger_reason)
> @@ -1982,12 +2499,6 @@ static void btintel_pcie_coredump_worker(struct work_struct *work)
> goto out;
> }
>
> - if (btintel_pcie_is_mdbgc_supported(data)) {
> - bt_dev_info(data->hdev,
> - "Skipping coredump: MDBGC dump format not supported yet");
> - goto out;
> - }
> -
> btintel_pcie_dump_traces(data->hdev);
> out:
> /* Release guard last so a new trigger can run only after this
> diff --git a/drivers/bluetooth/btintel_pcie.h b/drivers/bluetooth/btintel_pcie.h
> index 5c35e65d3e81..9d84db48a8e5 100644
> --- a/drivers/bluetooth/btintel_pcie.h
> +++ b/drivers/bluetooth/btintel_pcie.h
> @@ -74,11 +74,16 @@
> #define BTINTEL_PCIE_DBGC_BASE_ADDR_SCP (0xf0d5d500)
> #define BTINTEL_PCIE_DBGC_CUR_DBGBUFF_STATUS_SCP (BTINTEL_PCIE_DBGC_BASE_ADDR_SCP + 0x1C)
> #define BTINTEL_PCIE_DBGC_DBGBUFF_WRAP_ARND_SCP (BTINTEL_PCIE_DBGC_BASE_ADDR_SCP + 0x2C)
> +#define BTINTEL_PCIE_DBGC_ALLOCATION_OFFSET 0x100
>
> +#define BTINTEL_PCIE_SMEM_MAX_SIZE (16 * 1024)
> +#define BTINTEL_PCIE_REGION_MAX_SIZE (16 * 1024 * 1024)
> #define BTINTEL_PCIE_DBG_IDX_BIT_MASK 0x0F
> #define BTINTEL_PCIE_DBGC_DBG_BUF_IDX(data) (((data) >> 24) & BTINTEL_PCIE_DBG_IDX_BIT_MASK)
> #define BTINTEL_PCIE_DBG_OFFSET_BIT_MASK 0xFFFFFF
>
> +#define BTINTEL_PCIE_TARGET_ACCESS_FRAG_OFFSET 4
> +
> /* The DRAM buffer count, each buffer size, and
> * fragment buffer size
> */
> @@ -242,7 +247,7 @@ enum {
> #define BTINTEL_PCIE_TLV_TYPE_DCCM_MEM_ADDRESS 0x05
> #define BTINTEL_PCIE_TLV_TYPE_SDS_MEM_ADDRESS 0x06
> #define BTINTEL_PCIE_TLV_TYPE_ECL_MEM_ADDRESS 0x07
> -#define BTINTEL_PCIE_TLV_TYPE_SMEM_ADDRESS 0x08
> +#define BTINTEL_PCIE_TLV_TYPE_SMEM_ADDRESS 0x08
>
> /*
> * Struct for Context Information (v2)
> @@ -527,10 +532,147 @@ struct btintel_pcie_dump_header {
> u32 wrap_ctr;
> u16 trigger_reason;
> int state;
> - u8 event_type;
> - u16 event_id;
> };
>
> +/* Per-fragment range descriptor for dump regions.
> + * Binary-compatible with iwl_fw_ini_error_dump_range.
> + */
> +struct btintel_pcie_dump_range {
> + __le32 range_data_size;
> + union {
> + __le32 internal_base_addr;
> + __le64 dram_base_addr;
> + __le32 page_num;
> + };
> + __le32 data[];
> +} __packed;
> +
> +/*
> + * INI region types for ini_dump_data.type field.
> + * The unified decoder dispatches parsing logic based on these values.
> + */
> +#define BTINTEL_PCIE_INI_REGION_INTERNAL_BUFFER 2
> +#define BTINTEL_PCIE_INI_REGION_DRAM_BUFFER 3
> +#define BTINTEL_PCIE_INI_REGION_DEVICE_MEMORY 9
> +
> +/* INI region IDs - used in dump header region_id field and regions_mask */
> +#define BTINTEL_PCIE_INI_ID_EXCEPTION_EVT 7
> +#define BTINTEL_PCIE_INI_ID_SMEM 15
> +#define BTINTEL_PCIE_INI_ID_DCCM 39
> +#define BTINTEL_PCIE_INI_ID_SDS 40
> +#define BTINTEL_PCIE_INI_ID_SDS_IOSF 41
> +#define BTINTEL_PCIE_INI_ID_ECL 42
> +#define BTINTEL_PCIE_INI_ID_DRAM_MONITOR3 61
> +#define BTINTEL_PCIE_INI_ID_DRAM_MONITOR2 62
> +#define BTINTEL_PCIE_INI_ID_DRAM_MONITOR1 63
> +
> +/*
> + * INI (Intel INI debug infrastructure) style dump data TLV - wraps each dump
> + * region. INI is the iwlwifi firmware debug format used by the unified decoder.
> + * Compatible with iwl_fw_ini_error_dump_data.
> + */
> +struct btintel_pcie_ini_dump_data {
> + u8 type;
> + u8 sub_type;
> + u8 sub_type_ver;
> + u8 reserved;
> + __le32 len;
> + u8 data[];
> +} __packed;
> +
> +/*
> + * INI-style region dump header.
> + * Compatible with iwl_fw_ini_error_dump_header.
> + */
> +#define BTINTEL_PCIE_INI_MAX_NAME 32
> +#define BTINTEL_PCIE_INI_DUMP_VER 1
> +
> +struct btintel_pcie_ini_dump_header {
> + __le32 version;
> + __le32 region_id;
> + __le32 num_of_ranges;
> + __le32 name_len;
> + u8 name[BTINTEL_PCIE_INI_MAX_NAME];
> +};
> +
> +/*
> + * INI-style monitor dump - region header + monitor state.
> + * Compatible with iwl_fw_ini_monitor_dump.
> + */
> +struct btintel_pcie_ini_monitor_dump {
> + struct btintel_pcie_ini_dump_header header;
> + __le32 write_ptr;
> + __le32 cycle_cnt;
> + __le32 cur_frag;
> + u8 data[];
> +} __packed;
> +
> +/* Linked list entry for modular dump collection */
> +struct btintel_pcie_dump_entry {
> + struct list_head list;
> + u32 size;
> + u8 data[];
> +};
> +
> +/* File-level header for coredump output.
/*
* File-level …
> + * Compatible with iwl_fw_ini_dump_file_hdr.
> + * Uses IWL_FW_INI_ERROR_DUMP_BARKER (0x14789633) for decoder compatibility.
> + */
> +#define BTINTEL_PCIE_INI_ERROR_DUMP_BARKER 0x14789633
> +
> +struct btintel_pcie_dump_file_hdr {
> + __le32 barker;
> + __le32 file_len;
> +} __packed;
> +
> +/*
> + * Legacy-style dump data wrapper for dump info TLV.
> + * Compatible with iwl_fw_error_dump_data.
> + * Used only for the dump info entry (type=BTINTEL_PCIE_INI_DUMP_INFO_TYPE).
> + */
> +struct btintel_pcie_error_dump_data {
> + __le32 type;
> + __le32 len;
> + u8 data[];
> +} __packed;
> +
> +/* Use bit 31 as dump info type, matching IWL_INI_DUMP_INFO_TYPE */
> +#define BTINTEL_PCIE_INI_DUMP_INFO_TYPE BIT(31)
> +
> +/* Time point values matching iwl_fw_ini_time_point for fwdump parser */
> +#define BTINTEL_PCIE_TIME_POINT_FW_ASSERT 4
> +#define BTINTEL_PCIE_TIME_POINT_USER_TRIGGER 9
> +
> +/*
> + * Dump info struct - single TLV containing all metadata.
> + * Compatible with iwl_fw_ini_dump_info.
> + * Packs all device/firmware info that the decoder needs.
> + */
> +struct btintel_pcie_ini_dump_info {
> + __le32 version;
> + __le32 time_point;
> + __le32 trigger_reason;
> + __le32 external_cfg_state;
> + __le32 ver_type;
> + __le32 ver_subtype;
> + __le32 hw_step;
> + __le32 hw_type;
> + __le32 rf_id_flavor;
> + __le32 rf_id_dash;
> + __le32 rf_id_step;
> + __le32 rf_id_type;
> + __le32 lmac_major;
> + __le32 lmac_minor;
> + __le32 umac_major;
> + __le32 umac_minor;
> + __le32 fw_mon_mode;
> + __le64 regions_mask;
> + __le32 build_tag_len;
> + u8 build_tag[64];
> + __le32 num_of_cfg_names;
> + /* no cfg_names for BT - keep zero-length */
> +} __packed;
> +
> /* struct btintel_pcie_data
> * @pdev: pci device
> * @hdev: hdev device
> @@ -688,4 +830,3 @@ static inline u32 btintel_pcie_rd_dev_mem(struct btintel_pcie_data *data,
> btintel_pcie_wr_reg32(data, BTINTEL_PCIE_PRPH_DEV_ADDR_REG, addr);
> return btintel_pcie_rd_reg32(data, BTINTEL_PCIE_PRPH_DEV_RD_REG);
> }
> -
The removal of the last line could also be removed from this commit.
It’d be great if you could resend a smaller commit for review.
Kind regards,
Paul
^ permalink raw reply [flat|nested] 15+ messages in thread* Re: [PATCH v2 3/3] Bluetooth: btintel_pcie: unified decoder coredump format
2026-09-04 0:29 ` [PATCH v2 3/3] Bluetooth: btintel_pcie: unified decoder coredump format Chandrashekar Devegowda
2026-09-04 0:46 ` [v2,3/3] " bluez.test.bot
2026-09-04 9:49 ` [PATCH v2 3/3] " Paul Menzel
@ 2026-09-04 17:50 ` patchwork-bot+bluetooth
2 siblings, 0 replies; 15+ messages in thread
From: patchwork-bot+bluetooth @ 2026-09-04 17:50 UTC (permalink / raw)
To: Chandrashekar Devegowda
Cc: linux-bluetooth, ravishankar.srivatsa, chethan.tumkur.narayan,
kiran.k
Hello:
This patch was applied to bluetooth/bluetooth-next.git (master)
by Luiz Augusto von Dentz <luiz.von.dentz@intel.com>:
On Fri, 4 Sep 2026 05:59:38 +0530 you wrote:
> Replace the legacy TLV-based coredump with a unified INI-format dump
> compatible with the iwlwifi decoder so a single decoder can parse
> coredumps across all Intel BT PCIe controller variants.
>
> The dump is emitted via dev_coredumpsg() as structured regions built
> from firmware-advertised addresses: DRAM monitor buffers (single or
> MDBGC multi-buffer), SMEM monitor, exception event buffer, DCCM, SDS,
> SDS IOSF, ECL and dump info metadata. Regions are collected only when
> firmware advertises a valid address and size, and only collected
> regions are advertised in regions_mask.
>
> [...]
Here is the summary with links:
- [v2,3/3] Bluetooth: btintel_pcie: unified decoder coredump format
https://git.kernel.org/bluetooth/bluetooth-next/c/755cf7adf8dd
You are awesome, thank you!
--
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH v1 1/4] Bluetooth: btintel_pcie: remove duplicate BTINTEL_PCIE_MAGIC_NUM definition
2026-08-28 14:27 [PATCH v1 1/4] Bluetooth: btintel_pcie: remove duplicate BTINTEL_PCIE_MAGIC_NUM definition Chandrashekar Devegowda
` (6 preceding siblings ...)
2026-09-04 0:29 ` [PATCH v2 3/3] Bluetooth: btintel_pcie: unified decoder coredump format Chandrashekar Devegowda
@ 2026-09-04 16:56 ` patchwork-bot+bluetooth
7 siblings, 0 replies; 15+ messages in thread
From: patchwork-bot+bluetooth @ 2026-09-04 16:56 UTC (permalink / raw)
To: Chandrashekar Devegowda
Cc: linux-bluetooth, ravishankar.srivatsa, chethan.tumkur.narayan
Hello:
This series was applied to bluetooth/bluetooth-next.git (master)
by Luiz Augusto von Dentz <luiz.von.dentz@intel.com>:
On Fri, 28 Aug 2026 19:57:22 +0530 you wrote:
> Two identical definitions of BTINTEL_PCIE_MAGIC_NUM were present in
> btintel_pcie.c, one indented with spaces and one with a tab. Remove the
> space-indented duplicate and the surrounding blank lines. Kernel coding
> style requires tabs for indentation, so keep the tab-indented copy.
>
> No functional change.
>
> [...]
Here is the summary with links:
- [v1,1/4] Bluetooth: btintel_pcie: remove duplicate BTINTEL_PCIE_MAGIC_NUM definition
https://git.kernel.org/bluetooth/bluetooth-next/c/1cdf710cc2a1
- [v1,2/4] Bluetooth: btintel_pcie: add MDBGC multi-buffer DBGC allocation
(no matching commit)
- [v1,3/4] Bluetooth: btintel_pcie: log mailbox handler latency in msix_gp1_handler
(no matching commit)
- [v1,4/4] Bluetooth: btintel_pcie: unified decoder coredump format
(no matching commit)
You are awesome, thank you!
--
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html
^ permalink raw reply [flat|nested] 15+ messages in thread