mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH v1 1/3] Bluetooth: btintel_pcie: Add yoyo support for config set TLV
@ 2026-11-09 17:31 Ajay KV
  2026-11-09 17:31 ` [PATCH v1 2/3] Bluetooth: btintel_pcie: Add yoyo configuration for debug region Ajay KV
  2026-11-09 17:31 ` [PATCH v1 3/3] Bluetooth: btintel_pcie: Add yoyo configuration for Hci cmd Tlvs Ajay KV
  0 siblings, 2 replies; 3+ messages in thread
From: Ajay KV @ 2026-11-09 17:31 UTC (permalink / raw)
  To: linux-bluetooth
  Cc: chandrashekar.devegowda, kiran.k, linux-kernel, luiz.dentz,
	marcel, stable, chethan.tumkur.narayan, lakshmipathi.k, Ajaykv

From: Ajaykv <ajay.k.v@intel.com>

Add parsing of the YoYo CONFIG_SET TLV so that debug register
configuration can be supplied through the external TLV file.
Each CONFIG_SET TLV is validated, and its address/value pairs
are stored in the config set list of the YoYo context.

At the configured timepoint, the stored pairs are written to
the target memory region, with or without target access
depending on the region type. Write failures are logged
and do not stop normal Bluetooth bring-up. The stored pairs
are freed when the device is removed.

Signed-off-by: Ajaykv <ajay.k.v@intel.com>
---
 drivers/bluetooth/btintel_pcie.c       | 142 +++++++++++++++++++++++--
 drivers/bluetooth/btintel_pcie.h       |   1 +
 drivers/bluetooth/btintel_pcie_debug.c | 113 ++++++++++++++++++--
 drivers/bluetooth/btintel_pcie_debug.h |  76 ++++++++++++-
 4 files changed, 313 insertions(+), 19 deletions(-)

diff --git a/drivers/bluetooth/btintel_pcie.c b/drivers/bluetooth/btintel_pcie.c
index dadd79e4392f..de740477e6d7 100644
--- a/drivers/bluetooth/btintel_pcie.c
+++ b/drivers/bluetooth/btintel_pcie.c
@@ -927,6 +927,133 @@ static void btintel_pcie_release_mac_access(struct btintel_pcie_data *data)
 	}
 }
 
+static void
+btintel_pcie_apply_yoyo_config_sets(struct btintel_pcie_data *data,
+				    u32 time_point)
+{
+	struct btintel_pcie_yoyo_config_set *config_set;
+	resource_size_t bar0_len;
+	int ret;
+	u32 i;
+
+	if (!data)
+		return;
+
+	/* Get the length of PCI BAR0 */
+	bar0_len = pci_resource_len(data->pdev, 0);
+
+	if (!data->yoyo.enabled)
+		return;
+
+	list_for_each_entry(config_set, &data->yoyo.config_set_list, list) {
+		u32 pair_addr, addr, value;
+
+		if (config_set->time_point != time_point)
+			continue;
+
+		switch (config_set->config_set_type) {
+		case BTINTEL_PCIE_CFGSET_CSR:
+		case BTINTEL_PCIE_CFGSET_PERIPH_MAC:
+		case BTINTEL_PCIE_CFGSET_DEVICE_MEM:
+			break;
+		default:
+			BT_WARN("YoYo: unknown/unsupported config_set type %u, skipping",
+				config_set->config_set_type);
+			continue;
+		}
+
+		if (config_set->config_set_type == BTINTEL_PCIE_CFGSET_PERIPH_MAC ||
+		    config_set->config_set_type == BTINTEL_PCIE_CFGSET_DEVICE_MEM) {
+			ret = btintel_pcie_get_mac_access(data);
+			if (ret) {
+				BT_WARN("YoYo: MAC access failed for config set (%d), skipping",
+					ret);
+				/* get_mac_access() asserts MAC_ACCESS_REQ before it
+				 * polls, and leaves it set on timeout; drop it so the
+				 * request bit is not leaked.
+				 */
+				btintel_pcie_release_mac_access(data);
+				continue;
+			}
+		}
+
+		for (i = 0; i < config_set->num_pairs; i++) {
+			pair_addr = get_unaligned_le32(&config_set->pairs[i].address);
+
+			/* Calculate the effective address by adding the address offset */
+			if (check_add_overflow(pair_addr, config_set->addr_offset, &addr)) {
+				BT_WARN("YoYo: config set address overflow (0x%08x + 0x%08x)",
+					pair_addr, config_set->addr_offset);
+				continue;
+			}
+
+			value = get_unaligned_le32(&config_set->pairs[i].value);
+
+			switch (config_set->config_set_type) {
+			/* Direct Address Access (CSR) */
+			case BTINTEL_PCIE_CFGSET_CSR:
+				/* Validate the effective address for CSR access (Direct Address)
+				 * addr not aligned to 4 bytes or exceeds the BAR0 length
+				 */
+				if ((addr & (sizeof(u32) - 1)) ||
+				    bar0_len < sizeof(u32) ||
+				    addr > bar0_len - sizeof(u32)) {
+					BT_WARN("YoYo: bad CSR address 0x%08x for BAR0 length %llu",
+						addr, (unsigned long long)bar0_len);
+					continue;
+				}
+
+				BT_DBG("YoYo: CSR write [0x%08x] = 0x%08x", addr, value);
+				btintel_pcie_wr_reg32(data, addr, value);
+				break;
+			/* Indirect Address Access (Peripheral MAC and Device Memory) */
+			case BTINTEL_PCIE_CFGSET_PERIPH_MAC:
+			case BTINTEL_PCIE_CFGSET_DEVICE_MEM:
+				/* Validate the effective address for peripheral MAC and device
+				 * memory access: addr not aligned to 4 bytes
+				 */
+				if (!addr || (addr & (sizeof(u32) - 1))) {
+					BT_WARN("YoYo: bad indirect address 0x%08x for type %u",
+						addr, config_set->config_set_type);
+					continue;
+				}
+
+				btintel_pcie_wr_reg32(data,
+						      BTINTEL_PCIE_PRPH_DEV_ADDR_REG,
+						      addr);
+				btintel_pcie_wr_reg32(data,
+						      BTINTEL_PCIE_PRPH_DEV_WR_REG,
+						      value);
+				BT_DBG("YoYo: indirect write type %u addr 0x%08x value 0x%08x",
+				       config_set->config_set_type, addr, value);
+				break;
+			default:
+				BT_WARN("YoYo: unknown config_set type %u",
+					config_set->config_set_type);
+				break;
+			}
+		}
+
+		if (config_set->config_set_type == BTINTEL_PCIE_CFGSET_PERIPH_MAC ||
+		    config_set->config_set_type == BTINTEL_PCIE_CFGSET_DEVICE_MEM)
+			btintel_pcie_release_mac_access(data);
+	}
+}
+
+static void btintel_pcie_yoyo_handle_timepoint(struct btintel_pcie_data *data,
+					       enum btintel_pcie_dbg_timepoint tp)
+{
+	if (!data || !data->yoyo.enabled)
+		return;
+
+	if (tp <= BTINTEL_PCIE_TP_INVALID || tp >= BTINTEL_PCIE_TP_MAX_NUM) {
+		BT_WARN("YoYo: invalid timepoint %u", tp);
+		return;
+	}
+
+	btintel_pcie_apply_yoyo_config_sets(data, tp);
+}
+
 static struct scatterlist *btintel_pcie_alloc_sgtable(ssize_t size)
 {
 	int nents;
@@ -1617,6 +1744,9 @@ static int btintel_pcie_enable_bt(struct btintel_pcie_data *data)
 		BT_INFO("%s: Mac Init: Wait on Alive Interrupt",
 			btintel_pcie_dev_name(data));
 
+	/* EARLY Timepoint Handler */
+	btintel_pcie_yoyo_handle_timepoint(data, BTINTEL_PCIE_TP_EARLY);
+
 	btintel_pcie_set_reg_bits(data, BTINTEL_PCIE_CSR_FUNC_CTRL_REG,
 				  BTINTEL_PCIE_CSR_FUNC_CTRL_FUNC_INIT);
 
@@ -4751,7 +4881,8 @@ static int btintel_pcie_probe(struct pci_dev *pdev,
 	if (data->enable_ini) {
 		err = btintel_pcie_yoyo_load_and_parse(&data->yoyo, &pdev->dev);
 		if (err) {
-			BT_WARN("YoYo is not configured (%d)", err);
+			BT_WARN("YoYo (BDF - %s): yoyo is not configured (%d)",
+				pci_name(pdev), err);
 			btintel_pcie_yoyo_cleanup(&data->yoyo);
 		}
 	}
@@ -4830,12 +4961,6 @@ static void btintel_pcie_remove(struct pci_dev *pdev)
 	if (current_work() != &data->reset_work)
 		disable_work_sync(&data->reset_work);
 
-	btintel_pcie_mask_hw_rfkill(data);
-	btintel_pcie_synchronize_irqs(data);
-	disable_delayed_work_sync(&data->hw_rfkill_work);
-
-	btintel_pcie_yoyo_cleanup(&data->yoyo);
-
 	btintel_pcie_disable_interrupts(data);
 
 	btintel_pcie_synchronize_irqs(data);
@@ -4854,6 +4979,9 @@ static void btintel_pcie_remove(struct pci_dev *pdev)
 
 	btintel_pcie_release_hdev(data);
 
+	/* Cleanup YoYo debug framework */
+	btintel_pcie_yoyo_cleanup(&data->yoyo);
+
 	destroy_workqueue(data->dump_workqueue);
 	destroy_workqueue(data->workqueue);
 
diff --git a/drivers/bluetooth/btintel_pcie.h b/drivers/bluetooth/btintel_pcie.h
index 4125601b78d7..2224785b6e30 100644
--- a/drivers/bluetooth/btintel_pcie.h
+++ b/drivers/bluetooth/btintel_pcie.h
@@ -27,6 +27,7 @@
 #define BTINTEL_PCIE_CSR_MBOX_4_REG		(BTINTEL_PCIE_CSR_BASE + 0x17C)
 #define BTINTEL_PCIE_CSR_MBOX_STATUS_REG	(BTINTEL_PCIE_CSR_BASE + 0x180)
 #define BTINTEL_PCIE_PRPH_DEV_ADDR_REG		(BTINTEL_PCIE_CSR_BASE + 0x440)
+#define BTINTEL_PCIE_PRPH_DEV_WR_REG		(BTINTEL_PCIE_CSR_BASE + 0x454)
 #define BTINTEL_PCIE_PRPH_DEV_RD_REG		(BTINTEL_PCIE_CSR_BASE + 0x458)
 #define BTINTEL_PCIE_CSR_HBUS_TARG_WRPTR	(BTINTEL_PCIE_CSR_BASE + 0x460)
 
diff --git a/drivers/bluetooth/btintel_pcie_debug.c b/drivers/bluetooth/btintel_pcie_debug.c
index d675d3ff63dc..c52ef24dd0b0 100644
--- a/drivers/bluetooth/btintel_pcie_debug.c
+++ b/drivers/bluetooth/btintel_pcie_debug.c
@@ -314,8 +314,10 @@ static int btintel_pcie_yoyo_parse_dbg_info(struct btintel_pcie_yoyo_ctx *ctx,
 	const u8 *data = (const u8 *)info;
 	u32 domain, image_type, core_type, version;
 
-	if (len != sizeof(*info) - sizeof(struct btintel_pcie_tlv_hdr))
+	if (len != sizeof(*info) - sizeof(struct btintel_pcie_tlv_hdr)) {
+		BT_WARN("YoYo: dbg info tlv is too short or Invalid");
 		return -EBADMSG;
+	}
 
 	image_type = get_unaligned_le32(data +
 				       offsetof(struct btintel_pcie_tlv_dbg_info,
@@ -361,7 +363,7 @@ static int btintel_pcie_yoyo_parse_buf_alloc(struct btintel_pcie_yoyo_ctx *ctx,
 	u32 req_buf_size, version;
 
 	if (len != sizeof(*tlv) - sizeof(struct btintel_pcie_tlv_hdr)) {
-		BT_WARN("YoYo: unexpected buf alloc TLV length %u", len);
+		BT_WARN("YoYo: buf alloc tlv is too short or Invalid");
 		return -EBADMSG;
 	}
 
@@ -423,6 +425,84 @@ static int btintel_pcie_yoyo_parse_buf_alloc(struct btintel_pcie_yoyo_ctx *ctx,
 	return 0;
 }
 
+static int btintel_pcie_yoyo_parse_config_set(struct btintel_pcie_yoyo_ctx *ctx,
+					      const struct btintel_pcie_tlv_config_set *tlv,
+					      u32 len)
+{
+	struct btintel_pcie_yoyo_config_set *cs;
+	u32 fixed_size = sizeof(*tlv) - sizeof(struct btintel_pcie_tlv_hdr);
+	u32 pairs_bytes, num_pairs, domain, version, time_point, config_set_type;
+
+	if (len < fixed_size) {
+		BT_WARN("YoYo: config set tlv too short or Invalid");
+		return -EBADMSG;
+	}
+
+	domain = get_unaligned_le32(&tlv->dbg_hdr.domain);
+	version = get_unaligned_le32(&tlv->dbg_hdr.tlv_data_version);
+	if (domain != BTINTEL_PCIE_DOMAIN_BT ||
+	    version != BTINTEL_PCIE_CONFIG_SET_TLV_VERSION) {
+		BT_WARN("YoYo: unsupported domain/version (%u/%u)",
+			domain, version);
+		return 0;
+	}
+
+	time_point = get_unaligned_le32(&tlv->time_point);
+	if (time_point != BTINTEL_PCIE_TP_EARLY) {
+		BT_WARN("YoYo: config set unsupported time_point %u, skipping",
+			time_point);
+		return 0;
+	}
+
+	config_set_type = get_unaligned_le32(&tlv->config_set_type);
+	if (config_set_type != BTINTEL_PCIE_CFGSET_CSR &&
+	    config_set_type != BTINTEL_PCIE_CFGSET_PERIPH_MAC &&
+	    config_set_type != BTINTEL_PCIE_CFGSET_DEVICE_MEM) {
+		BT_WARN("YoYo: config set unsupported config_set_type %u, skipping",
+			config_set_type);
+		return 0;
+	}
+
+	/* Malformed pair data: skip this TLV, keep parsing the rest. */
+	pairs_bytes = len - fixed_size;
+	if (pairs_bytes % sizeof(struct btintel_pcie_addr_value)) {
+		BT_WARN("YoYo: config set has an incomplete addr/value pair, skipping");
+		return 0;
+	}
+
+	num_pairs = pairs_bytes / sizeof(struct btintel_pcie_addr_value);
+
+	if (!num_pairs) {
+		BT_WARN("YoYo: config set with no addr/value pairs, skipping");
+		return 0;
+	}
+
+	cs = kzalloc(sizeof(*cs), GFP_KERNEL);
+	if (!cs) {
+		BT_WARN("YoYo: failed to allocate config set entry, skipping");
+		return -ENOMEM;
+	}
+
+	cs->time_point = time_point;
+	cs->config_set_type = config_set_type;
+	cs->addr_offset = get_unaligned_le32(&tlv->addr_offset);
+	cs->num_pairs = num_pairs;
+
+	cs->pairs = kmemdup(tlv->addr_pair, pairs_bytes, GFP_KERNEL);
+	if (!cs->pairs) {
+		kfree(cs);
+		BT_WARN("YoYo: failed to allocate config set pairs, skipping");
+		return -ENOMEM;
+	}
+
+	BT_DBG("YoYo: config set tp=%u type=%u pairs=%u offset=0x%x",
+	       cs->time_point, cs->config_set_type,
+	       cs->num_pairs, cs->addr_offset);
+
+	list_add_tail(&cs->list, &ctx->config_set_list);
+	return 0;
+}
+
 /*
  * Parse the optional YoYo firmware TLV stream and validate each record.
  * This is the entry point for the runtime debug configuration payload.
@@ -463,10 +543,12 @@ static int btintel_pcie_yoyo_parse_tlv_buffer(struct btintel_pcie_yoyo_ctx *ctx,
 			ret = btintel_pcie_yoyo_parse_buf_alloc(ctx,
 								(const void *)tlv, len);
 			break;
+		case BTINTEL_PCIE_TLV_TYPE_DBG_CONFIG_SET:
+			ret = btintel_pcie_yoyo_parse_config_set(ctx, (const void *)tlv, len);
+			break;
 		case BTINTEL_PCIE_TLV_TYPE_DBG_REGION:
 		case BTINTEL_PCIE_TLV_TYPE_DBG_TRIGGER:
 		case BTINTEL_PCIE_TLV_TYPE_DBG_HCMD:
-		case BTINTEL_PCIE_TLV_TYPE_DBG_CONFIG_SET:
 		case BTINTEL_PCIE_TLV_TYPE_DBG_PRESET_OVERRIDE:
 			BT_DBG("YoYo: valid tlv type 0x%x len=%u", type, len);
 			break;
@@ -528,6 +610,8 @@ EXPORT_SYMBOL_GPL(btintel_pcie_yoyo_init);
  */
 void btintel_pcie_yoyo_cleanup(struct btintel_pcie_yoyo_ctx *ctx)
 {
+	struct btintel_pcie_yoyo_config_set *config_set, *config_tmp;
+
 	if (!ctx)
 		return;
 
@@ -535,6 +619,14 @@ void btintel_pcie_yoyo_cleanup(struct btintel_pcie_yoyo_ctx *ctx)
 	ctx->dbg_info_present = false;
 	ctx->num_buf_allocs = 0;
 	memset(ctx->buf_alloc, 0, sizeof(ctx->buf_alloc));
+
+	ctx->hdev = NULL;
+
+	list_for_each_entry_safe(config_set, config_tmp, &ctx->config_set_list, list) {
+		list_del(&config_set->list);
+		kfree(config_set->pairs);
+		kfree(config_set);
+	}
 }
 EXPORT_SYMBOL_GPL(btintel_pcie_yoyo_cleanup);
 
@@ -552,7 +644,7 @@ int btintel_pcie_yoyo_load_and_parse(struct btintel_pcie_yoyo_ctx *ctx,
 	const struct firmware *fw;
 	int ret;
 
-	if (!ctx)
+	if (!ctx || !dev)
 		return -EINVAL;
 
 	ret = request_firmware_direct(&fw, BTINTEL_PCIE_YOYO_FW_NAME, dev);
@@ -576,17 +668,22 @@ int btintel_pcie_yoyo_load_and_parse(struct btintel_pcie_yoyo_ctx *ctx,
 
 	/* File Corruption / TLV validation failed */
 	if (ret) {
-		BT_ERR("YoYo: tlv parsing failed (%d)", ret);
+		BT_WARN("YoYo: tlv parsing failed (%d)", ret);
+		btintel_pcie_yoyo_cleanup(ctx);
 		return ret;
 	}
-	if (!ctx->dbg_info_present) {
-		BT_ERR("YoYo: Invalid BT debug Info tlv, using legacy DBGC defaults");
+
+	/* Mandatory debug Info Tlv is missing */
+	if (!ctx->dbg_info_present ||
+	    !ctx->num_buf_allocs) {
+		BT_WARN("YoYo: mandatory tlv is missing, using legacy DBGC defaults");
+		btintel_pcie_yoyo_cleanup(ctx);
 		return -EBADMSG;
 	}
 
 	ctx->enabled = true;
 
-	BT_INFO("YoYo: feature enabled and configured");
+	BT_INFO("YoYo (BDF - %s): feature enabled and configured", dev_name(dev));
 	return 0;
 }
 EXPORT_SYMBOL_GPL(btintel_pcie_yoyo_load_and_parse);
diff --git a/drivers/bluetooth/btintel_pcie_debug.h b/drivers/bluetooth/btintel_pcie_debug.h
index b4d1d329886b..a9ecc7462d4e 100644
--- a/drivers/bluetooth/btintel_pcie_debug.h
+++ b/drivers/bluetooth/btintel_pcie_debug.h
@@ -48,9 +48,9 @@ bool btintel_pcie_fatal_err_testmode_active(struct btintel_pcie_data *data);
 #define BTINTEL_PCIE_YOYO_FW_NAME	"intel/ibt_debug_yoyo.bin"
 
 /* TLV type codes */
-#define BTINTEL_PCIE_TLV_TYPE_DBG_INFO		0x1000005
+#define BTINTEL_PCIE_TLV_TYPE_DBG_INFO			0x1000005
 #define BTINTEL_PCIE_TLV_TYPE_DBG_BUF_ALLOC		0x1000006
-#define BTINTEL_PCIE_TLV_TYPE_DBG_HCMD		0x1000007
+#define BTINTEL_PCIE_TLV_TYPE_DBG_HCMD			0x1000007
 #define BTINTEL_PCIE_TLV_TYPE_DBG_REGION		0x1000008
 #define BTINTEL_PCIE_TLV_TYPE_DBG_TRIGGER		0x1000009
 #define BTINTEL_PCIE_TLV_TYPE_DBG_CONFIG_SET		0x100000A
@@ -59,6 +59,7 @@ bool btintel_pcie_fatal_err_testmode_active(struct btintel_pcie_data *data);
 /* Supported TLV type versions */
 #define BTINTEL_PCIE_DBG_INFO_TLV_VERSION	2
 #define BTINTEL_PCIE_BUF_ALLOC_TLV_VERSION	1
+#define BTINTEL_PCIE_CONFIG_SET_TLV_VERSION	1
 /* Debug Info TLV */
 #define BTINTEL_PCIE_CONFIG_NAME_LEN		64
 #define BTINTEL_PCIE_DOMAIN_BT			0
@@ -77,7 +78,7 @@ enum btintel_pcie_image_tlv_type {
 
 /* Allocation IDs */
 enum btintel_dbg_alloc_id {
-	BTINTEL_PCIE_ALLOC_ID_INVALID	= 0,
+	BTINTEL_PCIE_ALLOC_ID_INVALID		= 0,
 	BTINTEL_PCIE_ALLOC_ID_DBGC1		= 1,
 	BTINTEL_PCIE_ALLOC_ID_DBGC2		= 2,
 	BTINTEL_PCIE_ALLOC_ID_DBGC3		= 3,
@@ -93,6 +94,41 @@ enum btintel_dbg_buf_location {
 	BTINTEL_PCIE_BUF_LOC_MAX,
 };
 
+/* Config set types */
+enum btintel_pcie_dbg_config_set_type {
+	BTINTEL_PCIE_CFGSET_INVALID		= 0,
+	BTINTEL_PCIE_CFGSET_PERIPH_MAC		= 1,
+	BTINTEL_PCIE_CFGSET_PERIPH_PHY		= 2,
+	BTINTEL_PCIE_CFGSET_PERIPH_AUX		= 3,
+	BTINTEL_PCIE_CFGSET_DEVICE_MEM		= 4,
+	BTINTEL_PCIE_CFGSET_CSR			= 5,
+	BTINTEL_PCIE_CFGSET_DBGC_DRAM_ADDR	= 6,
+	BTINTEL_PCIE_CFGSET_PERIPH_SCRATCH	= 7,
+	BTINTEL_PCIE_CFGSET_MAX,
+};
+
+/* Types of Timepoints */
+enum btintel_pcie_dbg_timepoint {
+	BTINTEL_PCIE_TP_INVALID			= 0,
+	BTINTEL_PCIE_TP_EARLY			= 1,
+	BTINTEL_PCIE_TP_AFTER_ALIVE		= 2,
+	BTINTEL_PCIE_TP_POST_INIT		= 3,
+	BTINTEL_PCIE_TP_FW_ASSERT		= 4,
+	BTINTEL_PCIE_TP_FW_HW_ERROR		= 5,
+	BTINTEL_PCIE_TP_FW_TFD_Q_HANG		= 6,
+	BTINTEL_PCIE_TP_FW_DHC_NOTIF		= 7,
+	BTINTEL_PCIE_TP_FW_RSP_OR_NOTIF		= 8,
+	BTINTEL_PCIE_TP_USER_TRIGGER		= 9,
+	BTINTEL_PCIE_TP_PERIODIC		= 10,
+	BTINTEL_PCIE_TP_RESERVED		= 11,
+	BTINTEL_PCIE_TP_HOST_ASSERT		= 12,
+	BTINTEL_PCIE_TP_HOST_ALIVE_TIMEOUT	= 13,
+	BTINTEL_PCIE_TP_HOST_DEVICE_DISABLE	= 15,
+	BTINTEL_PCIE_TP_HOST_D3_START		= 16,
+	BTINTEL_PCIE_TP_HOST_D3_END		= 17,
+	BTINTEL_PCIE_TP_MAX_NUM,
+};
+
 /* TLV header */
 struct btintel_pcie_tlv_hdr {
 	__le32 type;
@@ -152,6 +188,27 @@ struct btintel_pcie_tlv_buf_alloc {
 	__le32 min_buf_size;
 } __packed;
 
+/*
+ * struct btintel_pcie_tlv_config_set - YoYo configuration-set TLV
+ * @hdr: Common TLV type and payload length
+ * @dbg_hdr: Debug TLV version and domain
+ * @time_point: Timepoint at which to apply this configuration
+ * @config_set_type: Target register or memory space
+ * @addr_offset: Offset added to each address/value pair address
+ * @addr_pair: Variable-length array of address/value pairs
+ *
+ * Describes configuration values to apply at a specified timepoint. Fields
+ * are stored in little-endian format in the packed firmware TLV.
+ */
+struct btintel_pcie_tlv_config_set {
+	struct btintel_pcie_tlv_hdr hdr;
+	struct btintel_pcie_dbg_hdr dbg_hdr;
+	__le32 time_point;
+	__le32 config_set_type;
+	__le32 addr_offset;
+	struct btintel_pcie_addr_value addr_pair[];
+} __packed;
+
 /* Parsed buffer allocation entry in the YoYo context */
 struct btintel_pcie_buf_alloc {
 	u32	alloc_id;
@@ -162,9 +219,20 @@ struct btintel_pcie_buf_alloc {
 	bool	present;
 };
 
+/* Parsed config set entry in yoyo ctx */
+struct btintel_pcie_yoyo_config_set {
+	struct list_head		list;
+	u32				time_point;
+	u32				config_set_type;
+	u32				addr_offset;
+	u32				num_pairs;
+	struct btintel_pcie_addr_value	*pairs;
+};
+
 /*
- * struct btintel_pcie_yoyo_ctx- Runtime state for YoYo debug configuration
+ * struct btintel_pcie_yoyo_ctx - Runtime state for YoYo debug configuration
  * @enabled: Whether the loaded YoYo configuration passed validation
+ * @dbg_info_present: Whether the mandatory DEBUG_INFO TLV was validated
  * @region_list: Parsed debug-region entries
  * @trigger_list: Parsed debug-trigger entries
  * @hcmd_list: Parsed HCI command entries
-- 
2.53.0


^ permalink raw reply	[flat|nested] 3+ messages in thread

* [PATCH v1 2/3] Bluetooth: btintel_pcie: Add yoyo configuration for debug region
  2026-11-09 17:31 [PATCH v1 1/3] Bluetooth: btintel_pcie: Add yoyo support for config set TLV Ajay KV
@ 2026-11-09 17:31 ` Ajay KV
  2026-11-09 17:31 ` [PATCH v1 3/3] Bluetooth: btintel_pcie: Add yoyo configuration for Hci cmd Tlvs Ajay KV
  1 sibling, 0 replies; 3+ messages in thread
From: Ajay KV @ 2026-11-09 17:31 UTC (permalink / raw)
  To: linux-bluetooth
  Cc: chandrashekar.devegowda, kiran.k, linux-kernel, luiz.dentz,
	marcel, stable, chethan.tumkur.narayan, lakshmipathi.k, Ajaykv

From: Ajaykv <ajay.k.v@intel.com>

Add parsing of the YoYo DEBUG_REGION TLV. A region TLV defines
a named, addressable debug region and registers it by region ID;
it does not trigger a dump by itself. A later trigger TLV
selects regions through a 64-bit region ID mask, and the dump
for each selected region is dispatched based on its stored
region type.

Each region TLV is validated for domain, version, region ID,
region type, address list and size. Invalid entries are skipped
with a warning. Valid regions are stored in the region list of
the YoYo context, replacing any existing entry with the same
region ID, and are freed when the YoYo context is cleaned up.

Signed-off-by: Ajaykv <ajay.k.v@intel.com>
---
 drivers/bluetooth/btintel_pcie_debug.c | 144 +++++++++++++++++++++++++
 drivers/bluetooth/btintel_pcie_debug.h | 133 +++++++++++++++++++++++
 2 files changed, 277 insertions(+)

diff --git a/drivers/bluetooth/btintel_pcie_debug.c b/drivers/bluetooth/btintel_pcie_debug.c
index c52ef24dd0b0..d132214dcc8c 100644
--- a/drivers/bluetooth/btintel_pcie_debug.c
+++ b/drivers/bluetooth/btintel_pcie_debug.c
@@ -503,6 +503,140 @@ static int btintel_pcie_yoyo_parse_config_set(struct btintel_pcie_yoyo_ctx *ctx,
 	return 0;
 }
 
+static int btintel_pcie_yoyo_parse_region(struct btintel_pcie_yoyo_ctx *ctx,
+					  const struct btintel_pcie_tlv_region *tlv,
+					  u32 len)
+{
+	struct btintel_pcie_yoyo_region *old_region, *region, *tmp;
+	u32 addr_count = 0, addr_offset = 0, domain, version;
+	u32 addr_bytes, alloc_id, size;
+	u16 region_id;
+
+	if (len < sizeof(*tlv) - sizeof(struct btintel_pcie_tlv_hdr)) {
+		BT_WARN("YoYo: region tlv is too short or Invalid");
+		return -EBADMSG;
+	}
+
+	domain = get_unaligned_le32(&tlv->dbg_hdr.domain);
+	version = get_unaligned_le32(&tlv->dbg_hdr.tlv_data_version);
+	if (domain != BTINTEL_PCIE_DOMAIN_BT ||
+	    version != BTINTEL_PCIE_REGION_TLV_VERSION) {
+		BT_WARN("YoYo: unsupported region domain/version (%u/%u), skipping",
+			domain, version);
+		return 0;
+	}
+
+	region_id = get_unaligned_le16(&tlv->region_id);
+	if (region_id > BTINTEL_PCIE_REGION_ID_MAX) {
+		BT_WARN("YoYo: invalid region_id %u, skipping", region_id);
+		return 0;
+	}
+
+	if (tlv->region_type <= BTINTEL_PCIE_REGION_INVALID ||
+	    tlv->region_type >= BTINTEL_PCIE_REGION_TYPE_MAX) {
+		BT_WARN("YoYo: invalid region type %u, skipping", tlv->region_type);
+		return 0;
+	}
+
+	/* Any trailing bytes after the fixed region structure are an address
+	 * list, regardless of region type, mirroring the reference parser.
+	 */
+	addr_offset = sizeof(*tlv) - sizeof(struct btintel_pcie_tlv_hdr);
+	addr_bytes = len - addr_offset;
+	if (addr_bytes % sizeof(__le32)) {
+		BT_WARN("YoYo: invalid region addr_bytes length %u", addr_bytes);
+		return 0;
+	}
+
+	addr_count = addr_bytes / sizeof(__le32);
+
+	/* Types without a size field default to a valid dword size. */
+	size = sizeof(u32);
+	switch (tlv->region_type) {
+	case BTINTEL_PCIE_REGION_DEVICE_MEM:
+		size = get_unaligned_le32(&tlv->conf_params.dev_addr.size_bytes);
+		fallthrough;
+	case BTINTEL_PCIE_REGION_PERIPH_MAC:
+	case BTINTEL_PCIE_REGION_CSR:
+		if (!addr_count) {
+			BT_WARN("YoYo: region id=%u type=%u has no addresses, skipping",
+				region_id, tlv->region_type);
+			return 0;
+		}
+		break;
+	case BTINTEL_PCIE_REGION_DRAM_BUF:
+		alloc_id = get_unaligned_le32(&tlv->conf_params.dram_alloc_id);
+		if (alloc_id < BTINTEL_PCIE_ALLOC_ID_DBGC1 ||
+		    alloc_id > BTINTEL_PCIE_ALLOC_ID_DBGC3) {
+			BT_WARN("YoYo: region id=%u invalid dram alloc id %u, skipping",
+				region_id, alloc_id);
+			return 0;
+		}
+		break;
+	case BTINTEL_PCIE_REGION_INTERNAL_BUF:
+		size = get_unaligned_le32(&tlv->conf_params.internal_buf.size_bytes);
+		break;
+	case BTINTEL_PCIE_REGION_SPECIAL_DEV_MEM:
+		size = get_unaligned_le32(&tlv->conf_params.special_dev_addr.size_bytes);
+		break;
+	default:
+		break;
+	}
+
+	if (!size || size % sizeof(u32)) {
+		BT_WARN("YoYo: region id=%u type=%u invalid size %u, skipping",
+			region_id, tlv->region_type, size);
+		return 0;
+	}
+
+	region = kzalloc(sizeof(*region), GFP_KERNEL);
+	if (!region)
+		return -ENOMEM;
+
+	region->region_id = region_id;
+	region->dump_policy = get_unaligned_le16(&tlv->region_dump_policy);
+	region->type = tlv->region_type;
+	region->sub_type = tlv->region_sub_type;
+	region->sub_type_ver = tlv->region_sub_type_ver;
+	memcpy(region->name, tlv->region_name, BTINTEL_PCIE_REGION_NAME_LEN);
+	memcpy(&region->conf, &tlv->conf_params, sizeof(region->conf));
+
+	if (addr_count) {
+		const u8 *addr_data = (const u8 *)tlv +
+				      sizeof(struct btintel_pcie_tlv_hdr) + addr_offset;
+		u32 i;
+
+		region->addrs = kmalloc_array(addr_count, sizeof(*region->addrs),
+					      GFP_KERNEL);
+		if (!region->addrs) {
+			kfree(region);
+			return -ENOMEM;
+		}
+
+		for (i = 0; i < addr_count; i++)
+			region->addrs[i] = get_unaligned_le32(addr_data +
+							     i * sizeof(__le32));
+	}
+	region->num_addrs = addr_count;
+
+	/* Swap into list if duplicate region_id exists, otherwise append */
+	list_for_each_entry_safe(old_region, tmp, &ctx->region_list, list) {
+		if (old_region->region_id == region_id) {
+			list_replace(&old_region->list, &region->list);
+			kfree(old_region->addrs);
+			kfree(old_region);
+			goto out;
+		}
+	}
+
+	list_add_tail(&region->list, &ctx->region_list);
+
+out:
+	BT_DBG("YoYo: region id=%u type=%u name='%.32s'",
+	       region->region_id, region->type, region->name);
+	return 0;
+}
+
 /*
  * Parse the optional YoYo firmware TLV stream and validate each record.
  * This is the entry point for the runtime debug configuration payload.
@@ -547,6 +681,8 @@ static int btintel_pcie_yoyo_parse_tlv_buffer(struct btintel_pcie_yoyo_ctx *ctx,
 			ret = btintel_pcie_yoyo_parse_config_set(ctx, (const void *)tlv, len);
 			break;
 		case BTINTEL_PCIE_TLV_TYPE_DBG_REGION:
+			ret = btintel_pcie_yoyo_parse_region(ctx, (const void *)tlv, len);
+			break;
 		case BTINTEL_PCIE_TLV_TYPE_DBG_TRIGGER:
 		case BTINTEL_PCIE_TLV_TYPE_DBG_HCMD:
 		case BTINTEL_PCIE_TLV_TYPE_DBG_PRESET_OVERRIDE:
@@ -611,6 +747,7 @@ EXPORT_SYMBOL_GPL(btintel_pcie_yoyo_init);
 void btintel_pcie_yoyo_cleanup(struct btintel_pcie_yoyo_ctx *ctx)
 {
 	struct btintel_pcie_yoyo_config_set *config_set, *config_tmp;
+	struct btintel_pcie_yoyo_region *region, *region_tmp;
 
 	if (!ctx)
 		return;
@@ -627,6 +764,13 @@ void btintel_pcie_yoyo_cleanup(struct btintel_pcie_yoyo_ctx *ctx)
 		kfree(config_set->pairs);
 		kfree(config_set);
 	}
+
+	list_for_each_entry_safe(region, region_tmp, &ctx->region_list, list) {
+		list_del(&region->list);
+		kfree(region->addrs);
+		kfree(region);
+	}
+
 }
 EXPORT_SYMBOL_GPL(btintel_pcie_yoyo_cleanup);
 
diff --git a/drivers/bluetooth/btintel_pcie_debug.h b/drivers/bluetooth/btintel_pcie_debug.h
index a9ecc7462d4e..f13107994fcf 100644
--- a/drivers/bluetooth/btintel_pcie_debug.h
+++ b/drivers/bluetooth/btintel_pcie_debug.h
@@ -60,6 +60,8 @@ bool btintel_pcie_fatal_err_testmode_active(struct btintel_pcie_data *data);
 #define BTINTEL_PCIE_DBG_INFO_TLV_VERSION	2
 #define BTINTEL_PCIE_BUF_ALLOC_TLV_VERSION	1
 #define BTINTEL_PCIE_CONFIG_SET_TLV_VERSION	1
+#define BTINTEL_PCIE_REGION_TLV_VERSION		3
+
 /* Debug Info TLV */
 #define BTINTEL_PCIE_CONFIG_NAME_LEN		64
 #define BTINTEL_PCIE_DOMAIN_BT			0
@@ -69,6 +71,9 @@ bool btintel_pcie_fatal_err_testmode_active(struct btintel_pcie_data *data);
 #define BTINTEL_PCIE_MAX_BUF_FRAGS		16
 #define BTINTEL_PCIE_MIN_BUF_SIZE		(32 * 1024)
 #define BTINTEL_PCIE_MAX_BUF_SIZE		(4 * 1024 * 1024)
+/* Region Tlv */
+#define BTINTEL_PCIE_REGION_ID_MAX		63
+#define BTINTEL_PCIE_REGION_NAME_LEN		32
 
 enum btintel_pcie_image_tlv_type {
 	BTINTEL_PCIE_IMAGE_TLV_TYPE_EXTERNAL	= 0x1,
@@ -129,6 +134,42 @@ enum btintel_pcie_dbg_timepoint {
 	BTINTEL_PCIE_TP_MAX_NUM,
 };
 
+/* Region types */
+enum btintel_pcie_dbg_region_type {
+	BTINTEL_PCIE_REGION_INVALID		= 0,
+	BTINTEL_PCIE_REGION_TLV		= 1,
+	BTINTEL_PCIE_REGION_INTERNAL_BUF	= 2,
+	BTINTEL_PCIE_REGION_DRAM_BUF		= 3,
+	BTINTEL_PCIE_REGION_DEVICE_TXFIFO	= 4,
+	BTINTEL_PCIE_REGION_DEVICE_RXFIFO	= 5,
+	BTINTEL_PCIE_REGION_LMAC_ERROR_TBL	= 6,
+	BTINTEL_PCIE_REGION_UMAC_ERROR_TBL	= 7,
+	BTINTEL_PCIE_REGION_FW_RSP_OR_NOTIF	= 8,
+	BTINTEL_PCIE_REGION_DEVICE_MEM		= 9,
+	BTINTEL_PCIE_REGION_PERIPH_MAC		= 10,
+	BTINTEL_PCIE_REGION_PERIPH_PHY		= 11,
+	BTINTEL_PCIE_REGION_PERIPH_AUX		= 12,
+	BTINTEL_PCIE_REGION_VM			= 13,
+	BTINTEL_PCIE_REGION_CSR			= 14,
+	BTINTEL_PCIE_REGION_DRAM_IMR		= 15,
+	BTINTEL_PCIE_REGION_PCI_IOSF_CFG	= 16,
+	BTINTEL_PCIE_REGION_SPECIAL_DEV_MEM	= 17,
+	BTINTEL_PCIE_REGION_DBGI_SRAM		= 18,
+	BTINTEL_PCIE_REGION_TYPE_MAX
+};
+
+/*
+ * Region dump policy flags for OS dump-size limits.
+ * @BTINTEL_PCIE_REGION_POLICY_NO_LIMIT: OS has no dump-size limit
+ * @BTINTEL_PCIE_REGION_POLICY_MAX_600KB: Wi-Fi LKD dump request
+ * @BTINTEL_PCIE_REGION_POLICY_MAX_5MB: Microsoft BSOD/LKD dump request
+ */
+enum btintel_pcie_dbg_region_policy {
+	BTINTEL_PCIE_REGION_POLICY_NO_LIMIT	= BIT(0),
+	BTINTEL_PCIE_REGION_POLICY_MAX_600KB	= BIT(1),
+	BTINTEL_PCIE_REGION_POLICY_MAX_5MB	= BIT(2),
+};
+
 /* TLV header */
 struct btintel_pcie_tlv_hdr {
 	__le32 type;
@@ -209,6 +250,84 @@ struct btintel_pcie_tlv_config_set {
 	struct btintel_pcie_addr_value addr_pair[];
 } __packed;
 
+/* Device address for region conf params */
+struct btintel_pcie_tlv_dev_addr {
+	__le32 size_bytes;
+	__le32 addr_offset;
+} __packed;
+
+/* Special device address */
+struct btintel_pcie_tlv_special_dev_addr {
+	__le16 special_addr_type;
+	__le16 struct_version;
+	__le32 base_addr;
+	__le32 size_bytes;
+	__le32 addr_offset;
+} __packed;
+
+/* FIFOs */
+struct btintel_pcie_tlv_fifos {
+	__le32 fifo_ids[2];
+	__le32 skip_data;
+	__le32 addr_offset;
+} __packed;
+
+/* Error table */
+struct btintel_pcie_tlv_error_table {
+	__le32 struct_version;
+	__le32 base_addr;
+	__le32 size_bytes;
+	__le32 addr_offset;
+} __packed;
+
+/* Internal buffer */
+struct btintel_pcie_tlv_internal_buf {
+	__le32 alloc_id;
+	__le32 base_addr;
+	__le32 size_bytes;
+} __packed;
+
+/* Region conf params union */
+union btintel_pcie_region_conf_params {
+	struct btintel_pcie_tlv_dev_addr	dev_addr;
+	struct btintel_pcie_tlv_special_dev_addr special_dev_addr;
+	struct btintel_pcie_tlv_fifos	fifos;
+	struct btintel_pcie_tlv_error_table	error_table;
+	struct btintel_pcie_tlv_internal_buf internal_buf;
+	__le32				dram_alloc_id;
+	__le32				tlv_output_bitmask;
+} __packed;
+
+/*
+ * struct btintel_pcie_tlv_region - YoYo debug region TLV
+ * @hdr: Common TLV type and payload length
+ * @dbg_hdr: Debug TLV version and domain
+ * @region_id: Identifier used to select this region for a trigger
+ * @region_dump_policy: Policy flags controlling dump size limits
+ * @region_type: Region storage or access type
+ * @region_sub_type: Region-specific subtype
+ * @region_sub_type_ver: Version of the region subtype data
+ * @reserved: Reserved for future use
+ * @region_name: Fixed-size region name
+ * @conf_params: Type-specific fixed configuration parameters
+ *
+ * Describes a debug region in the packed firmware TLV. Address-based region
+ * types may append a variable-length address array after @conf_params.
+ * Multi-byte fields are stored in little-endian format.
+ */
+struct btintel_pcie_tlv_region {
+	struct btintel_pcie_tlv_hdr hdr;
+	struct btintel_pcie_dbg_hdr dbg_hdr;
+	__le16 region_id;
+	__le16 region_dump_policy;
+	u8     region_type;
+	u8     region_sub_type;
+	u8     region_sub_type_ver;
+	u8     reserved;
+	u8     region_name[BTINTEL_PCIE_REGION_NAME_LEN];
+	union  btintel_pcie_region_conf_params conf_params;
+} __packed;
+
 /* Parsed buffer allocation entry in the YoYo context */
 struct btintel_pcie_buf_alloc {
 	u32	alloc_id;
@@ -229,6 +348,20 @@ struct btintel_pcie_yoyo_config_set {
 	struct btintel_pcie_addr_value	*pairs;
 };
 
+/* Parsed region entry */
+struct btintel_pcie_yoyo_region {
+	struct list_head	list;
+	u16			region_id;
+	u16			dump_policy;
+	u8			type;
+	u8			sub_type;
+	u8			sub_type_ver;
+	char		name[BTINTEL_PCIE_REGION_NAME_LEN];
+	u32			num_addrs;
+	u32			*addrs;
+	union btintel_pcie_region_conf_params conf;
+};
+
 /*
  * struct btintel_pcie_yoyo_ctx - Runtime state for YoYo debug configuration
  * @enabled: Whether the loaded YoYo configuration passed validation
-- 
2.53.0


^ permalink raw reply	[flat|nested] 3+ messages in thread

* [PATCH v1 3/3] Bluetooth: btintel_pcie: Add yoyo configuration for Hci cmd Tlvs
  2026-11-09 17:31 [PATCH v1 1/3] Bluetooth: btintel_pcie: Add yoyo support for config set TLV Ajay KV
  2026-11-09 17:31 ` [PATCH v1 2/3] Bluetooth: btintel_pcie: Add yoyo configuration for debug region Ajay KV
@ 2026-11-09 17:31 ` Ajay KV
  1 sibling, 0 replies; 3+ messages in thread
From: Ajay KV @ 2026-11-09 17:31 UTC (permalink / raw)
  To: linux-bluetooth
  Cc: chandrashekar.devegowda, kiran.k, linux-kernel, luiz.dentz,
	marcel, stable, chethan.tumkur.narayan, lakshmipathi.k, Ajaykv

From: Ajaykv <ajay.k.v@intel.com>

Add parsing of the YoYo HCI command TLV so that HCI commands can
be sent to the controller through the external TLV file. Each
TLV is validated for domain, version, timepoint and command
length. Only the ALIVE timepoint is supported, and periodic
commands are skipped with a warning. Valid commands are stored
in the HCI command list of the YoYo context and are freed when
the YoYo context is cleaned up.

When the ALIVE timepoint is reached after setup, the stored
commands are sent in order. A command is skipped if its
parameter length does not match the TLV payload. Sending stops
if the device is halted, in recovery, in D3 or unregistering,
or if a command fails because of a timeout or a lost device.
Other command failures are logged and do not stop normal
Bluetooth bring-up.

Signed-off-by: Ajaykv <ajay.k.v@intel.com>
---
 drivers/bluetooth/btintel_pcie.c       | 96 ++++++++++++++++++++++++++
 drivers/bluetooth/btintel_pcie_debug.c | 75 +++++++++++++++++++-
 drivers/bluetooth/btintel_pcie_debug.h | 46 ++++++++++--
 3 files changed, 212 insertions(+), 5 deletions(-)

diff --git a/drivers/bluetooth/btintel_pcie.c b/drivers/bluetooth/btintel_pcie.c
index de740477e6d7..8334ebea76ef 100644
--- a/drivers/bluetooth/btintel_pcie.c
+++ b/drivers/bluetooth/btintel_pcie.c
@@ -657,6 +657,8 @@ static inline void btintel_pcie_dump_debug_registers(struct hci_dev *hdev)
 	hci_recv_diag(hdev, skb);
 }
 
+static inline bool btintel_pcie_in_d3(struct btintel_pcie_data *data);
+
 static int btintel_pcie_send_sync(struct btintel_pcie_data *data,
 				  struct sk_buff *skb, u32 pkt_type, u16 opcode)
 {
@@ -927,6 +929,90 @@ static void btintel_pcie_release_mac_access(struct btintel_pcie_data *data)
 	}
 }
 
+/* btintel_yoyo_send_hcmds - Send all HCMDs configured for a timepoint */
+static void btintel_pcie_yoyo_send_hcmds(struct btintel_pcie_data *data,
+					 enum btintel_pcie_dbg_timepoint tp)
+{
+	struct btintel_pcie_yoyo_hcmd *hcmd;
+	struct sk_buff *skb;
+	u8 plen;
+	u16 opcode;
+
+	if (!data)
+		return;
+
+	/* Held across __hci_cmd_sync() so cleanup cannot free entries in use */
+	mutex_lock(&data->yoyo.lock);
+	if (!data->yoyo.enabled || !data->hdev) {
+		mutex_unlock(&data->yoyo.lock);
+		return;
+	}
+
+	list_for_each_entry(hcmd, &data->yoyo.hcmd_list, list) {
+		long err;
+
+		if (hcmd->time_point != tp)
+			continue;
+
+		/* Stop if device state changed or driver is shutting down */
+		if (test_bit(BTINTEL_PCIE_CORE_HALTED, &data->flags) ||
+		    test_bit(BTINTEL_PCIE_RECOVERY_IN_PROGRESS, &data->flags) ||
+		    btintel_pcie_in_d3(data) ||
+		    hci_dev_test_flag(data->hdev, HCI_UNREGISTER))
+			break;
+
+		if (hcmd->cmd_len < 3) {
+			bt_dev_warn(data->hdev, "YoYo: hci command too short (%u bytes)",
+				    hcmd->cmd_len);
+			continue;
+		}
+
+		opcode = get_unaligned_le16(hcmd->cmd_data);
+		plen = hcmd->cmd_data[2];
+
+		if (plen != hcmd->cmd_len - 3) {
+			bt_dev_warn(data->hdev,
+				    "YoYo: hci command 0x%04x plen %u does not match tlv payload %u",
+				    opcode, plen, hcmd->cmd_len - 3);
+			continue;
+		}
+
+		bt_dev_dbg(data->hdev, "YoYo: sending hci command, opcode=0x%04x len=%u at tp=%u",
+			   opcode, plen, tp);
+
+		skb = __hci_cmd_sync(data->hdev, opcode, plen,
+				     hcmd->cmd_data + 3,
+				     HCI_CMD_TIMEOUT);
+		/* HCI Err 0x11 corresponds to errno -95 (UNSUPPORTED)
+		 * HCI Err 0x12 corresponds to errno -22 (INVAL)
+		 * HCI Err 0x16 corresponds to errno -16 (EBUSY/CMD DISALLOWED)
+		 */
+		if (IS_ERR(skb)) {
+			err = PTR_ERR(skb);
+
+			/* Command completed with Command Status (0x0F) */
+			if (err == -ENODATA)
+				continue;
+
+			bt_dev_dbg(data->hdev, "YoYo: hci command 0x%04x failed: %ld",
+				   opcode, err);
+			/* A timeout already made the HCI core run hdev->reset
+			 * and start device recovery; stop issuing debug
+			 * commands instead of adding more timeouts to it.
+			 */
+			if (err == -ETIMEDOUT || err == -ENODEV ||
+			    err == -EHOSTDOWN || err == -EINTR)
+				break;
+		} else {
+			bt_dev_dbg(data->hdev, "YoYo: hci command 0x%04x completed successfully",
+				   opcode);
+			kfree_skb(skb);
+		}
+	}
+
+	mutex_unlock(&data->yoyo.lock);
+}
+
 static void
 btintel_pcie_apply_yoyo_config_sets(struct btintel_pcie_data *data,
 				    u32 time_point)
@@ -1046,11 +1132,16 @@ static void btintel_pcie_yoyo_handle_timepoint(struct btintel_pcie_data *data,
 	if (!data || !data->yoyo.enabled)
 		return;
 
+	BT_DBG("YoYo: Handling timepoint %u", tp);
+
 	if (tp <= BTINTEL_PCIE_TP_INVALID || tp >= BTINTEL_PCIE_TP_MAX_NUM) {
 		BT_WARN("YoYo: invalid timepoint %u", tp);
 		return;
 	}
 
+	/* PRE-DUMP phase: send HCMDs and apply config sets */
+	btintel_pcie_yoyo_send_hcmds(data, tp);
+
 	btintel_pcie_apply_yoyo_config_sets(data, tp);
 }
 
@@ -4265,9 +4356,14 @@ static int btintel_pcie_setup(struct hci_dev *hdev)
 		if (btintel_pcie_fatal_err_testmode_active(data))
 			set_bit(BTINTEL_PCIE_FATAL_ERR_POST_SETUP_PENDING, &data->flags);
 		set_bit(BTINTEL_PCIE_SETUP_DONE, &data->flags);
+
 		set_bit(BTINTEL_PCIE_HW_RFKILL_STATE_CHECK, &data->flags);
 		btintel_pcie_unmask_hw_rfkill(data);
 		btintel_delayed_hw_rfkill(data, true);
+
+		/* Notify YoYo: device is alive and operational */
+		btintel_pcie_yoyo_handle_timepoint(data,
+						   BTINTEL_PCIE_TP_AFTER_ALIVE);
 	}
 	return err;
 }
diff --git a/drivers/bluetooth/btintel_pcie_debug.c b/drivers/bluetooth/btintel_pcie_debug.c
index d132214dcc8c..a299867be1a7 100644
--- a/drivers/bluetooth/btintel_pcie_debug.c
+++ b/drivers/bluetooth/btintel_pcie_debug.c
@@ -637,6 +637,68 @@ static int btintel_pcie_yoyo_parse_region(struct btintel_pcie_yoyo_ctx *ctx,
 	return 0;
 }
 
+static int btintel_pcie_yoyo_parse_hcmd(struct btintel_pcie_yoyo_ctx *ctx,
+					const struct btintel_pcie_tlv_hcmd *tlv,
+					u32 len)
+{
+	u32 fixed_size = sizeof(*tlv) - sizeof(struct btintel_pcie_tlv_hdr);
+	u32 cmd_len, time_point, period_msec, domain, version;
+	struct btintel_pcie_yoyo_hcmd *hcmd;
+
+	if (len < fixed_size) {
+		BT_WARN("YoYo: hci cmd tlv is too short or Invalid");
+		return -EBADMSG;
+	}
+
+	domain = get_unaligned_le32(&tlv->dbg_hdr.domain);
+	version = get_unaligned_le32(&tlv->dbg_hdr.tlv_data_version);
+	if (domain != BTINTEL_PCIE_DOMAIN_BT ||
+	    version != BTINTEL_PCIE_HCMD_TLV_VERSION) {
+		BT_WARN("YoYo: unsupported hcmd domain/version (%u/%u), skipping",
+			domain, version);
+		return 0;
+	}
+
+	time_point = get_unaligned_le32(&tlv->time_point);
+	if (time_point != BTINTEL_PCIE_TP_AFTER_ALIVE) {
+		BT_WARN("YoYo: skipping hcmd for timepoint %u", time_point);
+		return 0;
+	}
+
+	cmd_len = len - fixed_size;
+	if (cmd_len < 3 || (cmd_len - 3) > 255) {
+		BT_WARN("YoYo: invalid HCMD payload length (%u)", cmd_len);
+		return 0;
+	}
+
+	period_msec = get_unaligned_le32(&tlv->period_msec);
+	if (period_msec) {
+		BT_WARN("YoYo: periodic hcmd not supported (period=%u ms), skipping",
+			period_msec);
+		return 0;
+	}
+
+	hcmd = kzalloc_obj(*hcmd, GFP_KERNEL);
+	if (!hcmd)
+		return -ENOMEM;
+
+	hcmd->time_point = time_point;
+	hcmd->period_msec = period_msec;
+	hcmd->cmd_len = cmd_len;
+
+	hcmd->cmd_data = kmemdup(tlv->cmd_data, cmd_len, GFP_KERNEL);
+	if (!hcmd->cmd_data) {
+		kfree(hcmd);
+		return -ENOMEM;
+	}
+
+	BT_DBG("YoYo: hcmd tp=%u period=%u cmd_len=%u",
+	       hcmd->time_point, hcmd->period_msec, hcmd->cmd_len);
+
+	list_add_tail(&hcmd->list, &ctx->hcmd_list);
+	return 0;
+}
+
 /*
  * Parse the optional YoYo firmware TLV stream and validate each record.
  * This is the entry point for the runtime debug configuration payload.
@@ -683,8 +745,10 @@ static int btintel_pcie_yoyo_parse_tlv_buffer(struct btintel_pcie_yoyo_ctx *ctx,
 		case BTINTEL_PCIE_TLV_TYPE_DBG_REGION:
 			ret = btintel_pcie_yoyo_parse_region(ctx, (const void *)tlv, len);
 			break;
-		case BTINTEL_PCIE_TLV_TYPE_DBG_TRIGGER:
 		case BTINTEL_PCIE_TLV_TYPE_DBG_HCMD:
+			ret = btintel_pcie_yoyo_parse_hcmd(ctx, (const void *)tlv, len);
+			break;
+		case BTINTEL_PCIE_TLV_TYPE_DBG_TRIGGER:
 		case BTINTEL_PCIE_TLV_TYPE_DBG_PRESET_OVERRIDE:
 			BT_DBG("YoYo: valid tlv type 0x%x len=%u", type, len);
 			break;
@@ -728,6 +792,7 @@ int btintel_pcie_yoyo_init(struct btintel_pcie_yoyo_ctx *ctx)
 		return -EINVAL;
 
 	memset(ctx, 0, sizeof(*ctx));
+	mutex_init(&ctx->lock);
 	INIT_LIST_HEAD(&ctx->region_list);
 	INIT_LIST_HEAD(&ctx->trigger_list);
 	INIT_LIST_HEAD(&ctx->hcmd_list);
@@ -748,10 +813,12 @@ void btintel_pcie_yoyo_cleanup(struct btintel_pcie_yoyo_ctx *ctx)
 {
 	struct btintel_pcie_yoyo_config_set *config_set, *config_tmp;
 	struct btintel_pcie_yoyo_region *region, *region_tmp;
+	struct btintel_pcie_yoyo_hcmd *hcmd, *hcmd_tmp;
 
 	if (!ctx)
 		return;
 
+	mutex_lock(&ctx->lock);
 	ctx->enabled = false;
 	ctx->dbg_info_present = false;
 	ctx->num_buf_allocs = 0;
@@ -771,6 +838,12 @@ void btintel_pcie_yoyo_cleanup(struct btintel_pcie_yoyo_ctx *ctx)
 		kfree(region);
 	}
 
+	list_for_each_entry_safe(hcmd, hcmd_tmp, &ctx->hcmd_list, list) {
+		list_del(&hcmd->list);
+		kfree(hcmd->cmd_data);
+		kfree(hcmd);
+	}
+	mutex_unlock(&ctx->lock);
 }
 EXPORT_SYMBOL_GPL(btintel_pcie_yoyo_cleanup);
 
diff --git a/drivers/bluetooth/btintel_pcie_debug.h b/drivers/bluetooth/btintel_pcie_debug.h
index f13107994fcf..cada3b8ecda5 100644
--- a/drivers/bluetooth/btintel_pcie_debug.h
+++ b/drivers/bluetooth/btintel_pcie_debug.h
@@ -10,6 +10,7 @@
 #define __BTINTEL_PCIE_DEBUG_H__
 
 #include <linux/list.h>
+#include <linux/mutex.h>
 #include <linux/types.h>
 #include <linux/device.h>
 #include <linux/firmware.h>
@@ -61,6 +62,7 @@ bool btintel_pcie_fatal_err_testmode_active(struct btintel_pcie_data *data);
 #define BTINTEL_PCIE_BUF_ALLOC_TLV_VERSION	1
 #define BTINTEL_PCIE_CONFIG_SET_TLV_VERSION	1
 #define BTINTEL_PCIE_REGION_TLV_VERSION		3
+#define BTINTEL_PCIE_HCMD_TLV_VERSION		1
 
 /* Debug Info TLV */
 #define BTINTEL_PCIE_CONFIG_NAME_LEN		64
@@ -328,7 +330,29 @@ struct btintel_pcie_tlv_region {
 	union  btintel_pcie_region_conf_params conf_params;
 } __packed;
 
-/* Parsed buffer allocation entry in the YoYo context */
+/*
+ * struct btintel_pcie_tlv_hcmd - YoYo HCI command TLV
+ * @hdr: Common TLV type and payload length
+ * @dbg_hdr: Debug TLV version and domain
+ * @time_point: Timepoint at which to send the command
+ * @period_msec: Periodic interval in milliseconds (0 for one-time execution)
+ * @reserved: Command header (cmd_id, group_id, le16 seq_num); written as zero
+ *	      by the tooling and not used by the driver
+ * @cmd_data: Variable-length raw HCI command packet
+ *
+ * Describes an HCI command to dispatch at a specified timepoint. Multi-byte
+ * fields are stored in little-endian format in the packed firmware TLV.
+ */
+struct btintel_pcie_tlv_hcmd {
+	struct btintel_pcie_tlv_hdr hdr;
+	struct btintel_pcie_dbg_hdr dbg_hdr;
+	__le32 time_point;
+	__le32 period_msec;
+	u8     reserved[4];
+	u8     cmd_data[];
+} __packed;
+
+/* Parsed buffer allocation entry */
 struct btintel_pcie_buf_alloc {
 	u32	alloc_id;
 	u32	buf_location;
@@ -338,7 +362,7 @@ struct btintel_pcie_buf_alloc {
 	bool	present;
 };
 
-/* Parsed config set entry in yoyo ctx */
+/* Parsed config set entry */
 struct btintel_pcie_yoyo_config_set {
 	struct list_head		list;
 	u32				time_point;
@@ -362,6 +386,15 @@ struct btintel_pcie_yoyo_region {
 	union btintel_pcie_region_conf_params conf;
 };
 
+/* Parsed HCMD entry */
+struct btintel_pcie_yoyo_hcmd {
+	struct list_head	list;
+	u32			time_point;
+	u32			period_msec;
+	u32			cmd_len;
+	u8			*cmd_data;
+};
+
 /*
  * struct btintel_pcie_yoyo_ctx - Runtime state for YoYo debug configuration
  * @enabled: Whether the loaded YoYo configuration passed validation
@@ -373,6 +406,7 @@ struct btintel_pcie_yoyo_region {
  * @buf_alloc: Parsed buffer allocation entries
  * @num_buf_allocs: Number of parsed buffer allocation entries
  * @hdev: Associated HCI device; NULL until successful HCI registration
+ * @lock: Mutex protecting the parsed TLV lists against teardown
  *
  * Stores the validation state and parsed TLV data for the optional YoYo
  * firmware configuration.
@@ -385,8 +419,12 @@ struct btintel_pcie_yoyo_ctx {
 	struct list_head	 hcmd_list;
 	struct list_head	 config_set_list;
 	struct btintel_pcie_buf_alloc buf_alloc[BTINTEL_PCIE_MAX_BUF_ALLOC];
-	int			 num_buf_allocs;
-	struct hci_dev		 *hdev;
+	int			num_buf_allocs;
+	struct hci_dev		*hdev;
+	/* Serializes list teardown against consumers that sleep while
+	 * holding a list-entry pointer, such as HCMD dispatch.
+	 */
+	struct mutex		lock;
 };
 
 int  btintel_pcie_yoyo_init(struct btintel_pcie_yoyo_ctx *ctx);
-- 
2.53.0


^ permalink raw reply	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2026-11-09 17:32 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-11-09 17:31 [PATCH v1 1/3] Bluetooth: btintel_pcie: Add yoyo support for config set TLV Ajay KV
2026-11-09 17:31 ` [PATCH v1 2/3] Bluetooth: btintel_pcie: Add yoyo configuration for debug region Ajay KV
2026-11-09 17:31 ` [PATCH v1 3/3] Bluetooth: btintel_pcie: Add yoyo configuration for Hci cmd Tlvs Ajay KV

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®