summaryrefslogtreecommitdiff
path: root/drivers/bluetooth
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/bluetooth')
-rw-r--r--drivers/bluetooth/Kconfig2
-rw-r--r--drivers/bluetooth/bpa10x.c8
-rw-r--r--drivers/bluetooth/btintel.c10
-rw-r--r--drivers/bluetooth/btintel.h9
-rw-r--r--drivers/bluetooth/btintel_pcie.c531
-rw-r--r--drivers/bluetooth/btintel_pcie.h26
-rw-r--r--drivers/bluetooth/btmtk.c4
-rw-r--r--drivers/bluetooth/btrtl.c5
-rw-r--r--drivers/bluetooth/hci_qca.c11
-rw-r--r--drivers/bluetooth/hci_vhci.c10
10 files changed, 530 insertions, 86 deletions
diff --git a/drivers/bluetooth/Kconfig b/drivers/bluetooth/Kconfig
index c5d45cf91f88..fc1b37044a9b 100644
--- a/drivers/bluetooth/Kconfig
+++ b/drivers/bluetooth/Kconfig
@@ -502,7 +502,7 @@ config BT_NXPUART
config BT_INTEL_PCIE
tristate "Intel HCI PCIe driver"
- depends on PCI
+ depends on PCI && ACPI
select BT_INTEL
select FW_LOADER
help
diff --git a/drivers/bluetooth/bpa10x.c b/drivers/bluetooth/bpa10x.c
index 2ae38a321c4b..e63d1af250ec 100644
--- a/drivers/bluetooth/bpa10x.c
+++ b/drivers/bluetooth/bpa10x.c
@@ -255,9 +255,13 @@ static int bpa10x_setup(struct hci_dev *hdev)
if (IS_ERR(skb))
return PTR_ERR(skb);
- bt_dev_info(hdev, "%s", (char *)(skb->data + 1));
+ /* Bounded print: the device controls skb->len. */
+ if (skb->len > 1) {
+ int len = skb->len - 1;
- hci_set_fw_info(hdev, "%s", skb->data + 1);
+ bt_dev_info(hdev, "%.*s", len, (char *)(skb->data + 1));
+ hci_set_fw_info(hdev, "%.*s", len, skb->data + 1);
+ }
kfree_skb(skb);
return 0;
diff --git a/drivers/bluetooth/btintel.c b/drivers/bluetooth/btintel.c
index dcaaa4ca02b9..5e9cac090bd8 100644
--- a/drivers/bluetooth/btintel.c
+++ b/drivers/bluetooth/btintel.c
@@ -67,9 +67,10 @@ static struct {
u32 fw_build_num;
} coredump_info;
-static const guid_t btintel_guid_dsm =
+const guid_t btintel_guid_dsm =
GUID_INIT(0xaa10f4e0, 0x81ac, 0x4233,
0xab, 0xf6, 0x3b, 0x2a, 0xc5, 0x0e, 0x28, 0xd9);
+EXPORT_SYMBOL_GPL(btintel_guid_dsm);
int btintel_check_bdaddr(struct hci_dev *hdev)
{
@@ -2624,7 +2625,7 @@ static void btintel_set_ppag(struct hci_dev *hdev, struct intel_version_tlv *ver
kfree_skb(skb);
}
-static int btintel_acpi_reset_method(struct hci_dev *hdev)
+int btintel_acpi_reset_method(struct hci_dev *hdev)
{
int ret = 0;
acpi_status status;
@@ -2632,14 +2633,14 @@ static int btintel_acpi_reset_method(struct hci_dev *hdev)
struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
status = acpi_evaluate_object(ACPI_HANDLE(GET_HCIDEV_DEV(hdev)), "_PRR", NULL, &buffer);
- if (ACPI_FAILURE(status)) {
+ if (ACPI_FAILURE(status) || !buffer.pointer) {
bt_dev_err(hdev, "Failed to run _PRR method");
ret = -ENODEV;
return ret;
}
p = buffer.pointer;
- if (p->package.count != 1 || p->type != ACPI_TYPE_PACKAGE) {
+ if (p->type != ACPI_TYPE_PACKAGE || p->package.count != 1) {
bt_dev_err(hdev, "Invalid arguments");
ret = -EINVAL;
goto exit_on_error;
@@ -2663,6 +2664,7 @@ exit_on_error:
kfree(buffer.pointer);
return ret;
}
+EXPORT_SYMBOL_GPL(btintel_acpi_reset_method);
static void btintel_set_dsm_reset_method(struct hci_dev *hdev,
struct intel_version_tlv *ver_tlv)
diff --git a/drivers/bluetooth/btintel.h b/drivers/bluetooth/btintel.h
index 0e9ca99aaaae..37d93abdd5a3 100644
--- a/drivers/bluetooth/btintel.h
+++ b/drivers/bluetooth/btintel.h
@@ -53,6 +53,7 @@ struct intel_tlv {
} __packed;
#define BTINTEL_HCI_OP_RESET 0xfc01
+#define BTINTEL_HCI_OP_DEBUG 0xfcd9
#define BTINTEL_CNVI_BLAZARI 0x900 /* BlazarI - Lunar Lake */
#define BTINTEL_CNVI_BLAZARIW 0x901 /* BlazarIW - Wildcat Lake */
@@ -77,8 +78,11 @@ struct intel_tlv {
#define BTINTEL_HWID_BZRU 0x1d /* BlazarU - Meteor Lake */
#define BTINTEL_HWID_SCP 0x1f /* Scorpius Peak - Panther Lake */
#define BTINTEL_HWID_SCP2 0x20 /* Scorpius Peak2 - Nova Lake */
+#define BTINTEL_HWID_SCP2F 0x21 /* Scorpius Peak2-F - Nova Lake */
#define BTINTEL_HWID_BZRIW 0x22 /* BlazarIW - Wildcat Lake */
+extern const guid_t btintel_guid_dsm;
+
struct intel_version_tlv {
u32 cnvi_top;
u32 cnvr_top;
@@ -289,6 +293,7 @@ int btintel_bootloader_setup_tlv(struct hci_dev *hdev,
int btintel_shutdown_combined(struct hci_dev *hdev);
void btintel_hw_error(struct hci_dev *hdev, u8 code);
void btintel_print_fseq_info(struct hci_dev *hdev);
+int btintel_acpi_reset_method(struct hci_dev *hdev);
#else
static inline int btintel_check_bdaddr(struct hci_dev *hdev)
@@ -422,4 +427,8 @@ static inline void btintel_hw_error(struct hci_dev *hdev, u8 code)
static inline void btintel_print_fseq_info(struct hci_dev *hdev)
{
}
+static inline int btintel_acpi_reset_method(struct hci_dev *hdev)
+{
+ return -ENODEV;
+}
#endif
diff --git a/drivers/bluetooth/btintel_pcie.c b/drivers/bluetooth/btintel_pcie.c
index 37e050763633..be9348960348 100644
--- a/drivers/bluetooth/btintel_pcie.c
+++ b/drivers/bluetooth/btintel_pcie.c
@@ -15,6 +15,7 @@
#include <linux/wait.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
+#include <linux/acpi.h>
#include <linux/unaligned.h>
#include <linux/devcoredump.h>
@@ -102,6 +103,22 @@ enum {
BTINTEL_PCIE_D3
};
+enum {
+ BTINTEL_PCIE_DSM_SET_RESET_TIMING = 1,
+ BTINTEL_PCIE_DSM_GET_RESET_TIMING = 2,
+ BTINTEL_PCIE_DSM_BT_PLDR_CONFIG = 3,
+ BTINTEL_PCIE_DSM_GET_RESET_TYPE = 4,
+ BTINTEL_PCIE_DSM_DYNAMIC_PLDR = 5,
+ BTINTEL_PCIE_DSM_GET_RESET_METHOD = 6,
+ BTINTEL_PCIE_DSM_SET_PLDR_DELAY = 7,
+};
+
+enum btintel_dsm_internal_product_reset_mode {
+ BTINTEL_PCIE_DSM_PLDR_MODE_EN_PROD_RESET = BIT(0),
+ BTINTEL_PCIE_DSM_PLDR_MODE_EN_WIFI_FLR = BIT(1),
+ BTINTEL_PCIE_DSM_PLDR_MODE_EN_BT_OFF_ON = BIT(2),
+};
+
/* Structure for dbgc fragment buffer
* @buf_addr_lsb: LSB of the buffer's physical address
* @buf_addr_msb: MSB of the buffer's physical address
@@ -128,10 +145,21 @@ struct btintel_pcie_dbgc_ctxt {
struct btintel_pcie_dbgc_ctxt_buf bufs[BTINTEL_PCIE_DBGC_BUFFER_COUNT];
};
-struct btintel_pcie_removal {
- struct pci_dev *pdev;
- struct work_struct work;
-};
+struct btintel_pcie_trigger_evt {
+ u8 type;
+ u8 len;
+ __le32 addr;
+ __le32 size;
+} __packed;
+
+struct btintel_pcie_fwtrigger_evt {
+ __le32 reserved;
+ u8 type; /* Debug Trigger event */
+ __le16 len;
+ u8 event_type;
+ __le16 event_id;
+ __le16 reserved2;
+} __packed;
static LIST_HEAD(btintel_pcie_recovery_list);
static DEFINE_SPINLOCK(btintel_pcie_recovery_lock);
@@ -626,9 +654,10 @@ static void *btintel_pcie_copy_tlv(void *dest, enum btintel_pcie_tlv_type type,
static int btintel_pcie_read_dram_buffers(struct btintel_pcie_data *data)
{
u32 offset, prev_size, wr_ptr_status, dump_size, data_len;
+ u32 status_reg, wrap_reg;
struct btintel_pcie_dbgc *dbgc = &data->dbgc;
struct hci_dev *hdev = data->hdev;
- u8 *pdata, *p, buf_idx;
+ u8 *pdata, *p, buf_idx, hw_variant;
struct intel_tlv *tlv;
struct timespec64 now;
struct tm tm_now;
@@ -641,7 +670,28 @@ static int btintel_pcie_read_dram_buffers(struct btintel_pcie_data *data)
return -EOPNOTSUPP;
- wr_ptr_status = btintel_pcie_rd_dev_mem(data, BTINTEL_PCIE_DBGC_CUR_DBGBUFF_STATUS);
+ hw_variant = INTEL_HW_VARIANT(data->cnvi);
+ switch (hw_variant) {
+ case BTINTEL_HWID_BZRI:
+ case 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:
+ 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)",
+ 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);
+
offset = wr_ptr_status & BTINTEL_PCIE_DBG_OFFSET_BIT_MASK;
buf_idx = BTINTEL_PCIE_DBGC_DBG_BUF_IDX(wr_ptr_status);
@@ -684,6 +734,11 @@ static int btintel_pcie_read_dram_buffers(struct btintel_pcie_data *data)
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);
+ }
+
/*
* sizeof(u32) - signature
* sizeof(data_len) - to store tlv data size
@@ -718,10 +773,6 @@ static int btintel_pcie_read_dram_buffers(struct btintel_pcie_data *data)
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));
-
- data->dmp_hdr.wrap_ctr = btintel_pcie_rd_dev_mem(data,
- BTINTEL_PCIE_DBGC_DBGBUFF_WRAP_ARND);
-
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_git_sha1,
@@ -731,6 +782,17 @@ static int btintel_pcie_read_dram_buffers(struct btintel_pcie_data *data)
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;
@@ -1318,6 +1380,73 @@ exit_on_error:
kfree(buf);
}
+static int btintel_pcie_dump_fwtrigger_event(struct btintel_pcie_data *data)
+{
+ struct btintel_pcie_fwtrigger_evt *evt;
+ struct sk_buff *skb;
+ unsigned int len;
+ int err;
+ u8 *buf;
+
+ if (!data->debug_evt_size || !data->debug_evt_addr)
+ return -EINVAL;
+
+ len = data->debug_evt_size;
+
+ len = ALIGN_DOWN(len, 4);
+
+ if (len < sizeof(*evt) || len > HCI_MAX_EVENT_SIZE) {
+ bt_dev_err(data->hdev, "Invalid FW trigger data size (%u bytes)", len);
+ return -EINVAL;
+ }
+
+ buf = kzalloc(len, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ btintel_pcie_mac_init(data);
+
+ err = btintel_pcie_read_device_mem(data, buf, data->debug_evt_addr,
+ len);
+ if (err)
+ 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);
+
+ skb = bt_skb_alloc(len, GFP_KERNEL);
+ if (!skb) {
+ err = -ENOMEM;
+ goto exit_on_error;
+ }
+ skb_put_data(skb, buf, len);
+
+ hci_recv_diag(data->hdev, skb);
+ err = 0;
+
+exit_on_error:
+ kfree(buf);
+ return err;
+}
+
+static void btintel_pcie_msix_fw_trigger_handler(struct btintel_pcie_data *data)
+{
+ bt_dev_dbg(data->hdev, "Received firmware smart trigger cause");
+
+ if (test_and_set_bit(BTINTEL_PCIE_FWTRIGGER_DUMP_INPROGRESS, &data->flags))
+ return;
+
+ /* Trigger device core dump when there is FW assert */
+ if (!test_and_set_bit(BTINTEL_PCIE_COREDUMP_INPROGRESS, &data->flags))
+ data->dmp_hdr.trigger_reason = BTINTEL_PCIE_TRIGGER_REASON_FW_ASSERT;
+
+ queue_work(data->coredump_workqueue, &data->coredump_work);
+}
+
static void btintel_pcie_msix_hw_exp_handler(struct btintel_pcie_data *data)
{
bt_dev_err(data->hdev, "Received hw exception interrupt");
@@ -1332,14 +1461,27 @@ static void btintel_pcie_msix_hw_exp_handler(struct btintel_pcie_data *data)
if (!test_and_set_bit(BTINTEL_PCIE_COREDUMP_INPROGRESS, &data->flags))
data->dmp_hdr.trigger_reason = BTINTEL_PCIE_TRIGGER_REASON_FW_ASSERT;
- queue_work(data->workqueue, &data->rx_work);
+ queue_work(data->coredump_workqueue, &data->coredump_work);
}
-static void btintel_pcie_rx_work(struct work_struct *work)
+static void btintel_pcie_coredump_worker(struct work_struct *work)
{
struct btintel_pcie_data *data = container_of(work,
- struct btintel_pcie_data, rx_work);
- struct sk_buff *skb;
+ struct btintel_pcie_data, coredump_work);
+ int err;
+
+ /* hdev is NULL until setup_hdev() succeeds, and is cleared on
+ * teardown after disable_work_sync() drains us; bail in that case.
+ */
+ if (!data->hdev)
+ return;
+
+ if (test_bit(BTINTEL_PCIE_FWTRIGGER_DUMP_INPROGRESS, &data->flags)) {
+ err = btintel_pcie_dump_fwtrigger_event(data);
+ if (err)
+ bt_dev_warn(data->hdev, "failed to log fwtrigger event");
+ clear_bit(BTINTEL_PCIE_FWTRIGGER_DUMP_INPROGRESS, &data->flags);
+ }
if (test_bit(BTINTEL_PCIE_COREDUMP_INPROGRESS, &data->flags)) {
btintel_pcie_dump_traces(data->hdev);
@@ -1357,6 +1499,13 @@ static void btintel_pcie_rx_work(struct work_struct *work)
btintel_pcie_read_hwexp(data);
clear_bit(BTINTEL_PCIE_HWEXP_INPROGRESS, &data->flags);
}
+}
+
+static void btintel_pcie_rx_work(struct work_struct *work)
+{
+ struct btintel_pcie_data *data = container_of(work,
+ struct btintel_pcie_data, rx_work);
+ struct sk_buff *skb;
/* Process the sk_buf in queue and send to the HCI layer */
while ((skb = skb_dequeue(&data->rx_skb_q))) {
@@ -1497,6 +1646,9 @@ static irqreturn_t btintel_pcie_irq_msix_handler(int irq, void *dev_id)
btintel_pcie_msix_tx_handle(data);
}
+ if (intr_hw & BTINTEL_PCIE_MSIX_HW_INT_CAUSES_FWTRIG)
+ btintel_pcie_msix_fw_trigger_handler(data);
+
/* This interrupt is triggered by the firmware after updating
* boot_stage register and image_response register
*/
@@ -1571,6 +1723,7 @@ static struct btintel_pcie_causes_list causes_list[] = {
{ BTINTEL_PCIE_MSIX_FH_INT_CAUSES_1, BTINTEL_PCIE_CSR_MSIX_FH_INT_MASK, 0x01 },
{ BTINTEL_PCIE_MSIX_HW_INT_CAUSES_GP0, BTINTEL_PCIE_CSR_MSIX_HW_INT_MASK, 0x20 },
{ BTINTEL_PCIE_MSIX_HW_INT_CAUSES_HWEXP, BTINTEL_PCIE_CSR_MSIX_HW_INT_MASK, 0x23 },
+ { BTINTEL_PCIE_MSIX_HW_INT_CAUSES_FWTRIG, BTINTEL_PCIE_CSR_MSIX_HW_INT_MASK, 0x25 },
};
/* This function configures the interrupt masks for both HW_INT_CAUSES and
@@ -1961,6 +2114,9 @@ static int btintel_pcie_send_frame(struct hci_dev *hdev,
if (test_bit(BTINTEL_PCIE_CORE_HALTED, &data->flags))
return -ENODEV;
+ if (test_bit(BTINTEL_PCIE_RECOVERY_IN_PROGRESS, &data->flags))
+ return -ENODEV;
+
/* Due to the fw limitation, the type header of the packet should be
* 4 bytes unlike 1 byte for UART. In UART, the firmware can read
* the first byte to get the packet type and redirect the rest of data
@@ -2027,9 +2183,11 @@ exit_error:
static void btintel_pcie_release_hdev(struct btintel_pcie_data *data)
{
- struct hci_dev *hdev;
+ struct hci_dev *hdev = data->hdev;
+
+ if (!hdev)
+ return;
- hdev = data->hdev;
hci_unregister_dev(hdev);
hci_free_dev(hdev);
data->hdev = NULL;
@@ -2057,6 +2215,55 @@ static void btintel_pcie_synchronize_irqs(struct btintel_pcie_data *data)
synchronize_irq(data->msix_entries[i].vector);
}
+static int btintel_pcie_get_debug_info_addr(struct hci_dev *hdev)
+{
+ struct btintel_pcie_data *data = hci_get_drvdata(hdev);
+ struct btintel_pcie_trigger_evt *evt;
+ u8 param[1] = {0x10};
+ struct sk_buff *skb;
+ int err = 0;
+
+ skb = __hci_cmd_sync(hdev, BTINTEL_HCI_OP_DEBUG, 1, param,
+ HCI_CMD_TIMEOUT);
+ if (IS_ERR(skb)) {
+ bt_dev_err(hdev, "Reading Intel read debug info address command failed (%ld)",
+ PTR_ERR(skb));
+ /* Not all Intel products supports this command */
+ if (PTR_ERR(skb) == -EOPNOTSUPP)
+ return 0;
+ return PTR_ERR(skb);
+ }
+
+ if (skb->len < (1 + sizeof(*evt))) {
+ bt_dev_err(hdev, "Debug info response too short (%u bytes)", skb->len);
+ err = -EIO;
+ goto exit_error;
+ }
+
+ /* Check the status */
+ if (skb->data[0]) {
+ bt_dev_err(hdev, "Reading Intel read debug info command failed (0x%2.2x)",
+ skb->data[0]);
+ err = -EIO;
+ goto exit_error;
+ }
+
+ /* Consume Command Complete Status field */
+ skb_pull(skb, 1);
+
+ evt = (void *)skb->data;
+
+ data->debug_evt_addr = le32_to_cpu(evt->addr);
+ data->debug_evt_size = le32_to_cpu(evt->size);
+
+ bt_dev_dbg(hdev, "config type: %u config len: %u debug event addr: 0x%8.8x size: 0x%8.8x",
+ evt->type, evt->len, data->debug_evt_addr,
+ data->debug_evt_size);
+exit_error:
+ kfree_skb(skb);
+ return err;
+}
+
static int btintel_pcie_setup_internal(struct hci_dev *hdev)
{
struct btintel_pcie_data *data = hci_get_drvdata(hdev);
@@ -2156,6 +2363,10 @@ static int btintel_pcie_setup_internal(struct hci_dev *hdev)
if (ver_tlv.img_type == 0x02 || ver_tlv.img_type == 0x03)
data->dmp_hdr.fw_git_sha1 = ver_tlv.git_sha1;
+ err = btintel_pcie_get_debug_info_addr(hdev);
+ if (err)
+ goto exit_error;
+
btintel_print_fseq_info(hdev);
exit_error:
kfree_skb(skb);
@@ -2264,65 +2475,202 @@ static void btintel_pcie_inc_recovery_count(struct pci_dev *pdev,
}
}
-static int btintel_pcie_setup_hdev(struct btintel_pcie_data *data);
+static void btintel_pcie_reset(struct hci_dev *hdev);
-static void btintel_pcie_removal_work(struct work_struct *wk)
+static int btintel_pcie_acpi_reset_method(struct btintel_pcie_data *data)
{
- struct btintel_pcie_removal *removal =
- container_of(wk, struct btintel_pcie_removal, work);
- struct pci_dev *pdev = removal->pdev;
- struct btintel_pcie_data *data;
+ union acpi_object *obj, argv4;
+ acpi_handle handle;
+ int ret;
+ struct pldr_mode {
+ __le16 cmd_type;
+ __le16 cmd_payload;
+ } __packed;
+
+ /* set 1 for _PRR mode
+ * Product Reset (PLDR Abort flow)
+ */
+ static const struct pldr_mode mode = {
+ .cmd_type = cpu_to_le16(1),
+ .cmd_payload = cpu_to_le16(BTINTEL_PCIE_DSM_PLDR_MODE_EN_PROD_RESET |
+ BTINTEL_PCIE_DSM_PLDR_MODE_EN_WIFI_FLR),
+ };
+ struct hci_dev *hdev = data->hdev;
+
+ handle = ACPI_HANDLE(GET_HCIDEV_DEV(data->hdev));
+ if (!handle) {
+ bt_dev_err(data->hdev, "No support for bluetooth device in ACPI firmware");
+ return -EACCES;
+ }
+
+ if (!acpi_has_method(handle, "_PRR")) {
+ bt_dev_err(data->hdev, "No support for _PRR ACPI method, cold boot");
+ return -ENODEV;
+ }
+
+ argv4.buffer.type = ACPI_TYPE_BUFFER;
+ argv4.buffer.length = sizeof(mode);
+ argv4.buffer.pointer = (void *)&mode;
+
+ obj = acpi_evaluate_dsm(handle, &btintel_guid_dsm, 0,
+ BTINTEL_PCIE_DSM_DYNAMIC_PLDR, &argv4);
+ if (!obj) {
+ bt_dev_err(data->hdev, "Failed to call dsm to set reset method");
+ return -EIO;
+ }
+ ACPI_FREE(obj);
+
+ pci_dev_lock(data->pdev);
+ pci_save_state(data->pdev);
+ ret = btintel_acpi_reset_method(hdev);
+ if (ret)
+ bt_dev_err(data->hdev, "ACPI _PRR reset failed (%d), PLDR incomplete",
+ ret);
+ pci_restore_state(data->pdev);
+ pci_dev_unlock(data->pdev);
+ return ret;
+}
+
+static void btintel_pcie_perform_pldr(struct btintel_pcie_data *data)
+{
+ struct pci_dev *pdev = data->pdev;
+ struct pci_dev *wifi = NULL;
+ struct pci_bus *bus;
+ int ret;
+ /* on integrated we have to look up by ID (same bus) */
+ static const struct pci_device_id wifi_device_ids[] = {
+ #define WIFI_DEV(_id) { PCI_DEVICE(PCI_VENDOR_ID_INTEL, _id) }
+ WIFI_DEV(0xA840), /* LNL */
+ WIFI_DEV(0xE440), /* PTL-P */
+ WIFI_DEV(0xE340), /* PTL-H */
+ WIFI_DEV(0xD340), /* NVL-H */
+ WIFI_DEV(0x6E70), /* NVL-S */
+ WIFI_DEV(0x4D40), /* WCL */
+ {}
+ };
+ struct pci_dev *tmp = NULL;
+
+ bus = pdev->bus;
+ if (!bus)
+ return;
+
+ list_for_each_entry(tmp, &bus->devices, bus_list) {
+ if (pci_match_id(wifi_device_ids, tmp)) {
+ wifi = pci_dev_get(tmp);
+ break;
+ }
+ }
+
+ if (wifi)
+ device_release_driver(&wifi->dev);
+
+ /* Wi-Fi is fully unbound before the reset and fully reprobed after
+ * the normal PCI probe path handles all state setup from scratch.
+ * BT needs pci_save_state()/pci_restore_state() because the BT driver
+ * is still partially attached when the _PRR runs (it hasn't been unbound yet).
+ * The PCI device needs to remain minimally functional so that
+ * device_reprobe(&pdev->dev) can work afterward
+ */
+ ret = btintel_pcie_acpi_reset_method(data);
+
+ if (wifi) {
+ if (device_reprobe(&wifi->dev))
+ BT_ERR("WiFi reprobe failed for BDF:%s", pci_name(wifi));
+ pci_dev_put(wifi);
+ }
+
+ if (!ret) {
+ if (device_reprobe(&pdev->dev))
+ BT_ERR("BT reprobe failed for BDF:%s", pci_name(pdev));
+ }
+}
+
+/*
+ * Issue a Function Level Reset and hand teardown/re-init off to the PCI
+ * core via device_reprobe(), mirroring the PLDR path's contract.
+ *
+ * Caller must hold pci_lock_rescan_remove() and must have already
+ * disabled interrupts and drained both rx_work and coredump_work.
+ */
+static int btintel_pcie_perform_flr(struct btintel_pcie_data *data)
+{
+ struct pci_dev *pdev = data->pdev;
int err;
+ /* pci_try_reset_function() avoids the device_lock ABBA against
+ * btintel_pcie_remove(): .remove() runs with device_lock held and
+ * then waits for this work via disable_work_sync(); the blocking
+ * pci_reset_function() would deadlock by trying to re-acquire
+ * device_lock here.
+ */
+ err = pci_try_reset_function(pdev);
+ if (err) {
+ BT_ERR("Failed resetting the pcie device (%d)", err);
+ return err;
+ }
+
+ /* device_reprobe() always detaches the driver first (running
+ * .remove(), which frees 'data'); any re-probe failure leaves the
+ * device unbound but 'data' is already gone, so just log it.
+ */
+ if (device_reprobe(&pdev->dev))
+ BT_ERR("BT reprobe failed for BDF:%s", pci_name(pdev));
+
+ return 0;
+}
+
+static void btintel_pcie_reset_work(struct work_struct *wk)
+{
+ struct btintel_pcie_data *data =
+ container_of(wk, struct btintel_pcie_data, reset_work);
+ struct pci_dev *pdev = data->pdev;
+
pci_lock_rescan_remove();
if (!pdev->bus)
- goto error;
+ goto out;
- data = pci_get_drvdata(pdev);
+ if (!data)
+ goto out;
btintel_pcie_disable_interrupts(data);
btintel_pcie_synchronize_irqs(data);
flush_work(&data->rx_work);
+ /* Drain any in-flight coredump and block new ones across reset.
+ * Safe from self-deadlock: coredump_work runs on a separate wq.
+ */
+ disable_work_sync(&data->coredump_work);
bt_dev_dbg(data->hdev, "Release bluetooth interface");
- btintel_pcie_release_hdev(data);
-
- err = pci_reset_function(pdev);
- if (err) {
- BT_ERR("Failed resetting the pcie device (%d)", err);
- goto error;
- }
-
- btintel_pcie_enable_interrupts(data);
- btintel_pcie_config_msix(data);
- err = btintel_pcie_enable_bt(data);
- if (err) {
- BT_ERR("Failed to enable bluetooth hardware after reset (%d)",
- err);
- goto error;
+ /* Both reset paths follow the same contract: on success they
+ * destroy 'data' via device_reprobe() (a fresh probe re-INIT_WORKs
+ * the coredump_work with disable count 0), so enable_work() must
+ * NOT be called on the success path. Only the FLR path can fail
+ * with 'data' still alive, in which case we balance the
+ * disable_work_sync() above so a later successful reset is not
+ * permanently blocked.
+ *
+ * pci_lock_rescan_remove() (held above) serializes against PCI
+ * device addition/removal (hotplug), so no device can be added to
+ * or removed from the bus list while this code runs.
+ */
+ if (data->reset_type == BTINTEL_PCIE_IOSF_PRR_PLDR) {
+ btintel_pcie_perform_pldr(data);
+ goto out;
}
- btintel_pcie_reset_ia(data);
- btintel_pcie_start_rx(data);
- data->flags = 0;
+ if (btintel_pcie_perform_flr(data))
+ enable_work(&data->coredump_work);
- err = btintel_pcie_setup_hdev(data);
- if (err) {
- BT_ERR("Failed registering hdev (%d)", err);
- goto error;
- }
-error:
+out:
pci_dev_put(pdev);
pci_unlock_rescan_remove();
- kfree(removal);
}
static void btintel_pcie_reset(struct hci_dev *hdev)
{
- struct btintel_pcie_removal *removal;
struct btintel_pcie_data *data;
data = hci_get_drvdata(hdev);
@@ -2333,14 +2681,8 @@ static void btintel_pcie_reset(struct hci_dev *hdev)
if (test_and_set_bit(BTINTEL_PCIE_RECOVERY_IN_PROGRESS, &data->flags))
return;
- removal = kzalloc_obj(*removal, GFP_ATOMIC);
- if (!removal)
- return;
-
- removal->pdev = data->pdev;
- INIT_WORK(&removal->work, btintel_pcie_removal_work);
- pci_dev_get(removal->pdev);
- schedule_work(&removal->work);
+ pci_dev_get(data->pdev);
+ schedule_work(&data->reset_work);
}
static void btintel_pcie_hw_error(struct hci_dev *hdev, u8 code)
@@ -2350,15 +2692,19 @@ static void btintel_pcie_hw_error(struct hci_dev *hdev, u8 code)
struct pci_dev *pdev = dev_data->pdev;
time64_t retry_window;
- if (code == 0x13) {
- bt_dev_err(hdev, "Encountered top exception");
- return;
- }
+ btintel_pcie_dump_debug_registers(hdev);
data = btintel_pcie_get_recovery(pdev, &hdev->dev);
if (!data)
return;
+ if (code == 0x13)
+ dev_data->reset_type = BTINTEL_PCIE_IOSF_PRR_PLDR;
+ else
+ dev_data->reset_type = BTINTEL_PCIE_IOSF_PRR_FLR;
+
+ bt_dev_err(hdev, "Encountered exception err:0x%x triggering: %s", code,
+ dev_data->reset_type == BTINTEL_PCIE_IOSF_PRR_PLDR ? "PLDR" : "FLR");
retry_window = ktime_get_boottime_seconds() - data->last_error;
if (retry_window < BTINTEL_PCIE_RESET_WINDOW_SECS &&
@@ -2451,7 +2797,6 @@ static int btintel_pcie_setup_hdev(struct btintel_pcie_data *data)
hdev->bus = HCI_PCI;
hci_set_drvdata(hdev, data);
- data->hdev = hdev;
SET_HCIDEV_DEV(hdev, &data->pdev->dev);
hdev->manufacturer = 2;
@@ -2470,15 +2815,17 @@ static int btintel_pcie_setup_hdev(struct btintel_pcie_data *data)
err = hci_register_dev(hdev);
if (err < 0) {
BT_ERR("Failed to register to hdev (%d)", err);
- goto exit_error;
+ hci_free_dev(hdev);
+ return err;
}
+ /* Publish hdev only after successful registration; the coredump
+ * worker bails on !data->hdev, so it never observes a half-set-up
+ * device.
+ */
+ data->hdev = hdev;
data->dmp_hdr.driver_name = KBUILD_MODNAME;
return 0;
-
-exit_error:
- hci_free_dev(hdev);
- return err;
}
static int btintel_pcie_probe(struct pci_dev *pdev,
@@ -2509,12 +2856,23 @@ static int btintel_pcie_probe(struct pci_dev *pdev,
if (!data->workqueue)
return -ENOMEM;
+ data->coredump_workqueue = alloc_ordered_workqueue(KBUILD_MODNAME "_cd", 0);
+ if (!data->coredump_workqueue) {
+ destroy_workqueue(data->workqueue);
+ return -ENOMEM;
+ }
+
skb_queue_head_init(&data->rx_skb_q);
INIT_WORK(&data->rx_work, btintel_pcie_rx_work);
+ INIT_WORK(&data->reset_work, btintel_pcie_reset_work);
+ INIT_WORK(&data->coredump_work, btintel_pcie_coredump_worker);
data->boot_stage_cache = 0x00;
data->img_resp_cache = 0x00;
-
+ /* FLR can be invoked by echoing to debugfs path, so explicitly
+ * initialized
+ */
+ data->reset_type = BTINTEL_PCIE_IOSF_PRR_FLR;
err = btintel_pcie_config_pcie(pdev, data);
if (err)
goto exit_error;
@@ -2550,6 +2908,8 @@ exit_error:
/* reset device before exit */
btintel_pcie_reset_bt(data);
+ destroy_workqueue(data->coredump_workqueue);
+
pci_clear_master(pdev);
pci_set_drvdata(pdev, NULL);
@@ -2562,6 +2922,25 @@ static void btintel_pcie_remove(struct pci_dev *pdev)
struct btintel_pcie_data *data;
data = pci_get_drvdata(pdev);
+ if (!data) {
+ BT_WARN("PCI driver data is NULL, aborting remove");
+ return;
+ }
+
+ /* Permanently block coredump triggers and drain the worker before
+ * tearing down. Must run before cancel_work_sync(&reset_work) so
+ * the disable counter stays >= 1 even after reset_work()'s
+ * balanced enable_work() (counter 2 -> 1, never reaching 0).
+ */
+ disable_work_sync(&data->coredump_work);
+
+ /* Cancel pending reset work. Skip only when remove() is called from
+ * within the reset work itself (PLDR device_reprobe path) to avoid
+ * deadlock. current_work() returns the work_struct of the caller if
+ * we are in a workqueue context.
+ */
+ if (current_work() != &data->reset_work)
+ disable_work_sync(&data->reset_work);
btintel_pcie_disable_interrupts(data);
@@ -2581,6 +2960,7 @@ static void btintel_pcie_remove(struct pci_dev *pdev)
btintel_pcie_release_hdev(data);
+ destroy_workqueue(data->coredump_workqueue);
destroy_workqueue(data->workqueue);
btintel_pcie_free(data);
@@ -2596,11 +2976,19 @@ static void btintel_pcie_coredump(struct device *dev)
struct pci_dev *pdev = to_pci_dev(dev);
struct btintel_pcie_data *data = pci_get_drvdata(pdev);
+ if (!data)
+ return;
+
if (test_and_set_bit(BTINTEL_PCIE_COREDUMP_INPROGRESS, &data->flags))
return;
data->dmp_hdr.trigger_reason = BTINTEL_PCIE_TRIGGER_REASON_USER_TRIGGER;
- queue_work(data->workqueue, &data->rx_work);
+ /* queue_work() returns false if the work is disabled (reset or
+ * remove in progress); clear the in-progress bit so a later
+ * trigger can succeed once the work is re-enabled.
+ */
+ if (!queue_work(data->coredump_workqueue, &data->coredump_work))
+ clear_bit(BTINTEL_PCIE_COREDUMP_INPROGRESS, &data->flags);
}
#endif
@@ -2712,6 +3100,7 @@ static int btintel_pcie_resume(struct device *dev)
if (data->pm_sx_event == PM_EVENT_FREEZE ||
data->pm_sx_event == PM_EVENT_HIBERNATE) {
set_bit(BTINTEL_PCIE_CORE_HALTED, &data->flags);
+ data->reset_type = BTINTEL_PCIE_IOSF_PRR_FLR;
btintel_pcie_reset(data->hdev);
return 0;
}
@@ -2740,7 +3129,7 @@ static int btintel_pcie_resume(struct device *dev)
&data->flags)) {
data->dmp_hdr.trigger_reason =
BTINTEL_PCIE_TRIGGER_REASON_FW_ASSERT;
- queue_work(data->workqueue, &data->rx_work);
+ queue_work(data->coredump_workqueue, &data->coredump_work);
}
set_bit(BTINTEL_PCIE_CORE_HALTED, &data->flags);
btintel_pcie_reset(data->hdev);
diff --git a/drivers/bluetooth/btintel_pcie.h b/drivers/bluetooth/btintel_pcie.h
index 13efef499e4e..7caee093e316 100644
--- a/drivers/bluetooth/btintel_pcie.h
+++ b/drivers/bluetooth/btintel_pcie.h
@@ -68,6 +68,10 @@
#define BTINTEL_PCIE_DBGC_CUR_DBGBUFF_STATUS (BTINTEL_PCIE_DBGC_BASE_ADDR + 0x1C)
#define BTINTEL_PCIE_DBGC_DBGBUFF_WRAP_ARND (BTINTEL_PCIE_DBGC_BASE_ADDR + 0x2C)
+#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_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
@@ -98,6 +102,7 @@ enum msix_hw_int_causes {
BTINTEL_PCIE_MSIX_HW_INT_CAUSES_GP0 = BIT(0), /* cause 32 */
BTINTEL_PCIE_MSIX_HW_INT_CAUSES_GP1 = BIT(1), /* cause 33 */
BTINTEL_PCIE_MSIX_HW_INT_CAUSES_HWEXP = BIT(3), /* cause 35 */
+ BTINTEL_PCIE_MSIX_HW_INT_CAUSES_FWTRIG = BIT(5), /* cause 37 */
};
/* PCIe device states
@@ -115,6 +120,7 @@ enum {
BTINTEL_PCIE_CORE_HALTED,
BTINTEL_PCIE_HWEXP_INPROGRESS,
BTINTEL_PCIE_COREDUMP_INPROGRESS,
+ BTINTEL_PCIE_FWTRIGGER_DUMP_INPROGRESS,
BTINTEL_PCIE_RECOVERY_IN_PROGRESS,
BTINTEL_PCIE_SETUP_DONE
};
@@ -130,7 +136,9 @@ enum btintel_pcie_tlv_type {
BTINTEL_DUMP_TIME,
BTINTEL_FW_BUILD,
BTINTEL_VENDOR,
- BTINTEL_DRIVER
+ BTINTEL_DRIVER,
+ BTINTEL_EVENT_TYPE,
+ BTINTEL_EVENT_ID
};
/* causes for the MBOX interrupts */
@@ -141,6 +149,11 @@ enum msix_mbox_int_causes {
BTINTEL_PCIE_CSR_MBOX_STATUS_MBOX4 = BIT(3), /* cause MBOX4 */
};
+enum btintel_pcie_reset_type {
+ BTINTEL_PCIE_IOSF_PRR_FLR = 0,
+ BTINTEL_PCIE_IOSF_PRR_PLDR = 1,
+};
+
#define BTINTEL_PCIE_MSIX_NON_AUTO_CLEAR_CAUSE BIT(7)
/* Minimum and Maximum number of MSI-X Vector
@@ -425,6 +438,8 @@ struct btintel_pcie_dump_header {
u32 wrap_ctr;
u16 trigger_reason;
int state;
+ u8 event_type;
+ u16 event_id;
};
/* struct btintel_pcie_data
@@ -451,6 +466,8 @@ struct btintel_pcie_dump_header {
* @workqueue: workqueue for RX work
* @rx_skb_q: SKB queue for RX packet
* @rx_work: RX work struct to process the RX packet in @rx_skb_q
+ * @coredump_workqueue: dedicated workqueue for coredump collection
+ * @coredump_work: work struct for coredump trace collection
* @dma_pool: DMA pool for descriptors, index array and ci
* @dma_p_addr: DMA address for pool
* @dma_v_addr: address of pool
@@ -497,6 +514,10 @@ struct btintel_pcie_data {
struct workqueue_struct *workqueue;
struct sk_buff_head rx_skb_q;
struct work_struct rx_work;
+ struct work_struct reset_work;
+
+ struct workqueue_struct *coredump_workqueue;
+ struct work_struct coredump_work;
struct dma_pool *dma_pool;
dma_addr_t dma_p_addr;
@@ -508,9 +529,12 @@ struct btintel_pcie_data {
struct txq txq;
struct rxq rxq;
u32 alive_intr_ctxt;
+ enum btintel_pcie_reset_type reset_type;
struct btintel_pcie_dbgc dbgc;
struct btintel_pcie_dump_header dmp_hdr;
u8 pm_sx_event;
+ u32 debug_evt_addr;
+ u32 debug_evt_size;
};
static inline u32 btintel_pcie_rd_reg32(struct btintel_pcie_data *data,
diff --git a/drivers/bluetooth/btmtk.c b/drivers/bluetooth/btmtk.c
index 8ff66b276af0..c29e1841b2b3 100644
--- a/drivers/bluetooth/btmtk.c
+++ b/drivers/bluetooth/btmtk.c
@@ -1074,8 +1074,10 @@ struct urb *alloc_mtk_intr_urb(struct hci_dev *hdev, struct sk_buff *skb,
if (!urb)
return ERR_PTR(-ENOMEM);
- if (btmtk_isopkt_pad(hdev, skb))
+ if (btmtk_isopkt_pad(hdev, skb)) {
+ usb_free_urb(urb);
return ERR_PTR(-EINVAL);
+ }
pipe = usb_sndintpipe(btmtk_data->udev,
btmtk_data->isopkt_tx_ep->bEndpointAddress);
diff --git a/drivers/bluetooth/btrtl.c b/drivers/bluetooth/btrtl.c
index 62f9d4df3a4f..03fa9409e3ee 100644
--- a/drivers/bluetooth/btrtl.c
+++ b/drivers/bluetooth/btrtl.c
@@ -797,8 +797,9 @@ static int rtlbt_parse_firmware(struct hci_dev *hdev,
}
BT_DBG("length=%x offset=%x index %d", patch_length, patch_offset, i);
- min_size = patch_offset + patch_length;
- if (btrtl_dev->fw_len < min_size)
+ if (patch_length < sizeof(epatch_info->fw_version) ||
+ patch_offset > btrtl_dev->fw_len ||
+ patch_length > btrtl_dev->fw_len - patch_offset)
return -EINVAL;
/* Copy the firmware into a new buffer and write the version at
diff --git a/drivers/bluetooth/hci_qca.c b/drivers/bluetooth/hci_qca.c
index 34500137df2c..244447195619 100644
--- a/drivers/bluetooth/hci_qca.c
+++ b/drivers/bluetooth/hci_qca.c
@@ -1028,7 +1028,7 @@ static void qca_dmp_hdr(struct hci_dev *hdev, struct sk_buff *skb)
skb_put_data(skb, buf, strlen(buf));
snprintf(buf, sizeof(buf), "Driver: %s\n",
- hu->serdev->dev.driver->name);
+ hu->serdev ? hu->serdev->dev.driver->name : "hci_ldisc_qca");
skb_put_data(skb, buf, strlen(buf));
}
@@ -1916,9 +1916,12 @@ static int qca_setup(struct hci_uart *hu)
const char *rampatch_name = qca_get_rampatch_name(hu);
int ret;
struct qca_btsoc_version ver;
- struct qca_serdev *qcadev = serdev_device_get_drvdata(hu->serdev);
+ struct qca_serdev *qcadev = NULL;
const char *soc_name;
+ if (hu->serdev)
+ qcadev = serdev_device_get_drvdata(hu->serdev);
+
ret = qca_check_speeds(hu);
if (ret)
return ret;
@@ -1980,7 +1983,7 @@ retry:
case QCA_WCN6750:
case QCA_WCN6855:
case QCA_WCN7850:
- if (qcadev->bdaddr_property_broken)
+ if (qcadev && qcadev->bdaddr_property_broken)
hci_set_quirk(hdev, HCI_QUIRK_BDADDR_PROPERTY_BROKEN);
hci_set_aosp_capable(hdev);
@@ -2073,7 +2076,7 @@ out:
else
hu->hdev->set_bdaddr = qca_set_bdaddr;
- if (qcadev->support_hfp_hw_offload)
+ if (qcadev && qcadev->support_hfp_hw_offload)
qca_configure_hfp_offload(hdev);
qca->fw_version = le16_to_cpu(ver.patch_ver);
diff --git a/drivers/bluetooth/hci_vhci.c b/drivers/bluetooth/hci_vhci.c
index 2762eacf7f20..eddb32b30539 100644
--- a/drivers/bluetooth/hci_vhci.c
+++ b/drivers/bluetooth/hci_vhci.c
@@ -337,7 +337,17 @@ static ssize_t force_devcd_write(struct file *file, const char __user *user_buf,
if (copy_from_user(&dump_data, user_buf, count))
return -EFAULT;
+ switch (dump_data.state) {
+ case HCI_DEVCOREDUMP_DONE:
+ case HCI_DEVCOREDUMP_ABORT:
+ case HCI_DEVCOREDUMP_TIMEOUT:
+ break;
+ default:
+ return -EINVAL;
+ }
+
data_size = count - offsetof(struct devcoredump_test_data, data);
+
skb = alloc_skb(data_size, GFP_ATOMIC);
if (!skb)
return -ENOMEM;