From: Paul Menzel <pmenzel@molgen.mpg.de>
To: Chandrashekar Devegowda <chandrashekar.devegowda@intel.com>
Cc: ravishankar.srivatsa@intel.com, chethan.tumkur.narayan@intel.com,
kiran.k@intel.com, linux-bluetooth@vger.kernel.org
Subject: Re: [PATCH v2 3/3] Bluetooth: btintel_pcie: unified decoder coredump format
Date: Fri, 4 Sep 2026 11:49:32 +0200 [thread overview]
Message-ID: <49e4a2b0-87bc-4e46-8d98-85eb5da89807@molgen.mpg.de> (raw)
In-Reply-To: <20260904002939.622659-3-chandrashekar.devegowda@intel.com>
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
next prev parent reply other threads:[~2026-09-04 9:49 UTC|newest]
Thread overview: 15+ messages / expand[flat|nested] mbox.gz Atom feed top
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 ` [PATCH v1 4/4] Bluetooth: btintel_pcie: unified decoder coredump format Chandrashekar Devegowda
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 ` [PATCH v2 1/3] " 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
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 0:46 ` [v2,3/3] " bluez.test.bot
2026-09-04 9:49 ` Paul Menzel [this message]
2026-09-04 17:50 ` [PATCH v2 3/3] " patchwork-bot+bluetooth
2026-09-04 16:56 ` [PATCH v1 1/4] Bluetooth: btintel_pcie: remove duplicate BTINTEL_PCIE_MAGIC_NUM definition patchwork-bot+bluetooth
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=49e4a2b0-87bc-4e46-8d98-85eb5da89807@molgen.mpg.de \
--to=pmenzel@molgen.mpg.de \
--cc=chandrashekar.devegowda@intel.com \
--cc=chethan.tumkur.narayan@intel.com \
--cc=kiran.k@intel.com \
--cc=linux-bluetooth@vger.kernel.org \
--cc=ravishankar.srivatsa@intel.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox