diff options
Diffstat (limited to 'drivers/bluetooth')
| -rw-r--r-- | drivers/bluetooth/Kconfig | 2 | ||||
| -rw-r--r-- | drivers/bluetooth/bpa10x.c | 8 | ||||
| -rw-r--r-- | drivers/bluetooth/btintel.c | 10 | ||||
| -rw-r--r-- | drivers/bluetooth/btintel.h | 9 | ||||
| -rw-r--r-- | drivers/bluetooth/btintel_pcie.c | 531 | ||||
| -rw-r--r-- | drivers/bluetooth/btintel_pcie.h | 26 | ||||
| -rw-r--r-- | drivers/bluetooth/btmtk.c | 4 | ||||
| -rw-r--r-- | drivers/bluetooth/btrtl.c | 5 | ||||
| -rw-r--r-- | drivers/bluetooth/hci_qca.c | 11 | ||||
| -rw-r--r-- | drivers/bluetooth/hci_vhci.c | 10 |
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; |
