diff options
| author | Mark Brown <broonie@kernel.org> | 2026-09-07 13:26:23 +0100 |
|---|---|---|
| committer | Mark Brown <broonie@kernel.org> | 2026-09-07 13:26:23 +0100 |
| commit | 7e615d9c216c1c9c15c8894e3ab507cf4caa7013 (patch) | |
| tree | d8019a69e1e209110d5618d98d486355ce660757 | |
| parent | 01775b83ba0c2f397024723a1a49490147bc7200 (diff) | |
| parent | 755cf7adf8dd2d12627cb7de223d35b12228e2f5 (diff) | |
| download | linux-next-7e615d9c216c1c9c15c8894e3ab507cf4caa7013.tar.gz linux-next-7e615d9c216c1c9c15c8894e3ab507cf4caa7013.zip | |
Merge branch 'master' of https://git.kernel.org/pub/scm/linux/kernel/git/bluetooth/bluetooth-next.git
# Conflicts:
# net/bluetooth/hci_sync.c
# net/bluetooth/l2cap_core.c
32 files changed, 2603 insertions, 605 deletions
diff --git a/Documentation/devicetree/bindings/net/bluetooth/brcm,bluetooth.yaml b/Documentation/devicetree/bindings/net/bluetooth/brcm,bluetooth.yaml index 95501e858e6f..612d21123aaa 100644 --- a/Documentation/devicetree/bindings/net/bluetooth/brcm,bluetooth.yaml +++ b/Documentation/devicetree/bindings/net/bluetooth/brcm,bluetooth.yaml @@ -31,6 +31,7 @@ properties: - brcm,bcm43540-bt - brcm,bcm4335a0 - brcm,bcm4349-bt + - brcm,bcm4384-bt - cypress,cyw4373a0-bt - infineon,cyw55572-bt diff --git a/drivers/bluetooth/btbcm.c b/drivers/bluetooth/btbcm.c index 463d59890bef..63c4c788d9c4 100644 --- a/drivers/bluetooth/btbcm.c +++ b/drivers/bluetooth/btbcm.c @@ -31,6 +31,7 @@ #define BDADDR_BCM4334B0 (&(bdaddr_t) {{0x00, 0x00, 0x00, 0xb0, 0x34, 0x43}}) #define BDADDR_BCM4345C5 (&(bdaddr_t) {{0xac, 0x1f, 0x00, 0xc5, 0x45, 0x43}}) #define BDADDR_BCM43341B (&(bdaddr_t) {{0xac, 0x1f, 0x00, 0x1b, 0x34, 0x43}}) +#define BDADDR_BCM4384B0 (&(bdaddr_t) {{0x93, 0x76, 0x00, 0xb0, 0x84, 0x43}}) #define BCM_FW_NAME_LEN 64 #define BCM_FW_NAME_COUNT_MAX 4 @@ -130,7 +131,8 @@ int btbcm_check_bdaddr(struct hci_dev *hdev) !bacmp(&bda->bdaddr, BDADDR_BCM4345C5) || !bacmp(&bda->bdaddr, BDADDR_BCM43430A0) || !bacmp(&bda->bdaddr, BDADDR_BCM43430A1) || - !bacmp(&bda->bdaddr, BDADDR_BCM43341B)) { + !bacmp(&bda->bdaddr, BDADDR_BCM43341B) || + !bacmp(&bda->bdaddr, BDADDR_BCM4384B0)) { /* Try falling back to BDADDR EFI variable */ if (btbcm_set_bdaddr_from_efi(hdev) != 0) { bt_dev_info(hdev, "BCM: Using default device address (%pMR)", @@ -514,6 +516,8 @@ static const struct bcm_subver_table bcm_uart_subver_table[] = { { 0x4106, "BCM4335A0" }, /* 002.001.006 */ { 0x410c, "BCM43430B0" }, /* 002.001.012 */ { 0x2119, "BCM4373A0" }, /* 001.001.025 */ + { 0x2128, "BCM4384A0" }, /* 001.001.040 */ + { 0x4119, "BCM4384B0" }, /* 002.001.025 */ { } }; diff --git a/drivers/bluetooth/btintel.c b/drivers/bluetooth/btintel.c index 909a265fd906..964d2de30e65 100644 --- a/drivers/bluetooth/btintel.c +++ b/drivers/bluetooth/btintel.c @@ -66,6 +66,7 @@ static struct { const char *driver_name; u8 hw_variant; u32 fw_build_num; + u32 fw_sha; } coredump_info; const guid_t btintel_guid_dsm = @@ -560,6 +561,7 @@ int btintel_version_info_tlv(struct hci_dev *hdev, coredump_info.hw_variant = INTEL_HW_VARIANT(version->cnvi_bt); coredump_info.fw_build_num = version->build_num; + coredump_info.fw_sha = version->git_sha1; bt_dev_info(hdev, "%s timestamp %u.%u buildtype %u build %u", variant, 2000 + (version->timestamp >> 8), version->timestamp & 0xff, @@ -2373,6 +2375,7 @@ static int btintel_prepare_fw_download_tlv(struct hci_dev *hdev, struct intel_version_tlv *ver, u32 *boot_param) { + struct btintel_data *intel_data = hci_get_priv(hdev); const struct firmware *fw; char fwname[128]; int err; @@ -2485,6 +2488,7 @@ static int btintel_prepare_fw_download_tlv(struct hci_dev *hdev, btintel_reset_to_bootloader(hdev); done: + intel_data->cnvi_bt = ver->cnvi_bt; release_firmware(fw); return err; } @@ -3522,6 +3526,9 @@ int btintel_bootloader_setup_tlv(struct hci_dev *hdev, btintel_version_info_tlv(hdev, &new_ver); + /* Update ver with the operational firmware version */ + *ver = new_ver; + finish: /* Set the event mask for Intel specific vendor events. This enables * a few extra events that are useful during general operation. It diff --git a/drivers/bluetooth/btintel.h b/drivers/bluetooth/btintel.h index 966ec1b02be2..c00f793d9dc8 100644 --- a/drivers/bluetooth/btintel.h +++ b/drivers/bluetooth/btintel.h @@ -225,7 +225,9 @@ struct btintel_sar_rev2 { #define INTEL_HW_PLATFORM(cnvx_bt) ((u8)(((cnvx_bt) & 0x0000ff00) >> 8)) #define INTEL_HW_VARIANT(cnvx_bt) ((u8)(((cnvx_bt) & 0x003f0000) >> 16)) #define INTEL_CNVX_TOP_TYPE(cnvx_top) ((cnvx_top) & 0x00000fff) +#define INTEL_CNVX_TOP_DASH(cnvx_top) (((cnvx_top) & 0x00f00000) >> 20) #define INTEL_CNVX_TOP_STEP(cnvx_top) (((cnvx_top) & 0x0f000000) >> 24) +#define INTEL_CNVX_TOP_FLAVOR(cnvx_top) (((cnvx_top) & 0xf0000000) >> 28) #define INTEL_CNVX_TOP_PACK_SWAB(t, s) __swab16(((__u16)(((t) << 4) | (s)))) enum { @@ -247,6 +249,7 @@ enum { struct btintel_data { DECLARE_BITMAP(flags, __INTEL_NUM_FLAGS); int (*acpi_reset_method)(struct hci_dev *hdev); + u32 cnvi_bt; }; #define btintel_set_flag(hdev, nr) \ diff --git a/drivers/bluetooth/btintel_pcie.c b/drivers/bluetooth/btintel_pcie.c index eec95e5f3dbb..04f5b0273977 100644 --- a/drivers/bluetooth/btintel_pcie.c +++ b/drivers/bluetooth/btintel_pcie.c @@ -19,6 +19,7 @@ #include <linux/unaligned.h> #include <linux/devcoredump.h> +#include <linux/scatterlist.h> #include <net/bluetooth/bluetooth.h> #include <net/bluetooth/hci_core.h> @@ -73,17 +74,6 @@ struct btintel_pcie_dev_recovery { #define BTINTEL_PCIE_HCI_EVT_PKT 0x00000004 #define BTINTEL_PCIE_HCI_ISO_PKT 0x00000005 -#define BTINTEL_PCIE_MAGIC_NUM 0xA5A5A5A5 - -#define BTINTEL_PCIE_BLZR_HWEXP_SIZE 1024 -#define BTINTEL_PCIE_BLZR_HWEXP_DMP_ADDR 0xB00A7C00 - -#define BTINTEL_PCIE_SCP_HWEXP_SIZE 4096 -#define BTINTEL_PCIE_SCP_HWEXP_DMP_ADDR 0xB030F800 - -#define BTINTEL_PCIE_SCP2_HWEXP_SIZE 4096 -#define BTINTEL_PCIE_SCP2_HWEXP_DMP_ADDR 0xB031D000 - #define BTINTEL_PCIE_MAGIC_NUM 0xA5A5A5A5 #define BTINTEL_PCIE_TRIGGER_REASON_USER_TRIGGER 0x17A2 @@ -145,6 +135,20 @@ struct btintel_pcie_dbgc_ctxt { struct btintel_pcie_dbgc_ctxt_buf bufs[BTINTEL_PCIE_DBGC_BUFFER_COUNT]; }; +struct btintel_pcie_mdbgc_ctxt { + u32 magic_num; + u32 ver; + u32 buf1_index; + u32 buf1_count; + struct btintel_pcie_dbgc_ctxt_buf buf1[BTINTEL_PCIE_DBGC_BUFFER_COUNT]; + u32 buf2_index; + u32 buf2_count; + struct btintel_pcie_dbgc_ctxt_buf buf2[BTINTEL_PCIE_DBGC_BUFFER_COUNT]; + u32 buf3_index; + u32 buf3_count; + struct btintel_pcie_dbgc_ctxt_buf buf3[BTINTEL_PCIE_DBGC_BUFFER_COUNT]; +}; + struct btintel_pcie_trigger_evt { u8 type; u8 len; @@ -186,6 +190,138 @@ static inline char *btintel_pcie_alivectxt_state2str(u32 alive_intr_ctxt) } } +/* Returns true when firmware traces are routed to the WiFi DBGC. In that + * mode the host must not allocate DBGC buffers and must not publish their + * addresses in the context info. + */ +static inline bool btintel_pcie_dbg_to_wifi(struct btintel_pcie_data *data) +{ + return data->dbg_path_cache != BTINTEL_PCIE_DRAM; +} + +/* Helper function to allocate and setup a debug buffer group + * @data: driver data structure + * @buf: pointer to data_buf array pointer + * @p_addr: pointer to physical DMA address + * @v_addr: pointer to virtual address + * @frag: pointer to fragment buffer array + * @buf_index: buffer index (for error messages) + * @buf_count: number of buffers to allocate + */ +static int btintel_pcie_alloc_dbgc_buf(struct btintel_pcie_data *data, + struct data_buf **buf, + dma_addr_t *p_addr, + void **v_addr, + struct btintel_pcie_dbgc_ctxt_buf *frag, + u32 buf_index, + u32 buf_count) +{ + struct data_buf *b; + int i; + + *buf = devm_kcalloc(&data->pdev->dev, buf_count, + sizeof(**buf), GFP_KERNEL); + if (!*buf) { + BT_ERR("Failed to allocate dbgc buf: %u", buf_index + 1); + return -ENOMEM; + } + + *v_addr = dmam_alloc_coherent(&data->pdev->dev, + buf_count * + BTINTEL_PCIE_DBGC_BUFFER_SIZE, + p_addr, + GFP_KERNEL | __GFP_NOWARN); + if (!*v_addr) { + BT_ERR("Failed to allocate dbgc buf: %u DMA", buf_index + 1); + return -ENOMEM; + } + + for (i = 0; i < buf_count; i++) { + b = &(*buf)[i]; + b->data_p_addr = *p_addr + i * BTINTEL_PCIE_DBGC_BUFFER_SIZE; + b->data = *v_addr + i * BTINTEL_PCIE_DBGC_BUFFER_SIZE; + frag[i].buf_addr_lsb = lower_32_bits(b->data_p_addr); + frag[i].buf_addr_msb = upper_32_bits(b->data_p_addr); + frag[i].buf_size = BTINTEL_PCIE_DBGC_BUFFER_SIZE; + } + + return 0; +} + +/* This function initializes the memory for MDBGC buffers and formats the + * DBGC fragment which consists header info and DBGC buffer's LSB, MSB and + * size as the payload + */ +static int btintel_pcie_setup_mdbgc(struct btintel_pcie_data *data) +{ + struct btintel_pcie_mdbgc_ctxt db_frag; + u32 frag_size = sizeof(db_frag); + void *frag_v_addr; + int err; + + data->mdbgc.count = BTINTEL_PCIE_DBGC_BUFFER_COUNT; + + /* Allocate fragment context structure */ + frag_v_addr = dmam_alloc_coherent(&data->pdev->dev, frag_size, + &data->mdbgc.frag_p_addr, + GFP_KERNEL | __GFP_NOWARN); + if (!frag_v_addr) { + BT_ERR("Failed to allocate mdbgc context"); + return -ENOMEM; + } + + data->mdbgc.frag_v_addr = frag_v_addr; + data->mdbgc.frag_size = frag_size; + + /* Initialize fragment header */ + memset(&db_frag, 0, sizeof(db_frag)); + db_frag.magic_num = BTINTEL_PCIE_MAGIC_NUM; + db_frag.ver = BTINTEL_PCIE_MDBGC_FRAG_VERSION; + + /* Allocate DBGC buffer 1 */ + db_frag.buf1_index = BTINTEL_PCIE_MDBGC_ALLOCATIONID_1; + db_frag.buf1_count = data->mdbgc.count; + err = btintel_pcie_alloc_dbgc_buf(data, + &data->mdbgc.buf1, + &data->mdbgc.buf1_p_addr, + &data->mdbgc.buf1_v_addr, + db_frag.buf1, + 0, + data->mdbgc.count); + if (err) + return err; + + /* Allocate DBGC buffer 2 */ + db_frag.buf2_index = BTINTEL_PCIE_MDBGC_ALLOCATIONID_2; + db_frag.buf2_count = data->mdbgc.count; + err = btintel_pcie_alloc_dbgc_buf(data, + &data->mdbgc.buf2, + &data->mdbgc.buf2_p_addr, + &data->mdbgc.buf2_v_addr, + db_frag.buf2, + 1, + data->mdbgc.count); + if (err) + return err; + + /* Allocate DBGC buffer 3 */ + db_frag.buf3_index = BTINTEL_PCIE_MDBGC_ALLOCATIONID_3; + db_frag.buf3_count = data->mdbgc.count; + err = btintel_pcie_alloc_dbgc_buf(data, + &data->mdbgc.buf3, + &data->mdbgc.buf3_p_addr, + &data->mdbgc.buf3_v_addr, + db_frag.buf3, + 2, + data->mdbgc.count); + if (err) + return err; + + /* Copy fragment to DMA coherent memory */ + memcpy(data->mdbgc.frag_v_addr, &db_frag, sizeof(db_frag)); + return 0; +} + /* This function initializes the memory for DBGC buffers and formats the * DBGC fragment which consists header info and DBGC buffer's LSB, MSB and * size as the payload @@ -193,46 +329,42 @@ static inline char *btintel_pcie_alivectxt_state2str(u32 alive_intr_ctxt) static int btintel_pcie_setup_dbgc(struct btintel_pcie_data *data) { struct btintel_pcie_dbgc_ctxt db_frag; - struct data_buf *buf; - int i; + u32 frag_size = sizeof(db_frag); + int err; data->dbgc.count = BTINTEL_PCIE_DBGC_BUFFER_COUNT; - data->dbgc.bufs = devm_kcalloc(&data->pdev->dev, data->dbgc.count, - sizeof(*buf), GFP_KERNEL); - if (!data->dbgc.bufs) - return -ENOMEM; - - data->dbgc.buf_v_addr = dmam_alloc_coherent(&data->pdev->dev, - data->dbgc.count * - BTINTEL_PCIE_DBGC_BUFFER_SIZE, - &data->dbgc.buf_p_addr, - GFP_KERNEL | __GFP_NOWARN); - if (!data->dbgc.buf_v_addr) - return -ENOMEM; + /* Allocate fragment context structure */ data->dbgc.frag_v_addr = dmam_alloc_coherent(&data->pdev->dev, - sizeof(struct btintel_pcie_dbgc_ctxt), + frag_size, &data->dbgc.frag_p_addr, GFP_KERNEL | __GFP_NOWARN); - if (!data->dbgc.frag_v_addr) + if (!data->dbgc.frag_v_addr) { + BT_ERR("Failed to allocate dbgc context"); return -ENOMEM; + } data->dbgc.frag_size = sizeof(struct btintel_pcie_dbgc_ctxt); + /* Initialize fragment header */ + memset(&db_frag, 0, sizeof(db_frag)); db_frag.magic_num = BTINTEL_PCIE_MAGIC_NUM; db_frag.ver = BTINTEL_PCIE_DBGC_FRAG_VERSION; db_frag.total_size = BTINTEL_PCIE_DBGC_FRAG_PAYLOAD_SIZE; db_frag.num_buf = BTINTEL_PCIE_DBGC_FRAG_BUFFER_COUNT; - for (i = 0; i < data->dbgc.count; i++) { - buf = &data->dbgc.bufs[i]; - buf->data_p_addr = data->dbgc.buf_p_addr + i * BTINTEL_PCIE_DBGC_BUFFER_SIZE; - buf->data = data->dbgc.buf_v_addr + i * BTINTEL_PCIE_DBGC_BUFFER_SIZE; - db_frag.bufs[i].buf_addr_lsb = lower_32_bits(buf->data_p_addr); - db_frag.bufs[i].buf_addr_msb = upper_32_bits(buf->data_p_addr); - db_frag.bufs[i].buf_size = BTINTEL_PCIE_DBGC_BUFFER_SIZE; - } + /* Allocate DBGC buffers */ + err = btintel_pcie_alloc_dbgc_buf(data, + &data->dbgc.bufs, + &data->dbgc.buf_p_addr, + &data->dbgc.buf_v_addr, + db_frag.bufs, + 0, + data->dbgc.count); + if (err) + return err; + /* Copy fragment to DMA coherent memory */ memcpy(data->dbgc.frag_v_addr, &db_frag, sizeof(db_frag)); return 0; } @@ -639,169 +771,673 @@ static void btintel_pcie_release_mac_access(struct btintel_pcie_data *data) } } -static void *btintel_pcie_copy_tlv(void *dest, enum btintel_pcie_tlv_type type, - void *data, size_t size) +static struct scatterlist *btintel_pcie_alloc_sgtable(ssize_t size) { - struct intel_tlv *tlv; + int nents, i; + struct page *page; + struct scatterlist *sg, *result; - tlv = dest; - tlv->type = type; - tlv->len = size; - memcpy(tlv->val, data, tlv->len); - return dest + sizeof(*tlv) + size; + if (size <= 0) + return NULL; + + nents = DIV_ROUND_UP(size, PAGE_SIZE); + result = kcalloc(nents, sizeof(*result), GFP_KERNEL); + if (!result) + return NULL; + + sg_init_table(result, nents); + sg = result; + + for (i = 0; size > 0; i++) { + ssize_t bytes = min_t(ssize_t, size, PAGE_SIZE); + + page = alloc_page(GFP_KERNEL | __GFP_ZERO); + if (!page) + goto err_free; + + sg_set_page(sg, page, bytes, 0); + sg = sg_next(sg); + size -= bytes; + } + return result; + +err_free: + for (sg = result; sg; sg = sg_next(sg)) { + page = sg_page(sg); + if (page) + __free_page(page); + } + kfree(result); + return NULL; } -static int btintel_pcie_read_dram_buffers(struct btintel_pcie_data *data) +static struct btintel_pcie_dump_entry * +btintel_pcie_dump_entry_alloc(u32 data_size) { - u32 offset, prev_size, wr_ptr_status, dump_size, data_len; - u32 status_reg, wrap_reg; - struct btintel_pcie_dbgc *dbgc = &data->dbgc; + struct btintel_pcie_dump_entry *entry; + + entry = vzalloc(sizeof(*entry) + data_size); + if (!entry) + return NULL; + + entry->size = data_size; + INIT_LIST_HEAD(&entry->list); + return entry; +} + +static void btintel_pcie_dump_list_free(struct list_head *list) +{ + struct btintel_pcie_dump_entry *entry, *tmp; + + list_for_each_entry_safe(entry, tmp, list, list) { + list_del(&entry->list); + vfree(entry); + } +} + +static u32 btintel_pcie_dump_list_total_size(struct list_head *list) +{ + struct btintel_pcie_dump_entry *entry; + u32 total = 0; + + list_for_each_entry(entry, list, list) + total += entry->size; + + return total; +} + +static int btintel_pcie_dump_dram(struct list_head *list, + u8 count, struct data_buf *bufs, + u32 write_ptr, u32 wrap_ctr, u32 cur_frag, + u32 region_id, const char *name) +{ + struct btintel_pcie_dump_entry *entry; + struct btintel_pcie_ini_dump_data *dump_data; + struct btintel_pcie_ini_monitor_dump *mon_dump; + struct btintel_pcie_dump_range *range; + u32 mon_hdr_size, ranges_size, payload_size, total_size; + int i; + + mon_hdr_size = sizeof(*mon_dump); + ranges_size = count * (sizeof(*range) + BTINTEL_PCIE_DBGC_BUFFER_SIZE); + payload_size = mon_hdr_size + ranges_size; + total_size = sizeof(*dump_data) + payload_size; + + entry = btintel_pcie_dump_entry_alloc(total_size); + if (!entry) + return -ENOMEM; + + dump_data = (void *)entry->data; + dump_data->type = BTINTEL_PCIE_INI_REGION_DRAM_BUFFER; + dump_data->sub_type = 0; + dump_data->sub_type_ver = 0; + dump_data->reserved = 0; + dump_data->len = cpu_to_le32(payload_size); + + mon_dump = (void *)dump_data->data; + mon_dump->header.version = cpu_to_le32(BTINTEL_PCIE_INI_DUMP_VER); + mon_dump->header.region_id = cpu_to_le32(region_id); + mon_dump->header.num_of_ranges = cpu_to_le32(count); + mon_dump->header.name_len = cpu_to_le32(BTINTEL_PCIE_INI_MAX_NAME); + memset(mon_dump->header.name, 0, BTINTEL_PCIE_INI_MAX_NAME); + strscpy(mon_dump->header.name, name, BTINTEL_PCIE_INI_MAX_NAME); + + mon_dump->write_ptr = cpu_to_le32(write_ptr); + mon_dump->cycle_cnt = cpu_to_le32(wrap_ctr); + mon_dump->cur_frag = cpu_to_le32(cur_frag); + + range = (void *)mon_dump->data; + for (i = 0; i < count; i++) { + range->range_data_size = + cpu_to_le32(BTINTEL_PCIE_DBGC_BUFFER_SIZE); + range->dram_base_addr = cpu_to_le64(bufs[i].data_p_addr); + memcpy(range->data, bufs[i].data, + BTINTEL_PCIE_DBGC_BUFFER_SIZE); + range = (void *)range->data + BTINTEL_PCIE_DBGC_BUFFER_SIZE; + } + + list_add_tail(&entry->list, list); + return 0; +} + +static int +btintel_pcie_dump_dram_monitor(struct btintel_pcie_data *data, + struct list_head *list, u8 count, + struct data_buf *bufs, u32 status_reg, + u32 wrap_reg, u8 allocation_id, u32 region_id, + const char *name) +{ + u32 allocation_offset, write_ptr, wrap_ctr, wr_ptr_status; + u8 cur_frag; + + allocation_offset = allocation_id * + BTINTEL_PCIE_DBGC_ALLOCATION_OFFSET; + wr_ptr_status = btintel_pcie_rd_dev_mem(data, status_reg + + allocation_offset); + wrap_ctr = btintel_pcie_rd_dev_mem(data, wrap_reg + + allocation_offset); + + write_ptr = wr_ptr_status & BTINTEL_PCIE_DBG_OFFSET_BIT_MASK; + write_ptr >>= 2; + cur_frag = BTINTEL_PCIE_DBGC_DBG_BUF_IDX(wr_ptr_status); + if (cur_frag >= count) { + bt_dev_warn(data->hdev, + "Invalid DRAM monitor fragment %u for allocation %u", + cur_frag, allocation_id); + return -EINVAL; + } + + bt_dev_dbg(data->hdev, + "allocation=%u wr_ptr_status=0x%08x write_ptr=0x%06x cur_frag=%u wrap_ctr=0x%08x", + allocation_id, wr_ptr_status, write_ptr, cur_frag, wrap_ctr); + + return btintel_pcie_dump_dram(list, count, bufs, write_ptr, wrap_ctr, + cur_frag, region_id, name); +} + +static int btintel_pcie_dump_target_region(struct btintel_pcie_data *data, + struct list_head *list, + u32 region_id, const char *name, + u32 addr_start, u32 size) +{ + struct btintel_pcie_dump_entry *entry; + struct btintel_pcie_ini_dump_data *dump_data; + struct btintel_pcie_ini_dump_header *hdr; + struct btintel_pcie_dump_range *range; + u32 payload_size, total_size, target_mem_offset, tempdata; + u8 *dest; + int i; + + if (!size) { + bt_dev_warn(data->hdev, "Skipping empty dump region: %s", name); + return 1; + } + + if (!addr_start) { + bt_dev_warn(data->hdev, + "Skipping dump region with zero address: %s", + name); + return 1; + } + + if (size > BTINTEL_PCIE_REGION_MAX_SIZE) { + bt_dev_warn(data->hdev, + "Skipping dump region %s: size %u exceeds max %u", + name, size, BTINTEL_PCIE_REGION_MAX_SIZE); + return 1; + } + + if (addr_start > U32_MAX - size) { + bt_dev_warn(data->hdev, + "Skipping dump region %s: addr_start 0x%08x + size %u would overflow", + name, addr_start, size); + return 1; + } + + /* Align to 4 bytes - target access reads 32-bit words */ + size = round_down(size, sizeof(u32)); + if (!size) { + bt_dev_warn(data->hdev, + "Skipping dump region %s: size is smaller than 4-byte access", + name); + return 1; + } + + bt_dev_dbg(data->hdev, + "Target access: region=%s start=0x%08x size=%u", + name, addr_start, size); + + payload_size = sizeof(*hdr) + sizeof(*range) + size; + total_size = sizeof(*dump_data) + payload_size; + entry = btintel_pcie_dump_entry_alloc(total_size); + if (!entry) + return -ENOMEM; + + dump_data = (void *)entry->data; + dump_data->type = BTINTEL_PCIE_INI_REGION_DEVICE_MEMORY; + dump_data->sub_type = 0; + dump_data->sub_type_ver = 0; + dump_data->reserved = 0; + dump_data->len = cpu_to_le32(payload_size); + + hdr = (void *)dump_data->data; + hdr->version = cpu_to_le32(BTINTEL_PCIE_INI_DUMP_VER); + hdr->region_id = cpu_to_le32(region_id); + hdr->num_of_ranges = cpu_to_le32(1); + hdr->name_len = cpu_to_le32(BTINTEL_PCIE_INI_MAX_NAME); + memset(hdr->name, 0, BTINTEL_PCIE_INI_MAX_NAME); + strscpy(hdr->name, name, BTINTEL_PCIE_INI_MAX_NAME); + + range = (void *)(hdr + 1); + range->range_data_size = cpu_to_le32(size); + range->internal_base_addr = cpu_to_le32(addr_start); + + dest = (u8 *)range->data; + target_mem_offset = size / sizeof(u32); + for (i = 0; i < target_mem_offset; i++) { + u32 offset = BTINTEL_PCIE_TARGET_ACCESS_FRAG_OFFSET * i; + + tempdata = btintel_pcie_rd_dev_mem(data, + addr_start + offset); + memcpy(dest, &tempdata, sizeof(tempdata)); + dest += sizeof(tempdata); + } + + list_add_tail(&entry->list, list); + return 0; +} + +static int btintel_pcie_dump_smem_monitor_region(struct btintel_pcie_data *data, + struct list_head *list, + u32 region_id, + const char *name, + u32 addr_start, u32 size) +{ + struct btintel_pcie_dump_entry *entry; + struct btintel_pcie_ini_dump_data *dump_data; + struct btintel_pcie_ini_monitor_dump *mon; + struct btintel_pcie_dump_range *range; + u32 payload_size, total_size, target_mem_offset, tempdata; + u8 *dest; + int i; + + if (!size || !addr_start) { + bt_dev_err(data->hdev, "Skipping smem dump: size = %u addr = %8.8x", + size, addr_start); + return -EINVAL; + } + + payload_size = sizeof(*mon) + sizeof(*range) + size; + total_size = sizeof(*dump_data) + payload_size; + entry = btintel_pcie_dump_entry_alloc(total_size); + if (!entry) + return -ENOMEM; + + dump_data = (void *)entry->data; + dump_data->type = BTINTEL_PCIE_INI_REGION_INTERNAL_BUFFER; + dump_data->sub_type = 0; + dump_data->sub_type_ver = 0; + dump_data->reserved = 0; + dump_data->len = cpu_to_le32(payload_size); + + mon = (void *)dump_data->data; + mon->header.version = cpu_to_le32(BTINTEL_PCIE_INI_DUMP_VER); + mon->header.region_id = cpu_to_le32(region_id); + mon->header.num_of_ranges = cpu_to_le32(1); + mon->header.name_len = cpu_to_le32(BTINTEL_PCIE_INI_MAX_NAME); + memset(mon->header.name, 0, BTINTEL_PCIE_INI_MAX_NAME); + strscpy(mon->header.name, name, BTINTEL_PCIE_INI_MAX_NAME); + + mon->write_ptr = cpu_to_le32(0); + mon->cycle_cnt = cpu_to_le32(0); + mon->cur_frag = cpu_to_le32(0); + + range = (void *)mon->data; + range->range_data_size = cpu_to_le32(size); + range->internal_base_addr = cpu_to_le32(addr_start); + + dest = (u8 *)range->data; + target_mem_offset = size / sizeof(u32); + for (i = 0; i < target_mem_offset; i++) { + u32 offset = BTINTEL_PCIE_TARGET_ACCESS_FRAG_OFFSET * i; + + tempdata = btintel_pcie_rd_dev_mem(data, + addr_start + offset); + memcpy(dest, &tempdata, sizeof(tempdata)); + dest += sizeof(tempdata); + } + + list_add_tail(&entry->list, list); + return 0; +} + +static int btintel_pcie_dump_info(struct btintel_pcie_data *data, + struct list_head *list, + u64 regions_mask) +{ + struct btintel_pcie_dump_entry *entry; + struct btintel_pcie_error_dump_data *tlv; + struct btintel_pcie_ini_dump_info *dump; + u32 size = sizeof(*tlv) + sizeof(*dump); + char build_tag[64]; + + entry = btintel_pcie_dump_entry_alloc(size); + if (!entry) + return -ENOMEM; + + tlv = (void *)entry->data; + tlv->type = cpu_to_le32(BTINTEL_PCIE_INI_DUMP_INFO_TYPE); + tlv->len = cpu_to_le32(sizeof(*dump)); + + dump = (void *)tlv->data; + memset(dump, 0, sizeof(*dump)); + + dump->version = cpu_to_le32(BTINTEL_PCIE_INI_DUMP_VER); + dump->trigger_reason = cpu_to_le32(data->dmp_hdr.trigger_reason); + + if (data->dmp_hdr.trigger_reason == + BTINTEL_PCIE_TRIGGER_REASON_FW_ASSERT) + dump->time_point = + cpu_to_le32(BTINTEL_PCIE_TIME_POINT_FW_ASSERT); + else if (data->dmp_hdr.trigger_reason == + BTINTEL_PCIE_TRIGGER_REASON_USER_TRIGGER) + dump->time_point = + cpu_to_le32(BTINTEL_PCIE_TIME_POINT_USER_TRIGGER); + + dump->hw_type = cpu_to_le32(INTEL_CNVX_TOP_TYPE(data->dmp_hdr.cnvi_top)); + dump->hw_step = cpu_to_le32(INTEL_CNVX_TOP_STEP(data->dmp_hdr.cnvi_top)); + bt_dev_dbg(data->hdev, "hw_type=0x%x hw_step=0x%x (cnvi_top=0x%x)", + le32_to_cpu(dump->hw_type), le32_to_cpu(dump->hw_step), + data->dmp_hdr.cnvi_top); + + dump->ver_type = cpu_to_le32(data->dmp_hdr.cnvi_bt); + dump->ver_subtype = cpu_to_le32(data->dmp_hdr.fw_sha); + dump->rf_id_type = cpu_to_le32(INTEL_CNVX_TOP_TYPE(data->dmp_hdr.cnvr_top)); + dump->rf_id_dash = cpu_to_le32(INTEL_CNVX_TOP_DASH(data->dmp_hdr.cnvr_top)); + dump->rf_id_step = cpu_to_le32(INTEL_CNVX_TOP_STEP(data->dmp_hdr.cnvr_top)); + dump->rf_id_flavor = cpu_to_le32(INTEL_CNVX_TOP_FLAVOR(data->dmp_hdr.cnvr_top)); + bt_dev_dbg(data->hdev, + "rf_id_type=0x%x rf_id_dash=0x%x rf_id_step=0x%x rf_id_flavor=0x%x (cnvr_top=0x%x)", + le32_to_cpu(dump->rf_id_type), + le32_to_cpu(dump->rf_id_dash), + le32_to_cpu(dump->rf_id_step), + le32_to_cpu(dump->rf_id_flavor), + data->dmp_hdr.cnvr_top); + dump->lmac_major = cpu_to_le32(0); + dump->lmac_minor = cpu_to_le32(0); + dump->umac_major = cpu_to_le32(0); + dump->umac_minor = cpu_to_le32(0); + dump->fw_mon_mode = cpu_to_le32(BTINTEL_PCIE_FW_MON_MODE_DRAM); + + dump->regions_mask = cpu_to_le64(regions_mask); + + bt_dev_dbg(data->hdev, "ExpectedRegionIDs regions_mask=0x%016llx", + le64_to_cpu(dump->regions_mask)); + + snprintf(build_tag, sizeof(build_tag), "%08X", data->dmp_hdr.fw_sha); + + dump->build_tag_len = cpu_to_le32(strlen(build_tag)); + memcpy(dump->build_tag, build_tag, min(strlen(build_tag), + sizeof(dump->build_tag))); + + dump->num_of_cfg_names = cpu_to_le32(0); + + bt_dev_dbg(data->hdev, + "HwType=0x%08x HwStep=0x%08x RfIdType=0x%08x RfIdDash=0x%08x RfIdStep=0x%08x RfIdFlavor=0x%08x", + le32_to_cpu(dump->hw_type), le32_to_cpu(dump->hw_step), + le32_to_cpu(dump->rf_id_type), + le32_to_cpu(dump->rf_id_dash), + le32_to_cpu(dump->rf_id_step), + le32_to_cpu(dump->rf_id_flavor)); + bt_dev_dbg(data->hdev, "VerType=0x%08x VerSubType=0x%08x", + le32_to_cpu(dump->ver_type), le32_to_cpu(dump->ver_subtype)); + bt_dev_dbg(data->hdev, + "LmacMajor=0x%08x LmacMinor=0x%08x UmacMajor=0x%08x UmacMinor=0x%08x", + le32_to_cpu(dump->lmac_major), le32_to_cpu(dump->lmac_minor), + le32_to_cpu(dump->umac_major), + le32_to_cpu(dump->umac_minor)); + bt_dev_dbg(data->hdev, "TriggerReason=0x%04x MonMode=%u BuildTag=%.64s", + le32_to_cpu(dump->trigger_reason), + le32_to_cpu(dump->fw_mon_mode), + dump->build_tag); + + list_add(&entry->list, list); + + return 0; +} + +static bool btintel_pcie_is_mdbgc_supported(struct btintel_pcie_data *data) +{ + return data->pdev->device == BTINTEL_PCIE_DEVICE_ID_NVL_S_SCP2 || + data->pdev->device == BTINTEL_PCIE_DEVICE_ID_NVL_Hx_SCP2 || + data->pdev->device == BTINTEL_PCIE_DEVICE_ID_PTL_FMP2; +} + +static void btintel_pcie_dump_mem_range(struct btintel_pcie_data *data, + struct list_head *list, u32 region_id, + const char *name, u32 addr_start, + u32 addr_end, u64 *regions_mask) +{ + u32 region_size; + + if (!addr_start || !addr_end || addr_end < addr_start) + return; + + if (addr_end > U32_MAX - 0x04) { + bt_dev_warn(data->hdev, + "Skipping dump region %s: addr_end 0x%08x would overflow", + name, addr_end); + return; + } + + region_size = (addr_end + 0x04) - addr_start; + if (region_size > BTINTEL_PCIE_REGION_MAX_SIZE) { + bt_dev_warn(data->hdev, + "Skipping dump region %s: size %u exceeds max %u", + name, region_size, + BTINTEL_PCIE_REGION_MAX_SIZE); + return; + } + + if (!btintel_pcie_dump_target_region(data, list, region_id, name, + addr_start, region_size)) + *regions_mask |= BIT_ULL(region_id); +} + +static int btintel_pcie_read_debug_regions(struct btintel_pcie_data *data) +{ + struct btintel_pcie_dbgc *dbgc = NULL; + struct btintel_pcie_mdbgc *mdbgc = NULL; struct hci_dev *hdev = data->hdev; - u8 *pdata, *p, buf_idx, hw_variant; - struct intel_tlv *tlv; - struct timespec64 now; - struct tm tm_now; - char fw_build[128]; - char ts[128]; - char vendor[64]; - char driver[64]; + struct btintel_pcie_dump_entry *entry; + struct btintel_pcie_dump_file_hdr *file_hdr; + struct scatterlist *sg_dump_data; + u32 status_reg, wrap_reg; + u32 exception_dump_len; + u32 exc_addr; + u64 regions_mask = 0; + u8 hw_variant; + u32 smem_rd_addr = 0, smem_rd_size = 0; + u32 file_len; + u8 count; + int ret; + LIST_HEAD(dump_list); if (!IS_ENABLED(CONFIG_DEV_COREDUMP)) return -EOPNOTSUPP; + if (btintel_pcie_is_mdbgc_supported(data)) { + mdbgc = &data->mdbgc; + count = mdbgc->count; + } else { + dbgc = &data->dbgc; + count = dbgc->count; + } hw_variant = INTEL_HW_VARIANT(data->cnvi); - switch (hw_variant) { - case BTINTEL_HWID_BZRI: - case BTINTEL_HWID_BZRIW: + + if (hw_variant == BTINTEL_HWID_BZRI || + hw_variant == BTINTEL_HWID_BZRIW) { status_reg = BTINTEL_PCIE_DBGC_CUR_DBGBUFF_STATUS; wrap_reg = BTINTEL_PCIE_DBGC_DBGBUFF_WRAP_ARND; - break; - case BTINTEL_HWID_SCP: - case BTINTEL_HWID_SCP2: - case BTINTEL_HWID_SCP2F: + } else if (hw_variant >= BTINTEL_HWID_SCP) { status_reg = BTINTEL_PCIE_DBGC_CUR_DBGBUFF_STATUS_SCP; wrap_reg = BTINTEL_PCIE_DBGC_DBGBUFF_WRAP_ARND_SCP; - break; - default: - bt_dev_err(hdev, "Unsupported Intel hardware variant (0x%2.2x)", + } else { + bt_dev_err(hdev, + "Unsupported Intel hardware variant (0x%2.2x)", hw_variant); return -EINVAL; } - wr_ptr_status = btintel_pcie_rd_dev_mem(data, status_reg); - data->dmp_hdr.wrap_ctr = btintel_pcie_rd_dev_mem(data, wrap_reg); + smem_rd_addr = data->dump_info.smem_addr_start; + smem_rd_size = 0; - offset = wr_ptr_status & BTINTEL_PCIE_DBG_OFFSET_BIT_MASK; + if (!smem_rd_addr && !data->dump_info.smem_addr_end) { + bt_dev_dbg(hdev, "smem region not advertised by firmware"); + } else if (data->dump_info.smem_addr_end < smem_rd_addr || + data->dump_info.smem_addr_end > U32_MAX - 0x04) { + bt_dev_err(hdev, + "Invalid smem region: start=0x%08x end=0x%08x", + smem_rd_addr, data->dump_info.smem_addr_end); + } else { + smem_rd_size = (data->dump_info.smem_addr_end + 0x04) - + smem_rd_addr; - buf_idx = BTINTEL_PCIE_DBGC_DBG_BUF_IDX(wr_ptr_status); - if (buf_idx > dbgc->count) { - bt_dev_warn(hdev, "Buffer index is invalid"); - return -EINVAL; + bt_dev_dbg(hdev, + "smem_region: smem_start_addr=0x%08x smem_end_addr=0x%08x smem_rd_size=%u", + smem_rd_addr, data->dump_info.smem_addr_end, + smem_rd_size); + + if (smem_rd_size == 0 || + smem_rd_size > BTINTEL_PCIE_SMEM_MAX_SIZE) { + bt_dev_err(hdev, + "Invalid smem region: smem_rd_addr 0x%08x size %u (max %u)", + smem_rd_addr, smem_rd_size, + BTINTEL_PCIE_SMEM_MAX_SIZE); + smem_rd_size = 0; + } } - prev_size = buf_idx * BTINTEL_PCIE_DBGC_BUFFER_SIZE; - if (prev_size + offset >= prev_size) - data->dmp_hdr.write_ptr = prev_size + offset; - else - return -EINVAL; + if (btintel_pcie_is_mdbgc_supported(data)) { + ret = btintel_pcie_dump_dram_monitor(data, &dump_list, count, + mdbgc->buf1, status_reg, + wrap_reg, + BTINTEL_PCIE_MDBGC_ALLOCATIONID_1, + BTINTEL_PCIE_INI_ID_DRAM_MONITOR1, + "monitor"); + if (!ret) + regions_mask |= + BIT_ULL(BTINTEL_PCIE_INI_ID_DRAM_MONITOR1); + else + bt_dev_warn(hdev, "Failed to dump DRAM buf1: %d", ret); + + ret = btintel_pcie_dump_dram_monitor(data, &dump_list, count, + mdbgc->buf2, status_reg, + wrap_reg, + BTINTEL_PCIE_MDBGC_ALLOCATIONID_2, + BTINTEL_PCIE_INI_ID_DRAM_MONITOR2, + "monitor2"); + if (!ret) + regions_mask |= + BIT_ULL(BTINTEL_PCIE_INI_ID_DRAM_MONITOR2); + else + bt_dev_warn(hdev, "Failed to dump DRAM buf2: %d", ret); + + ret = btintel_pcie_dump_dram_monitor(data, &dump_list, count, + mdbgc->buf3, status_reg, + wrap_reg, + BTINTEL_PCIE_MDBGC_ALLOCATIONID_3, + BTINTEL_PCIE_INI_ID_DRAM_MONITOR3, + "monitor3"); + if (!ret) + regions_mask |= + BIT_ULL(BTINTEL_PCIE_INI_ID_DRAM_MONITOR3); + else + bt_dev_warn(hdev, "Failed to dump DRAM buf3: %d", ret); + } else { + ret = btintel_pcie_dump_dram_monitor(data, &dump_list, count, + dbgc->bufs, status_reg, + wrap_reg, + BTINTEL_PCIE_MDBGC_ALLOCATIONID_1, + BTINTEL_PCIE_INI_ID_DRAM_MONITOR1, + "monitor"); + if (!ret) + regions_mask |= + BIT_ULL(BTINTEL_PCIE_INI_ID_DRAM_MONITOR1); + else + bt_dev_warn(hdev, + "Failed to dump DRAM region: %d", ret); + } - strscpy(vendor, "Vendor: Intel\n"); - snprintf(driver, sizeof(driver), "Driver: %s\n", - data->dmp_hdr.driver_name); - - ktime_get_real_ts64(&now); - time64_to_tm(now.tv_sec, 0, &tm_now); - snprintf(ts, sizeof(ts), "Dump Time: %02d-%02d-%04ld %02d:%02d:%02d", - tm_now.tm_mday, tm_now.tm_mon + 1, tm_now.tm_year + 1900, - tm_now.tm_hour, tm_now.tm_min, tm_now.tm_sec); - - snprintf(fw_build, sizeof(fw_build), - "Firmware Timestamp: Year %u WW %02u buildtype %u build %u", - 2000 + (data->dmp_hdr.fw_timestamp >> 8), - data->dmp_hdr.fw_timestamp & 0xff, data->dmp_hdr.fw_build_type, - data->dmp_hdr.fw_build_num); - - data_len = sizeof(*tlv) + sizeof(data->dmp_hdr.cnvi_bt) + - sizeof(*tlv) + sizeof(data->dmp_hdr.write_ptr) + - sizeof(*tlv) + sizeof(data->dmp_hdr.wrap_ctr) + - sizeof(*tlv) + sizeof(data->dmp_hdr.trigger_reason) + - sizeof(*tlv) + sizeof(data->dmp_hdr.fw_git_sha1) + - sizeof(*tlv) + sizeof(data->dmp_hdr.cnvr_top) + - sizeof(*tlv) + sizeof(data->dmp_hdr.cnvi_top) + - sizeof(*tlv) + strlen(ts) + - sizeof(*tlv) + strlen(fw_build) + - sizeof(*tlv) + strlen(vendor) + - sizeof(*tlv) + strlen(driver); - - if (data->dmp_hdr.event_type && data->dmp_hdr.event_id) { - data_len += sizeof(*tlv) + sizeof(data->dmp_hdr.event_type); - data_len += sizeof(*tlv) + sizeof(data->dmp_hdr.event_id); + if (smem_rd_size && + !btintel_pcie_dump_smem_monitor_region(data, &dump_list, + BTINTEL_PCIE_INI_ID_SMEM, + "monitor_smem", + smem_rd_addr, + smem_rd_size)) + regions_mask |= BIT_ULL(BTINTEL_PCIE_INI_ID_SMEM); + + exc_addr = data->dump_info.exception_dump_addr; + exception_dump_len = data->dump_info.exception_dump_len; + if (exc_addr && exception_dump_len) { + ret = btintel_pcie_dump_target_region(data, &dump_list, + BTINTEL_PCIE_INI_ID_EXCEPTION_EVT, + "EXCEPTION_EVT_BUFFER", exc_addr, + exception_dump_len); + if (!ret) + regions_mask |= BIT_ULL(BTINTEL_PCIE_INI_ID_EXCEPTION_EVT); } - /* - * sizeof(u32) - signature - * sizeof(data_len) - to store tlv data size - * data_len - TLV data - */ - dump_size = sizeof(u32) + sizeof(data_len) + data_len; + btintel_pcie_dump_mem_range(data, &dump_list, + BTINTEL_PCIE_INI_ID_DCCM, "DCCM", + data->dump_info.dccm_addr_start, + data->dump_info.dccm_addr_end, + ®ions_mask); + + btintel_pcie_dump_mem_range(data, &dump_list, + BTINTEL_PCIE_INI_ID_SDS, "SDS", + data->dump_info.sds_start_addr_start, + data->dump_info.sds_start_addr_end, + ®ions_mask); + + btintel_pcie_dump_mem_range(data, &dump_list, + BTINTEL_PCIE_INI_ID_SDS_IOSF, "SDS_IOSF", + data->dump_info.sds_iosf_data_addr_start, + data->dump_info.sds_iosf_data_addr_end, + ®ions_mask); + + btintel_pcie_dump_mem_range(data, &dump_list, + BTINTEL_PCIE_INI_ID_ECL, "ECL_REGION", + data->dump_info.ecl_addr_start, + data->dump_info.ecl_addr_end, + ®ions_mask); + + ret = btintel_pcie_dump_info(data, &dump_list, regions_mask); + if (ret) { + btintel_pcie_dump_list_free(&dump_list); + return ret; + } + + file_len = sizeof(*file_hdr) + + btintel_pcie_dump_list_total_size(&dump_list); + entry = btintel_pcie_dump_entry_alloc(sizeof(*file_hdr)); + if (!entry) { + btintel_pcie_dump_list_free(&dump_list); + return -ENOMEM; + } + file_hdr = (void *)entry->data; + file_hdr->barker = cpu_to_le32(BTINTEL_PCIE_INI_ERROR_DUMP_BARKER); + file_hdr->file_len = cpu_to_le32(file_len); + list_add(&entry->list, &dump_list); - /* Add debug buffers data length to dump size */ - dump_size += BTINTEL_PCIE_DBGC_BUFFER_SIZE * dbgc->count; + sg_dump_data = btintel_pcie_alloc_sgtable(file_len); + if (sg_dump_data) { + int sg_entries = sg_nents(sg_dump_data); + u32 offs = 0; - pdata = vmalloc(dump_size); - if (!pdata) - return -ENOMEM; - p = pdata; - - *(u32 *)p = BTINTEL_PCIE_MAGIC_NUM; - p += sizeof(u32); - - *(u32 *)p = data_len; - p += sizeof(u32); - - - p = btintel_pcie_copy_tlv(p, BTINTEL_VENDOR, vendor, strlen(vendor)); - p = btintel_pcie_copy_tlv(p, BTINTEL_DRIVER, driver, strlen(driver)); - p = btintel_pcie_copy_tlv(p, BTINTEL_DUMP_TIME, ts, strlen(ts)); - p = btintel_pcie_copy_tlv(p, BTINTEL_FW_BUILD, fw_build, - strlen(fw_build)); - p = btintel_pcie_copy_tlv(p, BTINTEL_CNVI_BT, &data->dmp_hdr.cnvi_bt, - sizeof(data->dmp_hdr.cnvi_bt)); - p = btintel_pcie_copy_tlv(p, BTINTEL_WRITE_PTR, &data->dmp_hdr.write_ptr, - sizeof(data->dmp_hdr.write_ptr)); - p = btintel_pcie_copy_tlv(p, BTINTEL_WRAP_CTR, &data->dmp_hdr.wrap_ctr, - sizeof(data->dmp_hdr.wrap_ctr)); - p = btintel_pcie_copy_tlv(p, BTINTEL_TRIGGER_REASON, &data->dmp_hdr.trigger_reason, - sizeof(data->dmp_hdr.trigger_reason)); - p = btintel_pcie_copy_tlv(p, BTINTEL_FW_SHA, &data->dmp_hdr.fw_git_sha1, - sizeof(data->dmp_hdr.fw_git_sha1)); - p = btintel_pcie_copy_tlv(p, BTINTEL_CNVR_TOP, &data->dmp_hdr.cnvr_top, - sizeof(data->dmp_hdr.cnvr_top)); - p = btintel_pcie_copy_tlv(p, BTINTEL_CNVI_TOP, &data->dmp_hdr.cnvi_top, - sizeof(data->dmp_hdr.cnvi_top)); - - if (data->dmp_hdr.event_type && data->dmp_hdr.event_id) { - p = btintel_pcie_copy_tlv(p, BTINTEL_EVENT_TYPE, - &data->dmp_hdr.event_type, - sizeof(data->dmp_hdr.event_type)); - p = btintel_pcie_copy_tlv(p, BTINTEL_EVENT_ID, - &data->dmp_hdr.event_id, - sizeof(data->dmp_hdr.event_id)); - data->dmp_hdr.event_type = 0; - data->dmp_hdr.event_id = 0; - } - - memcpy(p, dbgc->bufs[0].data, dbgc->count * BTINTEL_PCIE_DBGC_BUFFER_SIZE); - dev_coredumpv(&hdev->dev, pdata, dump_size, GFP_KERNEL); - return 0; + list_for_each_entry(entry, &dump_list, list) { + sg_pcopy_from_buffer(sg_dump_data, sg_entries, + entry->data, entry->size, offs); + offs += entry->size; + } + + bt_dev_dbg(hdev, "triggering dev_coredumpsg()"); + dev_coredumpsg(&hdev->dev, sg_dump_data, file_len, GFP_KERNEL); + } else { + bt_dev_err(hdev, "Failed to allocate scatter-gather table for coredump"); + ret = -ENOMEM; + } + + btintel_pcie_dump_list_free(&dump_list); + return ret; } static void btintel_pcie_dump_traces(struct hci_dev *hdev) { struct btintel_pcie_data *data = hci_get_drvdata(hdev); - int ret = 0; + int ret; ret = btintel_pcie_get_mac_access(data); if (ret) { @@ -809,7 +1445,7 @@ static void btintel_pcie_dump_traces(struct hci_dev *hdev) return; } - ret = btintel_pcie_read_dram_buffers(data); + ret = btintel_pcie_read_debug_regions(data); btintel_pcie_release_mac_access(data); @@ -957,10 +1593,323 @@ static inline bool btintel_pcie_in_error(struct btintel_pcie_data *data) return data->boot_stage_cache & BTINTEL_PCIE_CSR_BOOT_STAGE_ABORT_HANDLER; } +static const char *btintel_pcie_tlv_str(u8 tlv_type) +{ + switch (tlv_type) { + case BTINTEL_PCIE_TLV_TYPE_EXCEPTION_DUMP_ADDRESS: + return "EXCEPTION_DUMP_ADDRESS"; + case BTINTEL_PCIE_TLV_TYPE_DCCM_MEM_ADDRESS: + return "DCCM_MEM_ADDRESS"; + case BTINTEL_PCIE_TLV_TYPE_SDS_MEM_ADDRESS: + return "SDS_MEM_ADDRESS"; + case BTINTEL_PCIE_TLV_TYPE_ECL_MEM_ADDRESS: + return "ECL_MEM_ADDRESS"; + case BTINTEL_PCIE_TLV_TYPE_SMEM_ADDRESS: + return "SMEM_ADDRESS"; + default: + return "UNKNOWN"; + } +} + +static int btintel_parse_mbox_tlv(struct btintel_pcie_data *data) +{ + /* Custom TLV structure for mailbox parsing + * len is __le16 as per agreement with FW + */ + struct mbox_tlv { + u8 type; + __le16 len; + u8 val[]; + } __packed; + + u8 *buffer, *ptr; + u32 buffer_len, remaining; + int err; + u32 tbl_addr, tbl_size; + struct mbox_tlv *tlv; + struct btintel_data *cnvi_data = hci_get_priv(data->hdev); + u8 hw_variant = INTEL_HW_VARIANT(cnvi_data->cnvi_bt); + long t; + + /* Wait for GP0 alive interrupt to post RX buffers */ + t = wait_event_timeout(data->mbox_parse_wait_q, + test_bit(BTINTEL_PCIE_MBOX_PARSE_READY, &data->flags), + msecs_to_jiffies(BTINTEL_PCIE_MBOX_INTR_TIMEOUT_MS)); + if (!t) { + bt_dev_warn(data->hdev, + "Timeout (%u ms) waiting for alive interrupt before mbox TLV parse; skipping", + BTINTEL_PCIE_MBOX_INTR_TIMEOUT_MS); + return 0; + } + clear_bit(BTINTEL_PCIE_MBOX_PARSE_READY, &data->flags); + + bt_dev_info(data->hdev, + "mbox TLV parse started at %lld ns (%lld us after mbox interrupt)", + ktime_to_ns(ktime_get()), + ktime_to_us(ktime_sub(ktime_get(), data->mbox_intr_ts))); + + memset(&data->dump_info, 0, sizeof(data->dump_info)); + + /* Snapshot to avoid TOCTOU with the GP1 IRQ handler */ + tbl_size = READ_ONCE(data->debug_table_size); + tbl_addr = READ_ONCE(data->debug_table_addr); + + if (!tbl_size || !tbl_addr) + return -EINVAL; + + /* Ensure size is 4-byte aligned; btintel_pcie_read_device_mem() + * reads device memory in 4-byte units. + */ + tbl_size = ALIGN_DOWN(tbl_size, 4); + + if (!tbl_size) + return -EINVAL; + + if (tbl_size > SZ_1M) { + bt_dev_err(data->hdev, "Debug table size too large: %u", + tbl_size); + return -EINVAL; + } + + buffer_len = tbl_size; + + buffer = vmalloc(buffer_len); + if (!buffer) + return -ENOMEM; + + btintel_pcie_mac_init(data); + + err = btintel_pcie_read_device_mem(data, buffer, tbl_addr, + buffer_len); + if (err) + goto exit_on_error; + + print_hex_dump(KERN_INFO, "Bluetooth: mbox_tlv: ", DUMP_PREFIX_OFFSET, 16, 1, + buffer, buffer_len, false); + + ptr = buffer; + remaining = buffer_len; + + /* Parse TLV structures: 1 byte type + 2 bytes length + + * variable value + */ + while (remaining >= sizeof(struct mbox_tlv)) { + u16 tlv_len; + u32 tlv_total; + + tlv = (struct mbox_tlv *)ptr; + tlv_len = le16_to_cpu(tlv->len); + tlv_total = sizeof(tlv->type) + sizeof(tlv->len) + tlv_len; + + if (tlv_total > remaining) { + bt_dev_err(data->hdev, "TLV parse error: not enough data for TLV value (type=%u, len=%u)", + tlv->type, tlv_len); + break; + } + + switch (tlv->type) { + case BTINTEL_PCIE_TLV_TYPE_EXCEPTION_DUMP_ADDRESS: + if (tlv_len < 8) { + bt_dev_err(data->hdev, + "TLV %s too short: len=%u (need 8)", + btintel_pcie_tlv_str(tlv->type), + tlv_len); + break; + } + data->dump_info.exception_dump_addr = + get_unaligned_le32(&tlv->val[0]); + data->dump_info.exception_dump_len = + get_unaligned_le32(&tlv->val[4]); + break; + case BTINTEL_PCIE_TLV_TYPE_DCCM_MEM_ADDRESS: + if (tlv_len < 8) { + bt_dev_err(data->hdev, + "TLV %s too short: len=%u (need 8)", + btintel_pcie_tlv_str(tlv->type), + tlv_len); + break; + } + data->dump_info.dccm_addr_start = + get_unaligned_le32(&tlv->val[0]); + data->dump_info.dccm_addr_end = + get_unaligned_le32(&tlv->val[4]); + break; + case BTINTEL_PCIE_TLV_TYPE_SDS_MEM_ADDRESS: + /* hw_variant comes from cnvi_bt which is set during + * setup. If mailbox fires before setup completes, + * hw_variant is 0. Skip SDS parsing in that case. + */ + if (!hw_variant) { + bt_dev_dbg(data->hdev, "SDS TLV: skipped, hw_variant not yet known"); + break; + } + if (tlv_len == 16 && hw_variant > BTINTEL_HWID_BZRI) { + data->dump_info.sds_start_addr_start = + get_unaligned_le32(&tlv->val[0]); + data->dump_info.sds_start_addr_end = + get_unaligned_le32(&tlv->val[4]); + data->dump_info.sds_iosf_data_addr_start = + get_unaligned_le32(&tlv->val[8]); + data->dump_info.sds_iosf_data_addr_end = + get_unaligned_le32(&tlv->val[12]); + } else if (tlv_len == 24 && + (hw_variant == BTINTEL_HWID_BZRI || + hw_variant == BTINTEL_HWID_BZRIW)) { + data->dump_info.sds_fixed_rom_addr_start = + get_unaligned_le32(&tlv->val[0]); + data->dump_info.sds_fixed_rom_addr_end = + get_unaligned_le32(&tlv->val[4]); + data->dump_info.sds_start_addr_start = + get_unaligned_le32(&tlv->val[8]); + data->dump_info.sds_start_addr_end = + get_unaligned_le32(&tlv->val[12]); + data->dump_info.sds_iosf_data_addr_start = + get_unaligned_le32(&tlv->val[16]); + data->dump_info.sds_iosf_data_addr_end = + get_unaligned_le32(&tlv->val[20]); + } else { + bt_dev_err(data->hdev, + "SDS TLV: hw=0x%2.2x len=%u", + hw_variant, tlv_len); + } + break; + case BTINTEL_PCIE_TLV_TYPE_ECL_MEM_ADDRESS: + if (tlv_len < 8) { + bt_dev_err(data->hdev, + "TLV %s too short: len=%u (need 8)", + btintel_pcie_tlv_str(tlv->type), + tlv_len); + break; + } + data->dump_info.ecl_addr_start = + get_unaligned_le32(&tlv->val[0]); + data->dump_info.ecl_addr_end = + get_unaligned_le32(&tlv->val[4]); + break; + case BTINTEL_PCIE_TLV_TYPE_SMEM_ADDRESS: + if (tlv_len < 8) { + bt_dev_err(data->hdev, + "TLV %s too short: len=%u (need 8)", + btintel_pcie_tlv_str(tlv->type), + tlv_len); + break; + } + data->dump_info.smem_addr_start = + get_unaligned_le32(&tlv->val[0]); + data->dump_info.smem_addr_end = + get_unaligned_le32(&tlv->val[4]); + break; + default: + bt_dev_dbg(data->hdev, "Unknown TLV type: %u length: %u", + tlv->type, tlv_len); + break; + } + + /* Move to next TLV */ + ptr += tlv_total; + remaining -= tlv_total; + } + + bt_dev_info(data->hdev, + "exception_dump: addr:0x%08x len:0x%08x", + data->dump_info.exception_dump_addr, + data->dump_info.exception_dump_len); + bt_dev_info(data->hdev, + "dccm: start:0x%08x end:0x%08x", + data->dump_info.dccm_addr_start, + data->dump_info.dccm_addr_end); + bt_dev_info(data->hdev, + "sds_fixed_rom: start:0x%08x end:0x%08x", + data->dump_info.sds_fixed_rom_addr_start, + data->dump_info.sds_fixed_rom_addr_end); + bt_dev_info(data->hdev, + "sds: start:0x%08x end:0x%08x", + data->dump_info.sds_start_addr_start, + data->dump_info.sds_start_addr_end); + bt_dev_info(data->hdev, + "sds_iosf: start:0x%08x end:0x%08x", + data->dump_info.sds_iosf_data_addr_start, + data->dump_info.sds_iosf_data_addr_end); + bt_dev_info(data->hdev, + "ecl: start:0x%08x end:0x%08x", + data->dump_info.ecl_addr_start, + data->dump_info.ecl_addr_end); + bt_dev_info(data->hdev, + "smem: start:0x%08x end:0x%08x", + data->dump_info.smem_addr_start, + data->dump_info.smem_addr_end); + + vfree(buffer); + return 0; + +exit_on_error: + vfree(buffer); + return err; +} + static void btintel_pcie_msix_gp1_handler(struct btintel_pcie_data *data) { - bt_dev_err(data->hdev, "Received gp1 mailbox interrupt"); - btintel_pcie_dump_debug_registers(data->hdev); + bool target_access = false; + u32 addr = 0, size = 0; + + /* Read the Mail box status and registers */ + data->mbox.mbox_status = btintel_pcie_rd_reg32(data, BTINTEL_PCIE_CSR_MBOX_STATUS_REG); + if (data->mbox.mbox_status & BTINTEL_PCIE_CSR_MBOX_STATUS_MBOX1) { + data->mbox.mbox1 = btintel_pcie_rd_reg32(data, BTINTEL_PCIE_CSR_MBOX_1_REG); + if (data->mbox.mbox1 == + BTINTEL_PCIE_BUILD_SPECIFIC_RESOURCES_MAPPING) { + bt_dev_info(data->hdev, + "mailbox for target access"); + target_access = true; + } + } + + if (data->mbox.mbox_status & BTINTEL_PCIE_CSR_MBOX_STATUS_MBOX2) { + data->mbox.mbox2 = btintel_pcie_rd_reg32(data, BTINTEL_PCIE_CSR_MBOX_2_REG); + if (target_access) + addr = data->mbox.mbox2; + } + + if (data->mbox.mbox_status & BTINTEL_PCIE_CSR_MBOX_STATUS_MBOX3) { + data->mbox.mbox3 = btintel_pcie_rd_reg32(data, BTINTEL_PCIE_CSR_MBOX_3_REG); + if (target_access) + size = data->mbox.mbox3; + } + + if (data->mbox.mbox_status & BTINTEL_PCIE_CSR_MBOX_STATUS_MBOX4) + data->mbox.mbox4 = btintel_pcie_rd_reg32(data, BTINTEL_PCIE_CSR_MBOX_4_REG); + + bt_dev_dbg(data->hdev, + "GP1: sts:0x%08x mb1:0x%08x mb2:0x%08x mb3:0x%08x mb4:0x%08x", + data->mbox.mbox_status, data->mbox.mbox1, + data->mbox.mbox2, data->mbox.mbox3, + data->mbox.mbox4); + + if (target_access && + !test_and_set_bit(BTINTEL_PCIE_MAIL_BOX_INTR, + &data->flags)) { + /* Arm the mbox<->alive handshake */ + clear_bit(BTINTEL_PCIE_MBOX_PARSE_READY, &data->flags); + set_bit(BTINTEL_PCIE_MBOX_PARSE_PENDING, &data->flags); + data->mbox_intr_ts = ktime_get(); + + bt_dev_info(data->hdev, + "mbox interrupt received at %lld ns; queuing mbox_work", + ktime_to_ns(data->mbox_intr_ts)); + + WRITE_ONCE(data->debug_table_addr, addr); + WRITE_ONCE(data->debug_table_size, size); + if (!queue_work(data->dump_workqueue, + &data->mbox_work)) { + clear_bit(BTINTEL_PCIE_MAIL_BOX_INTR, &data->flags); + clear_bit(BTINTEL_PCIE_MBOX_PARSE_PENDING, &data->flags); + } + } + + /* Mailbox is read, ack to FW */ + btintel_pcie_set_reg_bits(data, + BTINTEL_PCIE_CSR_IPC_DOORBELL_VEC_REG, + BTINTEL_PCIE_CSR_DOORBELL_MBOX_READ_CONFIRM); } /* This function handles the MSI-X interrupt for gp0 cause (bit 0 in @@ -1064,6 +2013,12 @@ static void btintel_pcie_msix_gp0_handler(struct btintel_pcie_data *data) if (submit_rx) { btintel_pcie_reset_ia(data); btintel_pcie_start_rx(data); + + /* Complete the mbox<->alive handshake */ + if (test_and_clear_bit(BTINTEL_PCIE_MBOX_PARSE_PENDING, &data->flags)) { + set_bit(BTINTEL_PCIE_MBOX_PARSE_READY, &data->flags); + wake_up(&data->mbox_parse_wait_q); + } } if (signal_waitq) { @@ -1099,7 +2054,7 @@ static void btintel_pcie_msix_tx_handle(struct btintel_pcie_data *data) urbd0 = &txq->urbd0s[cr_tia]; - if (urbd0->tfd_index > txq->count) + if (urbd0->tfd_index >= txq->count) return; cr_tia = (cr_tia + 1) % txq->count; @@ -1287,7 +2242,8 @@ exit_error: static void btintel_pcie_read_hwexp(struct btintel_pcie_data *data) { - int len, err, offset, pending; + int err, offset, pending; + u32 len; struct sk_buff *skb; u8 *buf, prefix[64]; u32 addr, val; @@ -1307,23 +2263,30 @@ static void btintel_pcie_read_hwexp(struct btintel_pcie_data *data) /* only from step B0 onwards */ if (INTEL_CNVX_TOP_STEP(data->dmp_hdr.cnvi_top) != 0x01) return; - len = BTINTEL_PCIE_BLZR_HWEXP_SIZE; /* exception data length */ - addr = BTINTEL_PCIE_BLZR_HWEXP_DMP_ADDR; break; case BTINTEL_CNVI_SCP: - len = BTINTEL_PCIE_SCP_HWEXP_SIZE; - addr = BTINTEL_PCIE_SCP_HWEXP_DMP_ADDR; - break; case BTINTEL_CNVI_SCP2: case BTINTEL_CNVI_SCP2F: - len = BTINTEL_PCIE_SCP2_HWEXP_SIZE; - addr = BTINTEL_PCIE_SCP2_HWEXP_DMP_ADDR; break; default: bt_dev_err(data->hdev, "Unsupported cnvi 0x%8.8x", data->dmp_hdr.cnvi_top); return; } + len = data->dump_info.exception_dump_len; + addr = data->dump_info.exception_dump_addr; + + if (!addr || len < sizeof(__le32) || len > SZ_4K) { + bt_dev_err(data->hdev, "Invalid exception address: 0x%8.8x or length: %u", + addr, len); + return; + } + + /* Ensure size is 4-byte aligned; btintel_pcie_read_device_mem() + * reads device memory in 4-byte units. + */ + len = ALIGN_DOWN(len, 4); + buf = kzalloc(len, GFP_KERNEL); if (!buf) goto exit_on_error; @@ -1425,11 +2388,8 @@ static int btintel_pcie_dump_fwtrigger_event(struct btintel_pcie_data *data) goto exit_on_error; evt = (void *)buf; - data->dmp_hdr.event_type = evt->event_type; - data->dmp_hdr.event_id = le16_to_cpu(evt->event_id); - bt_dev_dbg(data->hdev, "event type: 0x%2.2x event id: 0x%4.4x len: %u", - data->dmp_hdr.event_type, data->dmp_hdr.event_id, len); + evt->event_type, le16_to_cpu(evt->event_id), len); skb = bt_skb_alloc(len, GFP_KERNEL); if (!skb) { @@ -1455,8 +2415,8 @@ exit_on_error: * * Always queue this AFTER any companion event-reader work (hwexp / * fwtrigger) so that, on the ordered @dump_workqueue, the event reader - * runs first and populates dmp_hdr.event_type / event_id before - * dump_traces consumes them. + * runs first and the trigger metadata is populated before dump_traces + * consumes it. */ static bool btintel_pcie_queue_coredump(struct btintel_pcie_data *data, u16 trigger_reason) @@ -1530,6 +2490,15 @@ static void btintel_pcie_coredump_worker(struct work_struct *work) if (!data->hdev) goto out; + /* When firmware routes debug traces to the WiFi DBGC, no host + * DBGC buffers were allocated, so there is nothing to dump here. + */ + if (btintel_pcie_dbg_to_wifi(data)) { + bt_dev_info(data->hdev, + "Skipping coredump: debug traces routed to WiFi DBGC"); + goto out; + } + btintel_pcie_dump_traces(data->hdev); out: /* Release guard last so a new trigger can run only after this @@ -1576,6 +2545,20 @@ out: clear_bit(BTINTEL_PCIE_FWTRIGGER_DUMP_INPROGRESS, &data->flags); } +static void btintel_pcie_mbox_worker(struct work_struct *work) +{ + struct btintel_pcie_data *data = container_of(work, + struct btintel_pcie_data, mbox_work); + + if (!data->hdev) + goto out; + + btintel_parse_mbox_tlv(data); +out: + /* Release guard last; matches set in gp1 handler. */ + clear_bit(BTINTEL_PCIE_MAIL_BOX_INTR, &data->flags); +} + static void btintel_pcie_rx_work(struct work_struct *work) { struct btintel_pcie_data *data = container_of(work, @@ -1599,7 +2582,9 @@ static int btintel_pcie_submit_rx_work(struct btintel_pcie_data *data, u8 status rfh_hdr = buf; len = rfh_hdr->packet_len; - if (len <= 0) { + if (len == 0 || len > BTINTEL_PCIE_BUFFER_SIZE - sizeof(*rfh_hdr)) { + bt_dev_err(data->hdev, "Invalid packet_len %d (max %zu)", len, + BTINTEL_PCIE_BUFFER_SIZE - sizeof(*rfh_hdr)); ret = -EINVAL; goto resubmit; } @@ -1889,9 +2874,22 @@ static void btintel_pcie_init_ci(struct btintel_pcie_data *data, ci->num_urbdq1 = data->rxq.count; ci->urbdq_db_vec = BTINTEL_PCIE_RXQ_NUM; - ci->dbg_output_mode = 0x01; - ci->dbgc_addr = data->dbgc.frag_p_addr; - ci->dbgc_size = data->dbgc.frag_size; + ci->dbg_output_mode = btintel_pcie_dbg_to_wifi(data) ? + BTINTEL_PCIE_WIFI_DBGC : BTINTEL_PCIE_DRAM; + if (btintel_pcie_dbg_to_wifi(data)) { + /* Firmware forwards debug traces to the WiFi DBGC, so no + * host DBGC buffer is needed; leave dbgc_addr/size as 0. + */ + ci->dbgc_addr = 0; + ci->dbgc_size = 0; + } else if (btintel_pcie_is_mdbgc_supported(data)) { + ci->dbgc_addr = data->mdbgc.frag_p_addr; + ci->dbgc_size = data->mdbgc.frag_size; + } else { + ci->dbgc_addr = data->dbgc.frag_p_addr; + ci->dbgc_size = data->dbgc.frag_size; + } + ci->dbg_preset = 0x00; } @@ -2119,7 +3117,16 @@ static int btintel_pcie_alloc(struct btintel_pcie_data *data) v_addr += ci_size; /* Setup data buffers for dbgc */ - err = btintel_pcie_setup_dbgc(data); + if (btintel_pcie_dbg_to_wifi(data)) { + /* Firmware routes traces to the WiFi DBGC; skip host DBGC + * buffer allocation entirely. + */ + err = 0; + } else if (btintel_pcie_is_mdbgc_supported(data)) { + err = btintel_pcie_setup_mdbgc(data); + } else { + err = btintel_pcie_setup_dbgc(data); + } if (err) goto exit_error_txq; @@ -2383,6 +3390,7 @@ static int btintel_pcie_setup_internal(struct hci_dev *hdev) goto exit_error; } + data->dmp_hdr.cnvi_bt = ver_tlv.cnvi_bt; switch (INTEL_HW_PLATFORM(ver_tlv.cnvi_bt)) { case 0x37: break; @@ -2435,10 +3443,9 @@ static int btintel_pcie_setup_internal(struct hci_dev *hdev) data->dmp_hdr.fw_timestamp = ver_tlv.timestamp; data->dmp_hdr.fw_build_type = ver_tlv.build_type; data->dmp_hdr.fw_build_num = ver_tlv.build_num; - data->dmp_hdr.cnvi_bt = ver_tlv.cnvi_bt; if (ver_tlv.img_type == 0x02 || ver_tlv.img_type == 0x03) - data->dmp_hdr.fw_git_sha1 = ver_tlv.git_sha1; + data->dmp_hdr.fw_sha = ver_tlv.git_sha1; err = btintel_pcie_get_debug_info_addr(hdev); if (err) @@ -2718,6 +3725,7 @@ static void btintel_pcie_reset_work(struct work_struct *wk) disable_work_sync(&data->coredump_work); disable_work_sync(&data->hwexp_work); disable_work_sync(&data->fwtrigger_work); + disable_work_sync(&data->mbox_work); bt_dev_dbg(data->hdev, "Release bluetooth interface"); @@ -2742,6 +3750,7 @@ static void btintel_pcie_reset_work(struct work_struct *wk) enable_work(&data->coredump_work); enable_work(&data->hwexp_work); enable_work(&data->fwtrigger_work); + enable_work(&data->mbox_work); } out: @@ -2999,6 +4008,8 @@ static int btintel_pcie_probe(struct pci_dev *pdev, init_waitqueue_head(&data->tx_wait_q); data->tx_wait_done = false; + init_waitqueue_head(&data->mbox_parse_wait_q); + data->workqueue = alloc_ordered_workqueue(KBUILD_MODNAME, WQ_HIGHPRI); if (!data->workqueue) return -ENOMEM; @@ -3015,9 +4026,11 @@ static int btintel_pcie_probe(struct pci_dev *pdev, INIT_WORK(&data->coredump_work, btintel_pcie_coredump_worker); INIT_WORK(&data->hwexp_work, btintel_pcie_hwexp_worker); INIT_WORK(&data->fwtrigger_work, btintel_pcie_fwtrigger_worker); + INIT_WORK(&data->mbox_work, btintel_pcie_mbox_worker); data->boot_stage_cache = 0x00; data->img_resp_cache = 0x00; + data->dbg_path_cache = BTINTEL_PCIE_WIFI_DBGC; /* FLR can be invoked by echoing to debugfs path, so explicitly * initialized */ @@ -3084,6 +4097,7 @@ static void btintel_pcie_remove(struct pci_dev *pdev) disable_work_sync(&data->coredump_work); disable_work_sync(&data->hwexp_work); disable_work_sync(&data->fwtrigger_work); + disable_work_sync(&data->mbox_work); /* Cancel pending reset work. Skip only when remove() is called from * within the reset work itself (PLDR device_reprobe path) to avoid diff --git a/drivers/bluetooth/btintel_pcie.h b/drivers/bluetooth/btintel_pcie.h index 749369b24031..9d84db48a8e5 100644 --- a/drivers/bluetooth/btintel_pcie.h +++ b/drivers/bluetooth/btintel_pcie.h @@ -18,6 +18,7 @@ #define BTINTEL_PCIE_CSR_CI_ADDR_LSB_REG (BTINTEL_PCIE_CSR_BASE + 0x118) #define BTINTEL_PCIE_CSR_CI_ADDR_MSB_REG (BTINTEL_PCIE_CSR_BASE + 0x11C) #define BTINTEL_PCIE_CSR_IMG_RESPONSE_REG (BTINTEL_PCIE_CSR_BASE + 0x12C) +#define BTINTEL_PCIE_CSR_IPC_DOORBELL_VEC_REG (BTINTEL_PCIE_CSR_BASE + 0x130) #define BTINTEL_PCIE_CSR_MBOX_1_REG (BTINTEL_PCIE_CSR_BASE + 0x170) #define BTINTEL_PCIE_CSR_MBOX_2_REG (BTINTEL_PCIE_CSR_BASE + 0x174) #define BTINTEL_PCIE_CSR_MBOX_3_REG (BTINTEL_PCIE_CSR_BASE + 0x178) @@ -52,6 +53,8 @@ #define BTINTEL_PCIE_CSR_BOOT_STAGE_ALIVE (BIT(23)) #define BTINTEL_PCIE_CSR_BOOT_STAGE_D3_STATE_READY (BIT(24)) +#define BTINTEL_PCIE_CSR_DOORBELL_MBOX_READ_CONFIRM (BIT(4)) + /* Registers for MSI-X */ #define BTINTEL_PCIE_CSR_MSIX_BASE (0x2000) #define BTINTEL_PCIE_CSR_MSIX_FH_INT_CAUSES (BTINTEL_PCIE_CSR_MSIX_BASE + 0x0800) @@ -71,11 +74,16 @@ #define BTINTEL_PCIE_DBGC_BASE_ADDR_SCP (0xf0d5d500) #define BTINTEL_PCIE_DBGC_CUR_DBGBUFF_STATUS_SCP (BTINTEL_PCIE_DBGC_BASE_ADDR_SCP + 0x1C) #define BTINTEL_PCIE_DBGC_DBGBUFF_WRAP_ARND_SCP (BTINTEL_PCIE_DBGC_BASE_ADDR_SCP + 0x2C) +#define BTINTEL_PCIE_DBGC_ALLOCATION_OFFSET 0x100 +#define BTINTEL_PCIE_SMEM_MAX_SIZE (16 * 1024) +#define BTINTEL_PCIE_REGION_MAX_SIZE (16 * 1024 * 1024) #define BTINTEL_PCIE_DBG_IDX_BIT_MASK 0x0F #define BTINTEL_PCIE_DBGC_DBG_BUF_IDX(data) (((data) >> 24) & BTINTEL_PCIE_DBG_IDX_BIT_MASK) #define BTINTEL_PCIE_DBG_OFFSET_BIT_MASK 0xFFFFFF +#define BTINTEL_PCIE_TARGET_ACCESS_FRAG_OFFSET 4 + /* The DRAM buffer count, each buffer size, and * fragment buffer size */ @@ -91,6 +99,31 @@ /* Num of alloc Dbg buff (4) + (LSB(4), MSB(4), Size(4)) for each buffer */ #define BTINTEL_PCIE_DBGC_FRAG_PAYLOAD_SIZE 196 +/* dbg_output_mode values for the context info. + * BTINTEL_PCIE_DRAM: firmware writes traces to host DRAM DBGC buffers. + * BTINTEL_PCIE_WIFI_DBGC: firmware forwards traces to the WiFi DBGC; the + * host does NOT need to allocate DBGC fragment/data buffers and must + * publish dbgc_addr/size as 0 in the context info. + * + * Encoding of BTINTEL_PCIE_WIFI_DBGC (0x06): + * Bit[0] DBGC O/P : 0 = SRAM (don't care, DBGI selected) + * Bit[1] DBGC I/P : 1 = DBGI + * Bits[2:3] DBGI O/P : 01 = WiFi DBGC + */ +#define BTINTEL_PCIE_DRAM 0x01 +#define BTINTEL_PCIE_FW_MON_MODE_DRAM 0x02 +#define BTINTEL_PCIE_WIFI_DBGC 0x06 + +#define BTINTEL_PCIE_MDBGC_FRAG_VERSION 2 + +#define BTINTEL_PCIE_MDBGC_ALLOCATIONID_1 0 +#define BTINTEL_PCIE_MDBGC_ALLOCATIONID_2 1 +#define BTINTEL_PCIE_MDBGC_ALLOCATIONID_3 2 + +#define BTINTEL_PCIE_DEVICE_ID_NVL_S_SCP2 0x6E74 +#define BTINTEL_PCIE_DEVICE_ID_NVL_Hx_SCP2 0xD346 +#define BTINTEL_PCIE_DEVICE_ID_PTL_FMP2 0xE476 + /* Causes for the FH register interrupts */ enum msix_fh_int_causes { BTINTEL_PCIE_MSIX_FH_INT_CAUSES_0 = BIT(0), /* cause 0 */ @@ -121,7 +154,10 @@ enum { BTINTEL_PCIE_COREDUMP_INPROGRESS, BTINTEL_PCIE_FWTRIGGER_DUMP_INPROGRESS, BTINTEL_PCIE_RECOVERY_IN_PROGRESS, - BTINTEL_PCIE_SETUP_DONE + BTINTEL_PCIE_SETUP_DONE, + BTINTEL_PCIE_MAIL_BOX_INTR, + BTINTEL_PCIE_MBOX_PARSE_PENDING, + BTINTEL_PCIE_MBOX_PARSE_READY }; enum btintel_pcie_tlv_type { @@ -153,6 +189,14 @@ enum btintel_pcie_reset_type { BTINTEL_PCIE_IOSF_PRR_PLDR = 1, }; +enum btintel_pcie_mbox_msg { + BTINTEL_PCIE_NO_USE = 0, + BTINTEL_PCIE_TOP_SILENT_RESET, + BTINTEL_PCIE_SB_AUDIO_DEVICE_REPORT, + BTINTEL_PCIE_BUILD_SPECIFIC_RESOURCES_MAPPING, + BTINTEL_PCIE_LAST_MESSAGE = 4095 +}; + #define BTINTEL_PCIE_MSIX_NON_AUTO_CLEAR_CAUSE BIT(7) /* Minimum and Maximum number of MSI-X Vector @@ -166,6 +210,7 @@ enum btintel_pcie_reset_type { /* Default interrupt timeout in msec */ #define BTINTEL_DEFAULT_INTR_TIMEOUT_MS 3000 +#define BTINTEL_PCIE_MBOX_INTR_TIMEOUT_MS 500 #define BTINTEL_PCIE_DX_TRANSITION_MAX_RETRIES 3 @@ -198,6 +243,12 @@ enum { /* RBD buffer size mapping */ #define BTINTEL_PCIE_RBD_SIZE_4K 0x04 +#define BTINTEL_PCIE_TLV_TYPE_EXCEPTION_DUMP_ADDRESS 0x04 +#define BTINTEL_PCIE_TLV_TYPE_DCCM_MEM_ADDRESS 0x05 +#define BTINTEL_PCIE_TLV_TYPE_SDS_MEM_ADDRESS 0x06 +#define BTINTEL_PCIE_TLV_TYPE_ECL_MEM_ADDRESS 0x07 +#define BTINTEL_PCIE_TLV_TYPE_SMEM_ADDRESS 0x08 + /* * Struct for Context Information (v2) * @@ -424,6 +475,50 @@ struct btintel_pcie_dbgc { struct data_buf *bufs; }; +struct btintel_pcie_mdbgc { + u32 count; + + void *frag_v_addr; + dma_addr_t frag_p_addr; + u32 frag_size; + + dma_addr_t buf1_p_addr; + void *buf1_v_addr; + dma_addr_t buf2_p_addr; + void *buf2_v_addr; + dma_addr_t buf3_p_addr; + void *buf3_v_addr; + struct data_buf *buf1; + struct data_buf *buf2; + struct data_buf *buf3; +}; + +struct btintel_pcie_dump_mem_info { + u32 exception_dump_addr; + u32 exception_dump_len; + u32 dccm_addr_start; + u32 dccm_addr_end; + u32 sds_fixed_rom_addr_start; + u32 sds_fixed_rom_addr_end; + u32 sds_start_addr_start; + u32 sds_start_addr_end; + u32 sds_iosf_data_addr_start; + u32 sds_iosf_data_addr_end; + u32 ecl_addr_start; + u32 ecl_addr_end; + u32 smem_addr_start; + u32 smem_addr_end; +}; + +struct btintel_pcie_mbox { + u32 mbox_flags; + u32 mbox_status; + u32 mbox1; + u32 mbox2; + u32 mbox3; + u32 mbox4; +}; + struct btintel_pcie_dump_header { const char *driver_name; u32 cnvi_top; @@ -431,16 +526,153 @@ struct btintel_pcie_dump_header { u16 fw_timestamp; u8 fw_build_type; u32 fw_build_num; - u32 fw_git_sha1; + u32 fw_sha; u32 cnvi_bt; u32 write_ptr; u32 wrap_ctr; u16 trigger_reason; int state; - u8 event_type; - u16 event_id; }; +/* Per-fragment range descriptor for dump regions. + * Binary-compatible with iwl_fw_ini_error_dump_range. + */ +struct btintel_pcie_dump_range { + __le32 range_data_size; + union { + __le32 internal_base_addr; + __le64 dram_base_addr; + __le32 page_num; + }; + __le32 data[]; +} __packed; + +/* + * INI region types for ini_dump_data.type field. + * The unified decoder dispatches parsing logic based on these values. + */ +#define BTINTEL_PCIE_INI_REGION_INTERNAL_BUFFER 2 +#define BTINTEL_PCIE_INI_REGION_DRAM_BUFFER 3 +#define BTINTEL_PCIE_INI_REGION_DEVICE_MEMORY 9 + +/* INI region IDs - used in dump header region_id field and regions_mask */ +#define BTINTEL_PCIE_INI_ID_EXCEPTION_EVT 7 +#define BTINTEL_PCIE_INI_ID_SMEM 15 +#define BTINTEL_PCIE_INI_ID_DCCM 39 +#define BTINTEL_PCIE_INI_ID_SDS 40 +#define BTINTEL_PCIE_INI_ID_SDS_IOSF 41 +#define BTINTEL_PCIE_INI_ID_ECL 42 +#define BTINTEL_PCIE_INI_ID_DRAM_MONITOR3 61 +#define BTINTEL_PCIE_INI_ID_DRAM_MONITOR2 62 +#define BTINTEL_PCIE_INI_ID_DRAM_MONITOR1 63 + +/* + * INI (Intel INI debug infrastructure) style dump data TLV - wraps each dump + * region. INI is the iwlwifi firmware debug format used by the unified decoder. + * Compatible with iwl_fw_ini_error_dump_data. + */ +struct btintel_pcie_ini_dump_data { + u8 type; + u8 sub_type; + u8 sub_type_ver; + u8 reserved; + __le32 len; + u8 data[]; +} __packed; + +/* + * INI-style region dump header. + * Compatible with iwl_fw_ini_error_dump_header. + */ +#define BTINTEL_PCIE_INI_MAX_NAME 32 +#define BTINTEL_PCIE_INI_DUMP_VER 1 + +struct btintel_pcie_ini_dump_header { + __le32 version; + __le32 region_id; + __le32 num_of_ranges; + __le32 name_len; + u8 name[BTINTEL_PCIE_INI_MAX_NAME]; +}; + +/* + * INI-style monitor dump - region header + monitor state. + * Compatible with iwl_fw_ini_monitor_dump. + */ +struct btintel_pcie_ini_monitor_dump { + struct btintel_pcie_ini_dump_header header; + __le32 write_ptr; + __le32 cycle_cnt; + __le32 cur_frag; + u8 data[]; +} __packed; + +/* Linked list entry for modular dump collection */ +struct btintel_pcie_dump_entry { + struct list_head list; + u32 size; + u8 data[]; +}; + +/* File-level header for coredump output. + * Compatible with iwl_fw_ini_dump_file_hdr. + * Uses IWL_FW_INI_ERROR_DUMP_BARKER (0x14789633) for decoder compatibility. + */ +#define BTINTEL_PCIE_INI_ERROR_DUMP_BARKER 0x14789633 + +struct btintel_pcie_dump_file_hdr { + __le32 barker; + __le32 file_len; +} __packed; + +/* + * Legacy-style dump data wrapper for dump info TLV. + * Compatible with iwl_fw_error_dump_data. + * Used only for the dump info entry (type=BTINTEL_PCIE_INI_DUMP_INFO_TYPE). + */ +struct btintel_pcie_error_dump_data { + __le32 type; + __le32 len; + u8 data[]; +} __packed; + +/* Use bit 31 as dump info type, matching IWL_INI_DUMP_INFO_TYPE */ +#define BTINTEL_PCIE_INI_DUMP_INFO_TYPE BIT(31) + +/* Time point values matching iwl_fw_ini_time_point for fwdump parser */ +#define BTINTEL_PCIE_TIME_POINT_FW_ASSERT 4 +#define BTINTEL_PCIE_TIME_POINT_USER_TRIGGER 9 + +/* + * Dump info struct - single TLV containing all metadata. + * Compatible with iwl_fw_ini_dump_info. + * Packs all device/firmware info that the decoder needs. + */ +struct btintel_pcie_ini_dump_info { + __le32 version; + __le32 time_point; + __le32 trigger_reason; + __le32 external_cfg_state; + __le32 ver_type; + __le32 ver_subtype; + __le32 hw_step; + __le32 hw_type; + __le32 rf_id_flavor; + __le32 rf_id_dash; + __le32 rf_id_step; + __le32 rf_id_type; + __le32 lmac_major; + __le32 lmac_minor; + __le32 umac_major; + __le32 umac_minor; + __le32 fw_mon_mode; + __le64 regions_mask; + __le32 build_tag_len; + u8 build_tag[64]; + __le32 num_of_cfg_names; + /* no cfg_names for BT - keep zero-length */ +} __packed; + /* struct btintel_pcie_data * @pdev: pci device * @hdev: hdev device @@ -456,6 +688,7 @@ struct btintel_pcie_dump_header { * @hw_init_mask: initial unmaksed hw causes * @boot_stage_cache: cached value of boot stage register * @img_resp_cache: cached value of image response register + * @dbg_path_cache: cached debug output routing mode (BT DRAM or WiFi DBGC) * @cnvi: CNVi register value * @cnvr: CNVr register value * @gp0_received: condition for gp0 interrupt @@ -503,6 +736,7 @@ struct btintel_pcie_data { u32 boot_stage_cache; u32 img_resp_cache; + u32 dbg_path_cache; u32 cnvi; u32 cnvr; @@ -522,6 +756,7 @@ struct btintel_pcie_data { struct work_struct coredump_work; struct work_struct hwexp_work; struct work_struct fwtrigger_work; + struct work_struct mbox_work; struct dma_pool *dma_pool; dma_addr_t dma_p_addr; @@ -535,10 +770,20 @@ struct btintel_pcie_data { u32 alive_intr_ctxt; enum btintel_pcie_reset_type reset_type; struct btintel_pcie_dbgc dbgc; + struct btintel_pcie_mdbgc mdbgc; struct btintel_pcie_dump_header dmp_hdr; u8 pm_sx_event; u32 debug_evt_addr; u32 debug_evt_size; + dma_addr_t debug_table_addr; + u32 debug_table_size; + struct btintel_pcie_dump_mem_info dump_info; + struct btintel_pcie_mbox mbox; + + /* Wait queue for mbox_worker to wait for GP0 alive interrupt */ + wait_queue_head_t mbox_parse_wait_q; + /* Timestamp captured in GP1 handler when mbox interrupt is received */ + ktime_t mbox_intr_ts; }; static inline u32 btintel_pcie_rd_reg32(struct btintel_pcie_data *data, @@ -585,4 +830,3 @@ static inline u32 btintel_pcie_rd_dev_mem(struct btintel_pcie_data *data, btintel_pcie_wr_reg32(data, BTINTEL_PCIE_PRPH_DEV_ADDR_REG, addr); return btintel_pcie_rd_reg32(data, BTINTEL_PCIE_PRPH_DEV_RD_REG); } - diff --git a/drivers/bluetooth/btmrvl_main.c b/drivers/bluetooth/btmrvl_main.c index e25930351f64..d449fbdde464 100644 --- a/drivers/bluetooth/btmrvl_main.c +++ b/drivers/bluetooth/btmrvl_main.c @@ -87,8 +87,7 @@ int btmrvl_process_event(struct btmrvl_private *priv, struct sk_buff *skb) event = (struct btmrvl_event *) skb->data; if (event->ec != 0xff) { BT_DBG("Not Marvell Event=%x", event->ec); - ret = -EINVAL; - goto exit; + return -EINVAL; } switch (event->data[0]) { @@ -165,7 +164,6 @@ int btmrvl_process_event(struct btmrvl_private *priv, struct sk_buff *skb) break; } -exit: if (!ret) kfree_skb(skb); diff --git a/drivers/bluetooth/btmtk.c b/drivers/bluetooth/btmtk.c index c0ed51567ed4..911aba5c134e 100644 --- a/drivers/bluetooth/btmtk.c +++ b/drivers/bluetooth/btmtk.c @@ -1374,16 +1374,6 @@ int btmtk_usb_setup(struct hci_dev *hdev) break; case 0x7922: case 0x7925: - /* - * A remote wakeup could cause the device completely unresponsive, and - * recovering from such a state needs a power cycle. - * - * Since the remote wakeup capability is super broken, just disable it - * to get rid of the troubles. The device can still be autosuspended - * when the bluetooth interface is closed. - */ - device_set_wakeup_capable(&btmtk_data->udev->dev, false); - fallthrough; case 0x7961: case 0x7902: case 0x6639: @@ -1418,6 +1408,10 @@ int btmtk_usb_setup(struct hci_dev *hdev) err = btmtk_usb_hci_wmt_sync(hdev, &wmt_params); if (err < 0) { bt_dev_err(hdev, "Failed to send wmt func ctrl (%d)", err); + + if (dev_id == 0x7925 && err == -ETIMEDOUT) + btmtk_reset_sync(hdev); + return err; } @@ -1587,5 +1581,6 @@ MODULE_FIRMWARE(FIRMWARE_MT7663); MODULE_FIRMWARE(FIRMWARE_MT7668); MODULE_FIRMWARE(FIRMWARE_MT7922); MODULE_FIRMWARE(FIRMWARE_MT7961); +MODULE_FIRMWARE(FIRMWARE_MT7920); MODULE_FIRMWARE(FIRMWARE_MT7925); MODULE_FIRMWARE(FIRMWARE_MT7927); diff --git a/drivers/bluetooth/btmtk.h b/drivers/bluetooth/btmtk.h index c83c24897c95..bc26148ec544 100644 --- a/drivers/bluetooth/btmtk.h +++ b/drivers/bluetooth/btmtk.h @@ -7,6 +7,7 @@ #define FIRMWARE_MT7922 "mediatek/BT_RAM_CODE_MT7922_1_1_hdr.bin" #define FIRMWARE_MT7902 "mediatek/BT_RAM_CODE_MT7902_1_1_hdr.bin" #define FIRMWARE_MT7961 "mediatek/BT_RAM_CODE_MT7961_1_2_hdr.bin" +#define FIRMWARE_MT7920 "mediatek/BT_RAM_CODE_MT7961_1a_2_hdr.bin" #define FIRMWARE_MT7925 "mediatek/mt7925/BT_RAM_CODE_MT7925_1_1_hdr.bin" #define FIRMWARE_MT7927 "mediatek/mt7927/BT_RAM_CODE_MT6639_2_1_hdr.bin" diff --git a/drivers/bluetooth/btmtksdio.c b/drivers/bluetooth/btmtksdio.c index 94aa60d9cc20..fe4ca9395aa3 100644 --- a/drivers/bluetooth/btmtksdio.c +++ b/drivers/bluetooth/btmtksdio.c @@ -761,8 +761,16 @@ static int btmtksdio_close(struct hci_dev *hdev) sdio_release_irq(bdev->func); + /* No new work can be scheduled after sdio_release_irq(), so cancel the + * work outside the sdio host lock. btmtksdio_txrx_work() also claims + * the host, so canceling it while holding the lock would deadlock. + */ + sdio_release_host(bdev->func); + cancel_work_sync(&bdev->txrx_work); + sdio_claim_host(bdev->func); + btmtksdio_fw_pmctrl(bdev); clear_bit(BTMTKSDIO_FUNC_ENABLED, &bdev->tx_state); @@ -891,14 +899,14 @@ ignore_func_on: return 0; } -static int mt79xx_setup(struct hci_dev *hdev, const char *fwname) +static int mt79xx_setup(struct hci_dev *hdev, const char *fwname, u32 dev_id) { struct btmtksdio_dev *bdev = hci_get_drvdata(hdev); struct btmtk_hci_wmt_params wmt_params; u8 param = 0x1; int err; - err = btmtk_setup_firmware_79xx(hdev, fwname, mtk_hci_wmt_sync, 0); + err = btmtk_setup_firmware_79xx(hdev, fwname, mtk_hci_wmt_sync, dev_id); if (err < 0) { bt_dev_err(hdev, "Failed to setup 79xx firmware (%d)", err); return err; @@ -1111,8 +1119,8 @@ static int btmtksdio_setup(struct hci_dev *hdev) ktime_t calltime, delta, rettime; unsigned long long duration; char fwname[64]; - int err, dev_id; - u32 fw_version = 0, val; + int err; + u32 dev_id, fw_version = 0, val; calltime = ktime_get(); set_bit(BTMTKSDIO_HW_TX_READY, &bdev->tx_state); @@ -1154,10 +1162,7 @@ static int btmtksdio_setup(struct hci_dev *hdev) btmtk_fw_get_filename(fwname, sizeof(fwname), dev_id, fw_version, 0); - snprintf(fwname, sizeof(fwname), - "mediatek/BT_RAM_CODE_MT%04x_1_%x_hdr.bin", - dev_id & 0xffff, (fw_version & 0xff) + 1); - err = mt79xx_setup(hdev, fwname); + err = mt79xx_setup(hdev, fwname, dev_id); if (err < 0) return err; @@ -1308,8 +1313,22 @@ static void btmtksdio_reset(struct hci_dev *hdev) sdio_writel(bdev->func, C_INT_EN_CLR, MTK_REG_CHLPCR, NULL); skb_queue_purge(&bdev->txq); + + /* Unregister the IRQ before releasing the host lock so that a + * concurrently running btmtksdio_txrx_work() cannot re-enable the + * device interrupt (C_INT_EN_SET) and be rescheduled while the device + * is being reset. btmtksdio_txrx_work() also claims the host, so the + * work must be cancelled outside the sdio host lock to avoid a + * deadlock. The IRQ is re-claimed by btmtksdio_open() when the HCI + * device is re-opened after the reset. + */ + sdio_release_irq(bdev->func); + sdio_release_host(bdev->func); + cancel_work_sync(&bdev->txrx_work); + sdio_claim_host(bdev->func); + gpiod_set_value_cansleep(bdev->reset, 1); msleep(100); gpiod_set_value_cansleep(bdev->reset, 0); diff --git a/drivers/bluetooth/btqcomsmd.c b/drivers/bluetooth/btqcomsmd.c index d2e13fcb6bab..d669ea4eb3eb 100644 --- a/drivers/bluetooth/btqcomsmd.c +++ b/drivers/bluetooth/btqcomsmd.c @@ -188,7 +188,10 @@ static int btqcomsmd_probe(struct platform_device *pdev) return 0; hci_free_dev: + rpmsg_destroy_ept(btq->cmd_channel); + rpmsg_destroy_ept(btq->acl_channel); hci_free_dev(hdev); + return ret; destroy_cmd_channel: rpmsg_destroy_ept(btq->cmd_channel); destroy_acl_channel: @@ -202,10 +205,11 @@ static void btqcomsmd_remove(struct platform_device *pdev) struct btqcomsmd *btq = platform_get_drvdata(pdev); hci_unregister_dev(btq->hdev); - hci_free_dev(btq->hdev); rpmsg_destroy_ept(btq->cmd_channel); rpmsg_destroy_ept(btq->acl_channel); + + hci_free_dev(btq->hdev); } static const struct of_device_id btqcomsmd_of_match[] = { diff --git a/drivers/bluetooth/btrtl.c b/drivers/bluetooth/btrtl.c index 03fa9409e3ee..d29813331603 100644 --- a/drivers/bluetooth/btrtl.c +++ b/drivers/bluetooth/btrtl.c @@ -591,7 +591,7 @@ static int rtlbt_parse_firmware_v2(struct hci_dev *hdev, * headers. */ if (!key_id) - break; + continue; rc = btrtl_parse_section(hdev, btrtl_dev, opcode, ptr, section_len); break; @@ -600,8 +600,7 @@ static int rtlbt_parse_firmware_v2(struct hci_dev *hdev, ptr, section_len); break; default: - rc = 0; - break; + continue; } if (rc < 0) { rtl_dev_err(hdev, "RTL: Parse section (%u) err %d", diff --git a/drivers/bluetooth/btusb.c b/drivers/bluetooth/btusb.c index d70a3e7a13f5..61c18402911a 100644 --- a/drivers/bluetooth/btusb.c +++ b/drivers/bluetooth/btusb.c @@ -6,6 +6,7 @@ * Copyright (C) 2005-2008 Marcel Holtmann <marcel@holtmann.org> */ +#include <linux/cpufeature.h> #include <linux/dmi.h> #include <linux/module.h> #include <linux/usb.h> @@ -27,8 +28,9 @@ #include "btbcm.h" #include "btrtl.h" #include "btmtk.h" +#include "hci_uart.h" -#define VERSION "0.8" +#define VERSION "1.0" static bool disable_scofix; static bool force_scofix; @@ -487,6 +489,7 @@ static const struct usb_device_id quirks_table[] = { { USB_DEVICE(0x8087, 0x0037), .driver_info = BTUSB_INTEL_COMBINED }, { USB_DEVICE(0x8087, 0x0038), .driver_info = BTUSB_INTEL_COMBINED }, { USB_DEVICE(0x8087, 0x0039), .driver_info = BTUSB_INTEL_COMBINED }, + { USB_DEVICE(0x8087, 0x0043), .driver_info = BTUSB_INTEL_COMBINED }, /* Otter Peak 2 */ { USB_DEVICE(0x8087, 0x0040), .driver_info = BTUSB_INTEL_COMBINED }, /* Lizard Peak 2 */ { USB_DEVICE(0x8087, 0x07da), .driver_info = BTUSB_CSR }, { USB_DEVICE(0x8087, 0x07dc), .driver_info = BTUSB_INTEL_COMBINED | @@ -510,6 +513,10 @@ static const struct usb_device_id quirks_table[] = { BTUSB_WIDEBAND_SPEECH }, { USB_DEVICE(0x13d3, 0x3533), .driver_info = BTUSB_REALTEK | BTUSB_WIDEBAND_SPEECH }, + { USB_DEVICE(0x13d3, 0x3556), .driver_info = BTUSB_REALTEK | + BTUSB_WIDEBAND_SPEECH }, + { USB_DEVICE(0x13d3, 0x3558), .driver_info = BTUSB_REALTEK | + BTUSB_WIDEBAND_SPEECH }, /* Realtek 8822CE Bluetooth devices */ { USB_DEVICE(0x0bda, 0xb00c), .driver_info = BTUSB_REALTEK | @@ -980,6 +987,11 @@ struct btqca_data { #define BTUSB_USE_ALT3_FOR_WBS 15 #define BTUSB_ALT6_CONTINUOUS_TX 16 #define BTUSB_HW_SSR_ACTIVE 17 +#define BTUSB_WAKEUP_BROKEN 18 +#define BTUSB_RESET 19 + +#define BTUSB_PROTO_LEGACY 0x00 +#define BTUSB_PROTO_H4 0x01 struct btusb_data { struct hci_dev *hdev; @@ -1014,6 +1026,7 @@ struct btusb_data { struct sk_buff *evt_skb; struct sk_buff *acl_skb; struct sk_buff *sco_skb; + struct sk_buff *rx_skb; struct usb_endpoint_descriptor *intr_ep; struct usb_endpoint_descriptor *bulk_tx_ep; @@ -1027,6 +1040,7 @@ struct btusb_data { __u8 cmdreq_type; __u8 cmdreq; + __u8 proto; unsigned int sco_num; unsigned int air_mode; @@ -1054,13 +1068,15 @@ static void btusb_reset(struct hci_dev *hdev) int err; data = hci_get_drvdata(hdev); - /* This is not an unbalanced PM reference since the device will reset */ err = usb_autopm_get_interface(data->intf); if (err) { bt_dev_err(hdev, "Failed usb_autopm_get_interface: %d", err); return; } + if (test_and_set_bit(BTUSB_RESET, &data->flags)) + usb_autopm_put_interface_no_suspend(data->intf); + bt_dev_err(hdev, "Resetting usb device."); usb_queue_reset_device(data->intf); } @@ -1254,6 +1270,11 @@ static inline void btusb_free_frags(struct btusb_data *data) dev_kfree_skb_irq(data->sco_skb); data->sco_skb = NULL; + /* rx_skb may hold an ERR_PTR from a previous h4_recv_skb() call */ + if (!IS_ERR(data->rx_skb)) + dev_kfree_skb_irq(data->rx_skb); + data->rx_skb = NULL; + spin_unlock_irqrestore(&data->rxlock, flags); } @@ -1353,12 +1374,41 @@ static int btusb_recv_acl(struct hci_dev *hdev, struct sk_buff *skb) return 0; } +/* Dispatch through the btusb_recv_* wrappers so that vendor specific + * handling (data->recv_event, data->recv_acl) is preserved in H:4 mode. + */ +static const struct h4_recv_pkt btusb_recv_pkts[] = { + { H4_RECV_ACL, .recv = btusb_recv_acl }, + { H4_RECV_SCO, .recv = hci_recv_frame }, + { H4_RECV_EVENT, .recv = btusb_recv_event }, + { H4_RECV_ISO, .recv = hci_recv_frame }, +}; + +static int btusb_recv_h4(struct btusb_data *data, void *buffer, int count) +{ + unsigned long flags; + int err = 0; + + spin_lock_irqsave(&data->rxlock, flags); + data->rx_skb = h4_recv_skb(data->hdev, NULL, NULL, data->rx_skb, buffer, + count, btusb_recv_pkts, + ARRAY_SIZE(btusb_recv_pkts)); + if (IS_ERR(data->rx_skb)) + err = PTR_ERR(data->rx_skb); + spin_unlock_irqrestore(&data->rxlock, flags); + + return err; +} + static int btusb_recv_bulk(struct btusb_data *data, void *buffer, int count) { struct sk_buff *skb; unsigned long flags; int err = 0; + if (data->proto == BTUSB_PROTO_H4) + return btusb_recv_h4(data, buffer, count); + spin_lock_irqsave(&data->rxlock, flags); skb = data->acl_skb; @@ -2036,12 +2086,14 @@ static int btusb_open(struct hci_dev *hdev) data->intf->needs_remote_wakeup = 1; - if (test_and_set_bit(BTUSB_INTR_RUNNING, &data->flags)) - goto done; + if (data->proto == BTUSB_PROTO_LEGACY) { + if (test_and_set_bit(BTUSB_INTR_RUNNING, &data->flags)) + goto done; - err = btusb_submit_intr_urb(hdev, GFP_KERNEL); - if (err < 0) - goto failed; + err = btusb_submit_intr_urb(hdev, GFP_KERNEL); + if (err < 0) + goto failed; + } err = btusb_submit_bulk_urb(hdev, GFP_KERNEL); if (err < 0) { @@ -2092,18 +2144,24 @@ static int btusb_close(struct hci_dev *hdev) BT_DBG("%s", hdev->name); - cancel_delayed_work(&data->rx_work); cancel_work_sync(&data->work); cancel_work_sync(&data->waker); - skb_queue_purge(&data->acl_q); - clear_bit(BTUSB_ISOC_RUNNING, &data->flags); clear_bit(BTUSB_BULK_RUNNING, &data->flags); clear_bit(BTUSB_INTR_RUNNING, &data->flags); clear_bit(BTUSB_DIAG_RUNNING, &data->flags); btusb_stop_traffic(data); + + /* rx_work must only be canceled once the URBs that can rearm it are + * gone, and it must be canceled synchronously since btusb_disconnect() + * frees the btusb_data it dereferences right after hci_unregister_dev(). + */ + cancel_delayed_work_sync(&data->rx_work); + + skb_queue_purge(&data->acl_q); + btusb_free_frags(data); err = usb_autopm_get_interface(data->intf); @@ -2129,7 +2187,7 @@ static int btusb_flush(struct hci_dev *hdev) BT_DBG("%s", hdev->name); - cancel_delayed_work(&data->rx_work); + cancel_delayed_work_sync(&data->rx_work); skb_queue_purge(&data->acl_q); @@ -2139,56 +2197,72 @@ static int btusb_flush(struct hci_dev *hdev) return 0; } -static struct urb *alloc_ctrl_urb(struct hci_dev *hdev, struct sk_buff *skb) +static struct urb *alloc_bulk_urb(struct hci_dev *hdev, struct sk_buff *skb) { struct btusb_data *data = hci_get_drvdata(hdev); - struct usb_ctrlrequest *dr; struct urb *urb; unsigned int pipe; + if (!data->bulk_tx_ep) + return ERR_PTR(-ENODEV); + + if (data->proto == BTUSB_PROTO_H4) { + /* The frame type is prepended in place, so the buffer must not + * be shared with anyone else. + */ + if (skb_cow_head(skb, 1)) + return ERR_PTR(-ENOMEM); + } + urb = usb_alloc_urb(0, GFP_KERNEL); if (!urb) return ERR_PTR(-ENOMEM); - dr = kmalloc_obj(*dr); - if (!dr) { - usb_free_urb(urb); - return ERR_PTR(-ENOMEM); - } - - dr->bRequestType = data->cmdreq_type; - dr->bRequest = data->cmdreq; - dr->wIndex = 0; - dr->wValue = 0; - dr->wLength = __cpu_to_le16(skb->len); + pipe = usb_sndbulkpipe(data->udev, data->bulk_tx_ep->bEndpointAddress); - pipe = usb_sndctrlpipe(data->udev, 0x00); + if (data->proto == BTUSB_PROTO_H4) { + /* Prepend skb with frame type */ + memcpy(skb_push(skb, 1), &hci_skb_pkt_type(skb), 1); + } - usb_fill_control_urb(urb, data->udev, pipe, (void *)dr, - skb->data, skb->len, btusb_tx_complete, skb); + usb_fill_bulk_urb(urb, data->udev, pipe, + skb->data, skb->len, btusb_tx_complete, skb); skb->dev = (void *)hdev; return urb; } -static struct urb *alloc_bulk_urb(struct hci_dev *hdev, struct sk_buff *skb) +static struct urb *alloc_ctrl_urb(struct hci_dev *hdev, struct sk_buff *skb) { struct btusb_data *data = hci_get_drvdata(hdev); + struct usb_ctrlrequest *dr; struct urb *urb; unsigned int pipe; - if (!data->bulk_tx_ep) - return ERR_PTR(-ENODEV); + if (data->proto == BTUSB_PROTO_H4) + return alloc_bulk_urb(hdev, skb); urb = usb_alloc_urb(0, GFP_KERNEL); if (!urb) return ERR_PTR(-ENOMEM); - pipe = usb_sndbulkpipe(data->udev, data->bulk_tx_ep->bEndpointAddress); + dr = kmalloc_obj(*dr); + if (!dr) { + usb_free_urb(urb); + return ERR_PTR(-ENOMEM); + } - usb_fill_bulk_urb(urb, data->udev, pipe, - skb->data, skb->len, btusb_tx_complete, skb); + dr->bRequestType = data->cmdreq_type; + dr->bRequest = data->cmdreq; + dr->wIndex = 0; + dr->wValue = 0; + dr->wLength = __cpu_to_le16(skb->len); + + pipe = usb_sndctrlpipe(data->udev, 0x00); + + usb_fill_control_urb(urb, data->udev, pipe, (void *)dr, + skb->data, skb->len, btusb_tx_complete, skb); skb->dev = (void *)hdev; @@ -2201,6 +2275,9 @@ static struct urb *alloc_isoc_urb(struct hci_dev *hdev, struct sk_buff *skb) struct urb *urb; unsigned int pipe; + if (data->proto == BTUSB_PROTO_H4) + return alloc_bulk_urb(hdev, skb); + if (!data->isoc_tx_ep) return ERR_PTR(-ENODEV); @@ -2272,6 +2349,22 @@ static int submit_or_queue_tx_urb(struct hci_dev *hdev, struct urb *urb) return 0; } +static int submit_sco_urb(struct hci_dev *hdev, struct urb *urb) +{ + struct btusb_data *data = hci_get_drvdata(hdev); + + /* In H:4 mode SCO frames are carried over the bulk endpoint and + * complete via btusb_tx_complete(), which decrements tx_in_flight, so + * they have to be accounted for like any other bulk transfer. + * Isochronous transfers use btusb_isoc_tx_complete() instead, which + * does not, so they must not be counted. + */ + if (data->proto == BTUSB_PROTO_H4) + return submit_or_queue_tx_urb(hdev, urb); + + return submit_tx_urb(hdev, urb); +} + static int btusb_send_frame(struct hci_dev *hdev, struct sk_buff *skb) { struct urb *urb; @@ -2305,7 +2398,7 @@ static int btusb_send_frame(struct hci_dev *hdev, struct sk_buff *skb) return PTR_ERR(urb); hdev->stat.sco_tx++; - return submit_tx_urb(hdev, urb); + return submit_sco_urb(hdev, urb); case HCI_ISODATA_PKT: urb = alloc_bulk_urb(hdev, skb); @@ -2414,10 +2507,9 @@ static int btusb_switch_alt_setting(struct hci_dev *hdev, int new_alts) return 0; } -static struct usb_host_interface *btusb_find_altsetting(struct btusb_data *data, - int alt) +static struct usb_host_interface * +btusb_find_altsetting(struct usb_interface *intf, int alt) { - struct usb_interface *intf = data->isoc; int i; BT_DBG("Looking for Alt no :%d", alt); @@ -2440,6 +2532,13 @@ static void btusb_work(struct work_struct *work) int new_alts = 0; int err; + /* In H:4 mode SCO/ISO data is carried over the bulk endpoints, so + * there is no isochronous interface to resume or to switch alternate + * settings on. + */ + if (data->proto == BTUSB_PROTO_H4) + return; + if (data->sco_num > 0) { if (!test_bit(BTUSB_DID_ISO_RESUME, &data->flags)) { err = usb_autopm_get_interface(data->isoc ? data->isoc : data->intf); @@ -2473,9 +2572,9 @@ static void btusb_work(struct work_struct *work) * MTU >= 3 (packets) * 25 (size) - 3 (headers) = 72 * see also Core spec 5, vol 4, B 2.1.1 & Table 2.1. */ - if (btusb_find_altsetting(data, 6)) + if (btusb_find_altsetting(data->isoc, 6)) new_alts = 6; - else if (btusb_find_altsetting(data, 3) && + else if (btusb_find_altsetting(data->isoc, 3) && hdev->sco_mtu >= 72 && test_bit(BTUSB_USE_ALT3_FOR_WBS, &data->flags)) new_alts = 3; @@ -2724,8 +2823,13 @@ static int btusb_recv_bulk_intel(struct btusb_data *data, void *buffer, /* When the device is in bootloader mode, then it can send * events via the bulk endpoint. These events are treated the * same way as the ones received from the interrupt endpoint. + * + * In H:4 mode there is no interrupt endpoint and every frame on the + * bulk endpoint carries an H:4 header, including the ones sent by the + * bootloader, so the regular decoding applies. */ - if (btintel_test_flag(hdev, INTEL_BOOTLOADER)) + if (data->proto == BTUSB_PROTO_LEGACY && + btintel_test_flag(hdev, INTEL_BOOTLOADER)) return btusb_recv_intr(data, buffer, count); return btusb_recv_bulk(data, buffer, count); @@ -2786,7 +2890,7 @@ static int btusb_send_frame_intel(struct hci_dev *hdev, struct sk_buff *skb) return PTR_ERR(urb); hdev->stat.sco_tx++; - return submit_tx_urb(hdev, urb); + return submit_sco_urb(hdev, urb); case HCI_ISODATA_PKT: urb = alloc_bulk_urb(hdev, skb); @@ -2923,8 +3027,11 @@ static int btusb_mtk_reset(struct hci_dev *hdev, void *rst_data) } err = usb_autopm_get_interface(data->intf); - if (err < 0) + if (err < 0) { + bt_dev_err(hdev, "Failed usb_autopm_get_interface: %d", err); + clear_bit(BTMTK_HW_RESET_ACTIVE, &btmtk_data->flags); return err; + } /* Release MediaTek ISO data interface */ btusb_mtk_release_iso_intf(hdev); @@ -2946,6 +3053,11 @@ static int btusb_mtk_reset(struct hci_dev *hdev, void *rst_data) err = btmtk_usb_subsys_reset(hdev, btmtk_data->dev_id); + if (test_and_set_bit(BTUSB_RESET, &data->flags)) { + bt_dev_err(hdev, "last usb reset failed? Resetting again"); + usb_autopm_put_interface_no_suspend(data->intf); + } + usb_queue_reset_device(data->intf); clear_bit(BTMTK_HW_RESET_ACTIVE, &btmtk_data->flags); @@ -2969,10 +3081,25 @@ static int btusb_send_frame_mtk(struct hci_dev *hdev, struct sk_buff *skb) } } +static inline bool platform_is_ryzen(void) +{ +#ifdef CONFIG_X86 + return boot_cpu_has(X86_FEATURE_ZEN); +#else + return false; +#endif +} + +static inline bool is_direct_child_of_root_hub(struct usb_device *udev) +{ + return udev->parent == udev->bus->root_hub; +} + static int btusb_mtk_setup(struct hci_dev *hdev) { struct btusb_data *data = hci_get_drvdata(hdev); struct btmtk_data *btmtk_data = hci_get_priv(hdev); + int err; /* MediaTek WMT vendor cmd requiring below USB resources to * complete the handshake. @@ -2989,7 +3116,40 @@ static int btusb_mtk_setup(struct hci_dev *hdev) btusb_mtk_claim_iso_intf(data); } - return btmtk_usb_setup(hdev); + err = btmtk_usb_setup(hdev); + if (err) + return err; + + switch (btmtk_data->dev_id) { + case 0x7922: + case 0x7925: + /* + * All reports seen to be relevant to Ryzen-based laptops. These + * NICs are usually used as OEM components thanks to some sort + * of reference designs. + * + * Their popularity on other platforms is unclear. While there + * is still a chance that the quirk may exist on other + * platforms, be cautious and only apply the quirk to direct + * children of Ryzen platforms's root hubs for the time being. + * + * In most cases the root hub is on the SoC or PCH, which needs + * the quirk. Unfortunately, this can't distinguish root hubs on + * PCIe add-in cards. Such roughness should be acceptable, as + * PCIe USB controller add-in cards are less commonly used + * nowadays. On the other hand, applying the quirk doesn't hurt + * any functionalities either, as the device can still be used + * as a wakeup source if desired. + * + * Theoretically, we could retrieve the root hub's PCI vendor ID + * with some hierarchy magic, but that's too intrusive... + */ + if (platform_is_ryzen() && is_direct_child_of_root_hub(data->udev)) + set_bit(BTUSB_WAKEUP_BROKEN, &data->flags); + break; + } + + return 0; } static int btusb_mtk_shutdown(struct hci_dev *hdev) @@ -3949,8 +4109,11 @@ static ssize_t force_poll_sync_write(struct file *file, if (err) return err; - /* Only allow changes while the adapter is down */ - if (test_bit(HCI_UP, &data->hdev->flags)) + /* Only allow changes while the adapter is down and it is using legacy + * protocol. + */ + if (test_bit(HCI_UP, &data->hdev->flags) || + data->proto != BTUSB_PROTO_LEGACY) return -EPERM; if (data->poll_sync == enable) @@ -4049,7 +4212,7 @@ static int btusb_hci_drv_supported_altsettings(struct hci_dev *hdev, void *data, goto done; for (i = 0; i <= 6; i++) { - if (btusb_find_altsetting(drvdata, i)) + if (btusb_find_altsetting(drvdata->isoc, i)) rp->altsettings[rp->num++] = i; } @@ -4103,6 +4266,8 @@ static int btusb_probe(struct usb_interface *intf, const struct usb_device_id *id) { struct gpio_desc *reset_gpio; + struct usb_host_interface *alt; + struct usb_endpoint_descriptor *bulk_rx_ep, *bulk_tx_ep, *intr_ep; struct btusb_data *data; struct hci_dev *hdev; unsigned ifnum_base; @@ -4144,10 +4309,38 @@ static int btusb_probe(struct usb_interface *intf, return -ENOMEM; data->match_id = id; + + /* Alternate setting 1 with a single pair of bulk endpoints and no + * interrupt endpoint means the controller supports Bulk Serialization + * Mode, in which every packet is prefixed with an H:4 header and + * carried over the bulk endpoints. + */ + alt = btusb_find_altsetting(intf, 1); + if (alt && usb_find_int_in_endpoint(alt, &intr_ep) && + !usb_find_common_endpoints(alt, &bulk_rx_ep, &bulk_tx_ep, NULL, + NULL)) { + err = usb_set_interface(interface_to_usbdev(intf), ifnum_base, 1); + if (!err) + data->proto = BTUSB_PROTO_H4; + else + dev_warn(&intf->dev, + "failed to select alt setting 1 (%d), using legacy mode", + err); + } + + /* Check if all endpoints could be enumerated, legacy mode requires + * interrupt and bulk endpoints while H4 mode only requires bulk + * endpoints. + */ err = usb_find_common_endpoints(intf->cur_altsetting, &data->bulk_rx_ep, - &data->bulk_tx_ep, &data->intr_ep, NULL); - if (err) + &data->bulk_tx_ep, + data->proto == BTUSB_PROTO_LEGACY ? + &data->intr_ep : NULL, + NULL); + if (err) { + dev_err(&intf->dev, "failed to enumerate endpoints\n"); goto err_free_data; + } if (id->driver_info & BTUSB_AMP) { data->cmdreq_type = USB_TYPE_CLASS | 0x01; @@ -4359,6 +4552,12 @@ static int btusb_probe(struct usb_interface *intf, if (id->driver_info & BTUSB_AMP) { /* AMP controllers do not support SCO packets */ data->isoc = NULL; + } else if (data->proto == BTUSB_PROTO_H4) { + /* In H:4 mode every packet, including SCO/ISO, is carried over + * the bulk endpoints, so the isochronous interface must not be + * claimed nor have its alternate settings switched. + */ + data->isoc = NULL; } else { /* Interface orders are hardcoded in the specification */ data->isoc = usb_ifnum_to_if(data->udev, ifnum_base + 1); @@ -4468,7 +4667,8 @@ static int btusb_probe(struct usb_interface *intf, if (enable_autosuspend) usb_enable_autosuspend(data->udev); - data->poll_sync = enable_poll_sync; + if (data->proto == BTUSB_PROTO_LEGACY) + data->poll_sync = enable_poll_sync; err = hci_register_dev(hdev); if (err < 0) @@ -4540,6 +4740,9 @@ static void btusb_disconnect(struct usb_interface *intf) if (data->reset_gpio) gpiod_put(data->reset_gpio); + if (test_and_clear_bit(BTUSB_RESET, &data->flags)) + usb_autopm_put_interface_no_suspend(data->intf); + if (intf == data->intf) { if (data->isoc) usb_driver_release_interface(&btusb_driver, data->isoc); @@ -4565,11 +4768,26 @@ static int btusb_suspend(struct usb_interface *intf, pm_message_t message) BT_DBG("intf %p", intf); - /* Don't auto-suspend if there are connections or discovery in - * progress; external suspend calls shall never fail. + /* + * It is reported that remote wakeup events could sometimes cause some + * adapters completely unresponsive. Resetting the xHCI root hub doesn't + * help at all, and recovering from such a state needs a power cycle. + * Since disabling remote wakeup simply causes the USB core to gate + * runtime autosuspend as well due to needs_remote_wakeup == 1, let's do + * this ourselves to make our life easier. The interface can be safely + * autosuspended as long as remote wakeup is disabled, i.e., after + * closing the HCI device. + * + * Don't auto-suspend if there are connections or discovery in progress. + * + * External suspend calls shall never fail. Specifically, a device with + * broken remote wakeup may still take the advantage of remote wakeup in + * order to wake up the system from sleep if userspace has enabled it as + * a wakeup source. */ if (PMSG_IS_AUTO(message) && - (hci_conn_count(data->hdev) || hci_discovery_active(data->hdev))) + ((test_bit(BTUSB_WAKEUP_BROKEN, &data->flags) && data->intf->needs_remote_wakeup) || + hci_conn_count(data->hdev) || hci_discovery_active(data->hdev))) return -EBUSY; if (data->suspend_count++) diff --git a/drivers/bluetooth/hci_bcm.c b/drivers/bluetooth/hci_bcm.c index 9a103db7e355..e70b9f3fd968 100644 --- a/drivers/bluetooth/hci_bcm.c +++ b/drivers/bluetooth/hci_bcm.c @@ -1593,6 +1593,7 @@ static const struct of_device_id bcm_bluetooth_of_match[] = { { .compatible = "brcm,bcm4349-bt", .data = &bcm43438_device_data }, { .compatible = "brcm,bcm43540-bt", .data = &bcm4354_device_data }, { .compatible = "brcm,bcm4335a0" }, + { .compatible = "brcm,bcm4384-bt" }, { .compatible = "cypress,cyw4373a0-bt", .data = &cyw4373a0_device_data }, { .compatible = "infineon,cyw55572-bt", .data = &cyw55572_device_data }, { }, diff --git a/drivers/bluetooth/hci_h4.c b/drivers/bluetooth/hci_h4.c index 767372707498..cbdf51458ec3 100644 --- a/drivers/bluetooth/hci_h4.c +++ b/drivers/bluetooth/hci_h4.c @@ -29,6 +29,7 @@ #include <net/bluetooth/bluetooth.h> #include <net/bluetooth/hci_core.h> +#include <net/bluetooth/hci_h4.h> #include "hci_uart.h" @@ -112,8 +113,9 @@ static int h4_recv(struct hci_uart *hu, const void *data, int count) if (!h4) return -ENODEV; - h4->rx_skb = h4_recv_buf(hu, h4->rx_skb, data, count, - h4_recv_pkts, ARRAY_SIZE(h4_recv_pkts)); + h4->rx_skb = h4_recv_skb(hu->hdev, &hu->alignment, &hu->padding, + h4->rx_skb, data, count, h4_recv_pkts, + ARRAY_SIZE(h4_recv_pkts)); if (IS_ERR(h4->rx_skb)) { int err = PTR_ERR(h4->rx_skb); bt_dev_err(hu->hdev, "Frame reassembly failed (%d)", err); @@ -155,120 +157,7 @@ struct sk_buff *h4_recv_buf(struct hci_uart *hu, struct sk_buff *skb, const unsigned char *buffer, int count, const struct h4_recv_pkt *pkts, int pkts_count) { - u8 alignment = hu->alignment ? hu->alignment : 1; - struct hci_dev *hdev = hu->hdev; - - /* Check for error from previous call */ - if (IS_ERR(skb)) - skb = NULL; - - while (count) { - int i, len; - - /* remove padding bytes from buffer */ - for (; hu->padding && count > 0; hu->padding--) { - count--; - buffer++; - } - if (!count) - break; - - if (!skb) { - for (i = 0; i < pkts_count; i++) { - if (buffer[0] != (&pkts[i])->type) - continue; - - skb = bt_skb_alloc((&pkts[i])->maxlen, - GFP_ATOMIC); - if (!skb) - return ERR_PTR(-ENOMEM); - - hci_skb_pkt_type(skb) = (&pkts[i])->type; - hci_skb_expect(skb) = (&pkts[i])->hlen; - break; - } - - /* Check for invalid packet type */ - if (!skb) - return ERR_PTR(-EILSEQ); - - count -= 1; - buffer += 1; - } - - len = min_t(uint, hci_skb_expect(skb) - skb->len, count); - skb_put_data(skb, buffer, len); - - count -= len; - buffer += len; - - /* Check for partial packet */ - if (skb->len < hci_skb_expect(skb)) - continue; - - for (i = 0; i < pkts_count; i++) { - if (hci_skb_pkt_type(skb) == (&pkts[i])->type) - break; - } - - if (i >= pkts_count) { - kfree_skb(skb); - return ERR_PTR(-EILSEQ); - } - - if (skb->len == (&pkts[i])->hlen) { - u16 dlen; - - switch ((&pkts[i])->lsize) { - case 0: - /* No variable data length */ - dlen = 0; - break; - case 1: - /* Single octet variable length */ - dlen = skb->data[(&pkts[i])->loff]; - hci_skb_expect(skb) += dlen; - - if (skb_tailroom(skb) < dlen) { - kfree_skb(skb); - return ERR_PTR(-EMSGSIZE); - } - break; - case 2: - /* Double octet variable length */ - dlen = get_unaligned_le16(skb->data + - (&pkts[i])->loff); - hci_skb_expect(skb) += dlen; - - if (skb_tailroom(skb) < dlen) { - kfree_skb(skb); - return ERR_PTR(-EMSGSIZE); - } - break; - default: - /* Unsupported variable length */ - kfree_skb(skb); - return ERR_PTR(-EILSEQ); - } - - if (!dlen) { - hu->padding = (skb->len + 1) % alignment; - hu->padding = (alignment - hu->padding) % alignment; - - /* No more data, complete frame */ - (&pkts[i])->recv(hdev, skb); - skb = NULL; - } - } else { - hu->padding = (skb->len + 1) % alignment; - hu->padding = (alignment - hu->padding) % alignment; - - /* Complete frame */ - (&pkts[i])->recv(hdev, skb); - skb = NULL; - } - } - - return skb; + return h4_recv_skb(hu->hdev, &hu->alignment, &hu->padding, skb, buffer, + count, pkts, pkts_count); } EXPORT_SYMBOL_GPL(h4_recv_buf); diff --git a/drivers/bluetooth/hci_serdev.c b/drivers/bluetooth/hci_serdev.c index d2eaf2f12aa2..48ba017e219b 100644 --- a/drivers/bluetooth/hci_serdev.c +++ b/drivers/bluetooth/hci_serdev.c @@ -395,20 +395,54 @@ EXPORT_SYMBOL_GPL(hci_uart_register_device_priv); void hci_uart_unregister_device(struct hci_uart *hu) { struct hci_dev *hdev = hu->hdev; + bool proto_ready; + /* Wait for init_ready to finish to prevent registration races */ cancel_work_sync(&hu->init_ready); - if (test_bit(HCI_UART_REGISTERED, &hu->flags)) - hci_unregister_dev(hdev); - hci_free_dev(hdev); + proto_ready = test_bit(HCI_UART_PROTO_READY, &hu->flags); + if (proto_ready) { + /* Clear HCI_UART_PROTO_READY under the write lock so a + * concurrent hci_uart_tx_wakeup() cannot re-schedule + * write_work via the write_wakeup callback once the device + * is torn down. + */ + percpu_down_write(&hu->proto_lock); + clear_bit(HCI_UART_PROTO_READY, &hu->flags); + percpu_up_write(&hu->proto_lock); + } + + /* Unconditionally cancel write_work AFTER clearing PROTO_READY. + * This ensures that concurrent protocol timers cannot requeue + * write_work, permanently preventing double-free races and UAFs, + * and guarantees no write_work is in flight before the serdev + * device is closed. + */ cancel_work_sync(&hu->write_work); + /* Free any partially transmitted frame left over by write_work now + * that the transmit path is fully quiesced. hci_uart_close() would + * skip hci_uart_flush() because HCI_UART_PROTO_READY is cleared. + */ + if (hu->tx_skb) { + kfree_skb(hu->tx_skb); + hu->tx_skb = NULL; + } + + if (test_bit(HCI_UART_REGISTERED, &hu->flags)) + hci_unregister_dev(hdev); + + /* Close the protocol before freeing hdev (intrinsically purges queues). + * Some protocol close handlers (e.g. qca_close) may still access the + * serdev device, so keep the serdev port open until this completes. + */ hu->proto->close(hu); - if (test_bit(HCI_UART_PROTO_READY, &hu->flags)) { - clear_bit(HCI_UART_PROTO_READY, &hu->flags); + if (proto_ready) serdev_device_close(hu->serdev); - } + + hci_free_dev(hdev); + percpu_free_rwsem(&hu->proto_lock); } EXPORT_SYMBOL_GPL(hci_uart_unregister_device); diff --git a/drivers/bluetooth/hci_uart.h b/drivers/bluetooth/hci_uart.h index 48ac7ca9334e..7fbe8dffab98 100644 --- a/drivers/bluetooth/hci_uart.h +++ b/drivers/bluetooth/hci_uart.h @@ -8,6 +8,8 @@ * Copyright (C) 2004-2005 Marcel Holtmann <marcel@holtmann.org> */ +#include <net/bluetooth/hci_h4.h> + #ifndef N_HCI #define N_HCI 15 #endif @@ -121,43 +123,6 @@ void hci_uart_set_flow_control(struct hci_uart *hu, bool enable); void hci_uart_set_speeds(struct hci_uart *hu, unsigned int init_speed, unsigned int oper_speed); -struct h4_recv_pkt { - u8 type; /* Packet type */ - u8 hlen; /* Header length */ - u8 loff; /* Data length offset in header */ - u8 lsize; /* Data length field size */ - u16 maxlen; /* Max overall packet length */ - int (*recv)(struct hci_dev *hdev, struct sk_buff *skb); -}; - -#define H4_RECV_ACL \ - .type = HCI_ACLDATA_PKT, \ - .hlen = HCI_ACL_HDR_SIZE, \ - .loff = 2, \ - .lsize = 2, \ - .maxlen = HCI_MAX_FRAME_SIZE \ - -#define H4_RECV_SCO \ - .type = HCI_SCODATA_PKT, \ - .hlen = HCI_SCO_HDR_SIZE, \ - .loff = 2, \ - .lsize = 1, \ - .maxlen = HCI_MAX_SCO_SIZE - -#define H4_RECV_EVENT \ - .type = HCI_EVENT_PKT, \ - .hlen = HCI_EVENT_HDR_SIZE, \ - .loff = 1, \ - .lsize = 1, \ - .maxlen = HCI_MAX_EVENT_SIZE - -#define H4_RECV_ISO \ - .type = HCI_ISODATA_PKT, \ - .hlen = HCI_ISO_HDR_SIZE, \ - .loff = 2, \ - .lsize = 2, \ - .maxlen = HCI_MAX_FRAME_SIZE \ - #ifdef CONFIG_BT_HCIUART_H4 int h4_init(void); int h4_deinit(void); diff --git a/drivers/bluetooth/virtio_bt.c b/drivers/bluetooth/virtio_bt.c index c20d54088c8c..8c55b538deef 100644 --- a/drivers/bluetooth/virtio_bt.c +++ b/drivers/bluetooth/virtio_bt.c @@ -315,12 +315,12 @@ static int virtbt_probe(struct virtio_device *vdev) err = virtio_find_vqs(vdev, VIRTBT_NUM_VQS, vbt->vqs, vqs_info, NULL); if (err) - return err; + goto err_free_vbt; hdev = hci_alloc_dev(); if (!hdev) { err = -ENOMEM; - goto failed; + goto err_del_vqs; } vbt->hdev = hdev; @@ -390,20 +390,25 @@ static int virtbt_probe(struct virtio_device *vdev) if (hci_register_dev(hdev) < 0) { hci_free_dev(hdev); err = -EBUSY; - goto failed; + goto err_del_vqs; } virtio_device_ready(vdev); err = virtbt_open_vdev(vbt); - if (err) - goto open_failed; + if (err) { + hci_unregister_dev(hdev); + virtio_reset_device(vdev); + virtbt_close_vdev(vbt); + hci_free_dev(hdev); + goto err_del_vqs; + } return 0; -open_failed: - hci_free_dev(hdev); -failed: +err_del_vqs: vdev->config->del_vqs(vdev); +err_free_vbt: + kfree(vbt); return err; } diff --git a/include/net/bluetooth/hci_core.h b/include/net/bluetooth/hci_core.h index 4105c446ca98..c12cd6873f65 100644 --- a/include/net/bluetooth/hci_core.h +++ b/include/net/bluetooth/hci_core.h @@ -1030,6 +1030,9 @@ static inline bool hci_conn_sc_enabled(struct hci_conn *conn) static inline void hci_conn_hash_add(struct hci_dev *hdev, struct hci_conn *c) { struct hci_conn_hash *h = &hdev->conn_hash; + + lockdep_assert_held(&hdev->lock); + list_add_tail_rcu(&c->list, &h->list); switch (c->type) { case ACL_LINK: @@ -1060,6 +1063,8 @@ static inline void hci_conn_hash_del(struct hci_dev *hdev, struct hci_conn *c) { struct hci_conn_hash *h = &hdev->conn_hash; + lockdep_assert_held(&hdev->lock); + list_del_rcu(&c->list); synchronize_rcu(); @@ -1088,6 +1093,15 @@ static inline void hci_conn_hash_del(struct hci_dev *hdev, struct hci_conn *c) } } +#ifdef CONFIG_PROVE_RCU +#define HCI_CONN_HASH_LOCKDEP_CHECK(hdev) \ + RCU_LOCKDEP_WARN(!lockdep_is_held(&(hdev)->lock) && \ + !rcu_read_lock_held(), \ + "suspicious hci_conn locking") +#else +#define HCI_CONN_HASH_LOCKDEP_CHECK(hdev) do { } while (0 && (hdev)) +#endif + static inline unsigned int hci_conn_num(struct hci_dev *hdev, __u8 type) { struct hci_conn_hash *h = &hdev->conn_hash; @@ -1169,6 +1183,8 @@ static inline struct hci_conn *hci_conn_hash_lookup_bis(struct hci_dev *hdev, struct hci_conn_hash *h = &hdev->conn_hash; struct hci_conn *c; + HCI_CONN_HASH_LOCKDEP_CHECK(hdev); + rcu_read_lock(); list_for_each_entry_rcu(c, &h->list, list) { @@ -1191,6 +1207,8 @@ hci_conn_hash_lookup_create_pa_sync(struct hci_dev *hdev) struct hci_conn_hash *h = &hdev->conn_hash; struct hci_conn *c; + HCI_CONN_HASH_LOCKDEP_CHECK(hdev); + rcu_read_lock(); list_for_each_entry_rcu(c, &h->list, list) { @@ -1217,6 +1235,8 @@ hci_conn_hash_lookup_per_adv_bis(struct hci_dev *hdev, struct hci_conn_hash *h = &hdev->conn_hash; struct hci_conn *c; + HCI_CONN_HASH_LOCKDEP_CHECK(hdev); + rcu_read_lock(); list_for_each_entry_rcu(c, &h->list, list) { @@ -1241,6 +1261,8 @@ static inline struct hci_conn *hci_conn_hash_lookup_handle(struct hci_dev *hdev, struct hci_conn_hash *h = &hdev->conn_hash; struct hci_conn *c; + HCI_CONN_HASH_LOCKDEP_CHECK(hdev); + rcu_read_lock(); list_for_each_entry_rcu(c, &h->list, list) { @@ -1260,6 +1282,8 @@ static inline struct hci_conn *hci_conn_hash_lookup_ba(struct hci_dev *hdev, struct hci_conn_hash *h = &hdev->conn_hash; struct hci_conn *c; + HCI_CONN_HASH_LOCKDEP_CHECK(hdev); + rcu_read_lock(); list_for_each_entry_rcu(c, &h->list, list) { @@ -1281,6 +1305,8 @@ static inline struct hci_conn *hci_conn_hash_lookup_role(struct hci_dev *hdev, struct hci_conn_hash *h = &hdev->conn_hash; struct hci_conn *c; + HCI_CONN_HASH_LOCKDEP_CHECK(hdev); + rcu_read_lock(); list_for_each_entry_rcu(c, &h->list, list) { @@ -1302,6 +1328,8 @@ static inline struct hci_conn *hci_conn_hash_lookup_le(struct hci_dev *hdev, struct hci_conn_hash *h = &hdev->conn_hash; struct hci_conn *c; + HCI_CONN_HASH_LOCKDEP_CHECK(hdev); + rcu_read_lock(); list_for_each_entry_rcu(c, &h->list, list) { @@ -1328,6 +1356,8 @@ static inline struct hci_conn *hci_conn_hash_lookup_cis(struct hci_dev *hdev, struct hci_conn_hash *h = &hdev->conn_hash; struct hci_conn *c; + HCI_CONN_HASH_LOCKDEP_CHECK(hdev); + rcu_read_lock(); list_for_each_entry_rcu(c, &h->list, list) { @@ -1360,6 +1390,8 @@ static inline struct hci_conn *hci_conn_hash_lookup_cig(struct hci_dev *hdev, struct hci_conn_hash *h = &hdev->conn_hash; struct hci_conn *c; + HCI_CONN_HASH_LOCKDEP_CHECK(hdev); + rcu_read_lock(); list_for_each_entry_rcu(c, &h->list, list) { @@ -1383,6 +1415,8 @@ static inline struct hci_conn *hci_conn_hash_lookup_big(struct hci_dev *hdev, struct hci_conn_hash *h = &hdev->conn_hash; struct hci_conn *c; + HCI_CONN_HASH_LOCKDEP_CHECK(hdev); + rcu_read_lock(); list_for_each_entry_rcu(c, &h->list, list) { @@ -1407,6 +1441,8 @@ hci_conn_hash_lookup_big_sync_pend(struct hci_dev *hdev, struct hci_conn_hash *h = &hdev->conn_hash; struct hci_conn *c; + HCI_CONN_HASH_LOCKDEP_CHECK(hdev); + rcu_read_lock(); list_for_each_entry_rcu(c, &h->list, list) { @@ -1431,6 +1467,8 @@ hci_conn_hash_lookup_big_state(struct hci_dev *hdev, __u8 handle, __u16 state, struct hci_conn_hash *h = &hdev->conn_hash; struct hci_conn *c; + HCI_CONN_HASH_LOCKDEP_CHECK(hdev); + rcu_read_lock(); list_for_each_entry_rcu(c, &h->list, list) { @@ -1454,6 +1492,8 @@ hci_conn_hash_lookup_pa_sync_big_handle(struct hci_dev *hdev, __u8 big) struct hci_conn_hash *h = &hdev->conn_hash; struct hci_conn *c; + HCI_CONN_HASH_LOCKDEP_CHECK(hdev); + rcu_read_lock(); list_for_each_entry_rcu(c, &h->list, list) { @@ -1477,6 +1517,8 @@ hci_conn_hash_lookup_pa_sync_handle(struct hci_dev *hdev, __u16 sync_handle) struct hci_conn_hash *h = &hdev->conn_hash; struct hci_conn *c; + HCI_CONN_HASH_LOCKDEP_CHECK(hdev); + rcu_read_lock(); list_for_each_entry_rcu(c, &h->list, list) { @@ -1546,6 +1588,8 @@ static inline struct hci_conn *hci_lookup_le_connect(struct hci_dev *hdev) struct hci_conn_hash *h = &hdev->conn_hash; struct hci_conn *c; + HCI_CONN_HASH_LOCKDEP_CHECK(hdev); + rcu_read_lock(); list_for_each_entry_rcu(c, &h->list, list) { diff --git a/include/net/bluetooth/hci_h4.h b/include/net/bluetooth/hci_h4.h new file mode 100644 index 000000000000..a37e7df8c9ce --- /dev/null +++ b/include/net/bluetooth/hci_h4.h @@ -0,0 +1,60 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Bluetooth HCI H:4 packet reassembly + * + * Copyright (C) 2000-2001 Qualcomm Incorporated + * Copyright (C) 2002-2003 Maxim Krasnyansky <maxk@qualcomm.com> + * Copyright (C) 2004-2005 Marcel Holtmann <marcel@holtmann.org> + */ + +#ifndef __HCI_H4_H +#define __HCI_H4_H + +#include <linux/skbuff.h> +#include <linux/types.h> + +struct hci_dev; + +struct h4_recv_pkt { + u8 type; /* Packet type */ + u8 hlen; /* Header length */ + u8 loff; /* Data length offset in header */ + u8 lsize; /* Data length field size */ + u16 maxlen; /* Max overall packet length */ + int (*recv)(struct hci_dev *hdev, struct sk_buff *skb); +}; + +#define H4_RECV_ACL \ + .type = HCI_ACLDATA_PKT, \ + .hlen = HCI_ACL_HDR_SIZE, \ + .loff = 2, \ + .lsize = 2, \ + .maxlen = HCI_MAX_FRAME_SIZE \ + +#define H4_RECV_SCO \ + .type = HCI_SCODATA_PKT, \ + .hlen = HCI_SCO_HDR_SIZE, \ + .loff = 2, \ + .lsize = 1, \ + .maxlen = HCI_MAX_SCO_SIZE + +#define H4_RECV_EVENT \ + .type = HCI_EVENT_PKT, \ + .hlen = HCI_EVENT_HDR_SIZE, \ + .loff = 1, \ + .lsize = 1, \ + .maxlen = HCI_MAX_EVENT_SIZE + +#define H4_RECV_ISO \ + .type = HCI_ISODATA_PKT, \ + .hlen = HCI_ISO_HDR_SIZE, \ + .loff = 2, \ + .lsize = 2, \ + .maxlen = HCI_MAX_FRAME_SIZE \ + +struct sk_buff *h4_recv_skb(struct hci_dev *hdev, u8 *alignment, u8 *padding, + struct sk_buff *skb, const unsigned char *buffer, + int count, const struct h4_recv_pkt *pkts, + int pkts_count); + +#endif /* __HCI_H4_H */ diff --git a/include/net/bluetooth/l2cap.h b/include/net/bluetooth/l2cap.h index 3d9a32094347..efb9b7f422d1 100644 --- a/include/net/bluetooth/l2cap.h +++ b/include/net/bluetooth/l2cap.h @@ -614,31 +614,6 @@ struct l2cap_chan { struct mutex lock; }; -struct l2cap_ops { - char *name; - - int (*new_connection)(struct l2cap_chan *chan, - struct l2cap_chan *new_chan); - int (*recv) (struct l2cap_chan * chan, - struct sk_buff *skb); - void (*teardown) (struct l2cap_chan *chan, int err); - void (*close) (struct l2cap_chan *chan); - void (*state_change) (struct l2cap_chan *chan, - int state, int err); - void (*ready) (struct l2cap_chan *chan); - void (*defer) (struct l2cap_chan *chan); - void (*resume) (struct l2cap_chan *chan); - void (*suspend) (struct l2cap_chan *chan); - void (*set_shutdown) (struct l2cap_chan *chan); - long (*get_sndtimeo) (struct l2cap_chan *chan); - struct pid *(*get_peer_pid) (struct l2cap_chan *chan); - struct sk_buff *(*alloc_skb) (struct l2cap_chan *chan, - unsigned long hdr_len, - unsigned long len, int nb); - int (*filter) (struct l2cap_chan * chan, - struct sk_buff *skb); -}; - struct l2cap_conn { struct hci_conn *hcon; struct hci_chan *hchan; @@ -668,12 +643,45 @@ struct l2cap_conn { struct l2cap_chan *smp; - struct list_head chan_l; + struct list_head chan_l __guarded_by(&lock); struct mutex lock; struct kref ref; struct list_head users; }; +struct l2cap_ops { + char *name; + + int (*new_connection)(struct l2cap_chan *chan, + struct l2cap_chan *new_chan) + __must_hold(&chan->lock) + __must_hold(&new_chan->lock); + int (*recv) (struct l2cap_chan * chan, + struct sk_buff *skb); + void (*teardown) (struct l2cap_chan *chan, int err) + __must_hold(&chan->lock); + void (*close) (struct l2cap_chan *chan) + __must_hold(&chan->lock); + void (*state_change) (struct l2cap_chan *chan, + int state, int err); + void (*ready) (struct l2cap_chan *chan) + __must_hold(&chan->lock) + __must_hold(&chan->conn->lock); + void (*defer) (struct l2cap_chan *chan); + void (*resume) (struct l2cap_chan *chan) + __must_hold(&chan->lock); + void (*suspend) (struct l2cap_chan *chan); + void (*set_shutdown) (struct l2cap_chan *chan) + __must_hold(&chan->lock); + long (*get_sndtimeo) (struct l2cap_chan *chan); + struct pid *(*get_peer_pid) (struct l2cap_chan *chan); + struct sk_buff *(*alloc_skb) (struct l2cap_chan *chan, + unsigned long hdr_len, + unsigned long len, int nb); + int (*filter) (struct l2cap_chan * chan, + struct sk_buff *skb); +}; + struct l2cap_user { struct list_head list; int (*probe) (struct l2cap_conn *conn, struct l2cap_user *user); @@ -758,6 +766,10 @@ enum { * otherwise considers all channels equal and will e.g. complain about a * connection oriented channel triggering SMP procedures or a listening * channel creating and locking a child channel. + * + * Lock nesting of channels at the same nesting level is allowed if the channels + * have the same l2cap_chan::conn and l2cap_chan::conn.lock is taken before the + * nested locks. l2cap_chan_try_sibling_lock() must be used. */ enum { L2CAP_NESTING_SMP, @@ -830,11 +842,13 @@ struct l2cap_chan *l2cap_chan_hold_unless_zero(struct l2cap_chan *c); void l2cap_chan_put(struct l2cap_chan *c); static inline void l2cap_chan_lock(struct l2cap_chan *chan) + __acquires(&chan->lock) { mutex_lock_nested(&chan->lock, atomic_read(&chan->nesting)); } static inline void l2cap_chan_unlock(struct l2cap_chan *chan) + __releases(&chan->lock) { mutex_unlock(&chan->lock); } @@ -845,12 +859,11 @@ static inline void l2cap_set_timer(struct l2cap_chan *chan, BT_DBG("chan %p state %s timeout %ld", chan, state_to_string(chan->state), timeout); - /* If delayed work cancelled do not hold(chan) - since it is already done with previous set_timer */ - if (!cancel_delayed_work(work)) - l2cap_chan_hold(chan); + l2cap_chan_hold(chan); - schedule_delayed_work(work, timeout); + /* put(chan) if timer was already queued so it already has a ref */ + if (mod_delayed_work(system_percpu_wq, work, timeout)) + l2cap_chan_put(chan); } static inline bool l2cap_clear_timer(struct l2cap_chan *chan, @@ -952,14 +965,16 @@ void l2cap_cleanup_sockets(void); bool l2cap_is_socket(struct socket *sock); void __l2cap_le_connect_rsp_defer(struct l2cap_chan *chan); -void __l2cap_ecred_conn_rsp_defer(struct l2cap_chan *chan); +void __l2cap_ecred_conn_rsp_defer(struct l2cap_chan *chan) + __must_hold(&chan->lock) __must_hold(&chan->conn->lock); void __l2cap_connect_rsp_defer(struct l2cap_chan *chan); int l2cap_add_psm(struct l2cap_chan *chan, bdaddr_t *src, __le16 psm); int l2cap_add_scid(struct l2cap_chan *chan, __u16 scid); struct l2cap_chan *l2cap_chan_create(void); -void l2cap_chan_close(struct l2cap_chan *chan, int reason); +void l2cap_chan_close_unlocked(struct l2cap_chan *chan, int reason) + __must_not_hold(&chan->lock); int l2cap_chan_connect(struct l2cap_chan *chan, __le16 psm, u16 cid, bdaddr_t *dst, u8 dst_type, u16 timeout); int l2cap_chan_reconfigure(struct l2cap_chan *chan, __u16 mtu); @@ -971,11 +986,13 @@ int l2cap_chan_check_security(struct l2cap_chan *chan, bool initiator); void l2cap_chan_set_defaults(struct l2cap_chan *chan, struct l2cap_chan *pchan); int l2cap_ertm_init(struct l2cap_chan *chan); void l2cap_chan_add(struct l2cap_conn *conn, struct l2cap_chan *chan); -void __l2cap_chan_add(struct l2cap_conn *conn, struct l2cap_chan *chan); +void __l2cap_chan_add(struct l2cap_conn *conn, struct l2cap_chan *chan) + __must_hold(&conn->lock) __must_hold(&chan->lock); typedef void (*l2cap_chan_func_t)(struct l2cap_chan *chan, void *data); void l2cap_chan_list(struct l2cap_conn *conn, l2cap_chan_func_t func, void *data); -void l2cap_chan_del(struct l2cap_chan *chan, int err); +void l2cap_chan_del(struct l2cap_chan *chan, int err) + __must_hold(&chan->lock) __must_hold(&chan->conn->lock); void l2cap_send_conn_req(struct l2cap_chan *chan); struct l2cap_conn *l2cap_conn_get(struct l2cap_conn *conn); @@ -985,4 +1002,19 @@ void l2cap_conn_put(struct l2cap_conn *conn); int l2cap_register_user(struct l2cap_conn *conn, struct l2cap_user *user); void l2cap_unregister_user(struct l2cap_conn *conn, struct l2cap_user *user); +bool l2cap_chan_lock_conn(struct l2cap_chan *chan) + __acquires(&chan->lock) __cond_acquires(true, &chan->conn->lock); + +/* Release macro for l2cap_chan_lock_conn, so context analysis understands it */ +#define l2cap_chan_unlock_conn(chan, conn_locked) \ + ({ \ + struct l2cap_chan *__chan = (chan); \ + struct l2cap_conn *__conn = __chan->conn; \ + l2cap_chan_unlock(__chan); \ + if (conn_locked) { \ + mutex_unlock(&__conn->lock); \ + l2cap_conn_put(__conn); \ + } \ + }) + #endif /* __L2CAP_H */ diff --git a/net/bluetooth/6lowpan.c b/net/bluetooth/6lowpan.c index 30f4afa18bc8..836add41f5d1 100644 --- a/net/bluetooth/6lowpan.c +++ b/net/bluetooth/6lowpan.c @@ -722,6 +722,8 @@ out: } static inline void chan_ready_cb(struct l2cap_chan *chan) + __must_hold(&chan->lock) + __must_hold(&chan->conn->lock) { struct lowpan_btle_dev *dev; bool new_netdev = false; @@ -912,18 +914,27 @@ static int bt_6lowpan_connect(bdaddr_t *addr, u8 dst_type) static int bt_6lowpan_disconnect(struct l2cap_conn *conn, u8 dst_type) { struct lowpan_peer *peer; + struct l2cap_chan *chan; BT_DBG("conn %p dst type %u", conn, dst_type); + spin_lock(&devices_lock); + peer = lookup_peer(conn); - if (!peer) + if (!peer) { + spin_unlock(&devices_lock); return -ENOENT; + } + + chan = peer->chan; + l2cap_chan_hold(chan); + + spin_unlock(&devices_lock); - BT_DBG("peer %p chan %p", peer, peer->chan); + BT_DBG("peer %p chan %p", peer, chan); - l2cap_chan_lock(peer->chan); - l2cap_chan_close(peer->chan, ENOENT); - l2cap_chan_unlock(peer->chan); + l2cap_chan_close_unlocked(chan, ENOENT); + l2cap_chan_put(chan); return 0; } @@ -1025,9 +1036,9 @@ static void disconnect_all_peers(void) struct lowpan_peer *peer; int nchans; - /* l2cap_chan_close() cannot be called from RCU, and lock ordering - * chan->lock > devices_lock prevents taking write side lock, so copy - * then close. + /* l2cap_chan_close_unlocked() cannot be called from RCU, and lock + * ordering chan->lock > devices_lock prevents taking write side lock, + * so copy then close. */ rcu_read_lock(); @@ -1062,9 +1073,7 @@ done: spin_unlock(&devices_lock); for (i = 0; i < nchans; ++i) { - l2cap_chan_lock(chans[i]); - l2cap_chan_close(chans[i], ENOENT); - l2cap_chan_unlock(chans[i]); + l2cap_chan_close_unlocked(chans[i], ENOENT); l2cap_chan_put(chans[i]); } } while (nchans); @@ -1082,9 +1091,7 @@ static void do_enable_set(bool flag) mutex_lock(&set_lock); if (listen_chan) { - l2cap_chan_lock(listen_chan); - l2cap_chan_close(listen_chan, 0); - l2cap_chan_unlock(listen_chan); + l2cap_chan_close_unlocked(listen_chan, 0); l2cap_chan_put(listen_chan); } @@ -1132,9 +1139,7 @@ static ssize_t lowpan_control_write(struct file *fp, mutex_lock(&set_lock); if (listen_chan) { - l2cap_chan_lock(listen_chan); - l2cap_chan_close(listen_chan, 0); - l2cap_chan_unlock(listen_chan); + l2cap_chan_close_unlocked(listen_chan, 0); l2cap_chan_put(listen_chan); listen_chan = NULL; } @@ -1303,10 +1308,9 @@ static void __exit bt_6lowpan_exit(void) debugfs_remove(lowpan_control_debugfs); if (listen_chan) { - l2cap_chan_lock(listen_chan); - l2cap_chan_close(listen_chan, 0); - l2cap_chan_unlock(listen_chan); + l2cap_chan_close_unlocked(listen_chan, 0); l2cap_chan_put(listen_chan); + listen_chan = NULL; } disconnect_devices(); diff --git a/net/bluetooth/Makefile b/net/bluetooth/Makefile index ff466ea97436..b78ad98864d4 100644 --- a/net/bluetooth/Makefile +++ b/net/bluetooth/Makefile @@ -14,7 +14,7 @@ bluetooth_6lowpan-y := 6lowpan.o bluetooth-y := af_bluetooth.o hci_core.o hci_conn.o hci_event.o mgmt.o \ hci_sock.o hci_sysfs.o l2cap_core.o l2cap_sock.o smp.o lib.o \ ecdh_helper.o mgmt_util.o mgmt_config.o hci_codec.o eir.o hci_sync.o \ - hci_drv.o + hci_drv.o hci_h4.o bluetooth-$(CONFIG_DEV_COREDUMP) += coredump.o diff --git a/net/bluetooth/bnep/core.c b/net/bluetooth/bnep/core.c index f7d88c33e23e..0dde82e9a085 100644 --- a/net/bluetooth/bnep/core.c +++ b/net/bluetooth/bnep/core.c @@ -670,7 +670,7 @@ int bnep_add_connection(struct bnep_connadd_req *req, struct socket *sock) goto failed; } - strcpy(req->device, dev->name); + strscpy(req->device, dev->name, sizeof(req->device)); up_write(&bnep_session_sem); return 0; @@ -712,7 +712,7 @@ static void __bnep_copy_ci(struct bnep_conninfo *ci, struct bnep_session *s) memset(ci, 0, sizeof(*ci)); memcpy(ci->dst, s->eh.h_source, ETH_ALEN); - strcpy(ci->device, s->dev->name); + strscpy(ci->device, s->dev->name, sizeof(ci->device)); ci->flags = s->flags & valid_flags; ci->state = s->state; ci->role = s->role; diff --git a/net/bluetooth/hci_core.c b/net/bluetooth/hci_core.c index d7355c73f93e..66840df8c020 100644 --- a/net/bluetooth/hci_core.c +++ b/net/bluetooth/hci_core.c @@ -4075,7 +4075,7 @@ static int hci_send_cmd_sync(struct hci_dev *hdev, struct sk_buff *skb) if (!hdev->sent_cmd) { skb_queue_head(&hdev->cmd_q, skb); queue_work(hdev->workqueue, &hdev->cmd_work); - return -EINVAL; + return -ENOMEM; } if (hci_skb_opcode(skb) != HCI_OP_NOP) { diff --git a/net/bluetooth/hci_h4.c b/net/bluetooth/hci_h4.c new file mode 100644 index 000000000000..86f809018062 --- /dev/null +++ b/net/bluetooth/hci_h4.c @@ -0,0 +1,160 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Bluetooth HCI H:4 packet reassembly + * + * Copyright (C) 2000-2001 Qualcomm Incorporated + * Copyright (C) 2002-2003 Maxim Krasnyansky <maxk@qualcomm.com> + * Copyright (C) 2004-2005 Marcel Holtmann <marcel@holtmann.org> + */ + +#include <linux/export.h> +#include <linux/skbuff.h> +#include <linux/unaligned.h> + +#include <net/bluetooth/bluetooth.h> +#include <net/bluetooth/hci_core.h> +#include <net/bluetooth/hci_h4.h> + +/* h4_recv_skb - Reassemble H:4 framed packets + * @hdev: HCI device the packets are received on + * @alignment: optional packet alignment, NULL or 0 means no alignment + * @padding: optional padding state carried over between calls + * @skb: partially received packet from a previous call, may be NULL or an + * ERR_PTR returned by a previous call + * @buffer: buffer holding the received data + * @count: number of bytes in @buffer + * @pkts: table of supported packet types + * @pkts_count: number of entries in @pkts + * + * Returns the partially received packet to be passed to the next call, or an + * ERR_PTR on error. The returned value can be fed back into this function as + * is, but must be checked with IS_ERR() before being freed. + */ +struct sk_buff *h4_recv_skb(struct hci_dev *hdev, u8 *alignment, u8 *padding, + struct sk_buff *skb, const unsigned char *buffer, + int count, const struct h4_recv_pkt *pkts, + int pkts_count) +{ + u8 align = alignment && *alignment ? *alignment : 1; + + /* Check for error from previous call */ + if (IS_ERR(skb)) + skb = NULL; + + while (count) { + int i, len; + + /* remove padding bytes from buffer */ + if (padding) { + for (; (*padding) && count > 0; (*padding)--) { + count--; + buffer++; + } + } + + if (!count) + break; + + if (!skb) { + for (i = 0; i < pkts_count; i++) { + if (buffer[0] != pkts[i].type) + continue; + + skb = bt_skb_alloc(pkts[i].maxlen, + GFP_ATOMIC); + if (!skb) + return ERR_PTR(-ENOMEM); + + hci_skb_pkt_type(skb) = pkts[i].type; + hci_skb_expect(skb) = pkts[i].hlen; + break; + } + + /* Check for invalid packet type */ + if (!skb) + return ERR_PTR(-EILSEQ); + + count -= 1; + buffer += 1; + } + + len = min_t(uint, hci_skb_expect(skb) - skb->len, count); + skb_put_data(skb, buffer, len); + + count -= len; + buffer += len; + + /* Check for partial packet */ + if (skb->len < hci_skb_expect(skb)) + continue; + + for (i = 0; i < pkts_count; i++) { + if (hci_skb_pkt_type(skb) == pkts[i].type) + break; + } + + if (i >= pkts_count) { + kfree_skb(skb); + return ERR_PTR(-EILSEQ); + } + + if (skb->len == pkts[i].hlen) { + u16 dlen; + + switch (pkts[i].lsize) { + case 0: + /* No variable data length */ + dlen = 0; + break; + case 1: + /* Single octet variable length */ + dlen = skb->data[pkts[i].loff]; + hci_skb_expect(skb) += dlen; + + if (skb_tailroom(skb) < dlen) { + kfree_skb(skb); + return ERR_PTR(-EMSGSIZE); + } + break; + case 2: + /* Double octet variable length */ + dlen = get_unaligned_le16(skb->data + + pkts[i].loff); + hci_skb_expect(skb) += dlen; + + if (skb_tailroom(skb) < dlen) { + kfree_skb(skb); + return ERR_PTR(-EMSGSIZE); + } + break; + default: + /* Unsupported variable length */ + kfree_skb(skb); + return ERR_PTR(-EILSEQ); + } + + if (!dlen) { + if (padding) { + *padding = (skb->len + 1) % align; + *padding = (align - *padding) % align; + } + + /* No more data, complete frame */ + pkts[i].recv(hdev, skb); + skb = NULL; + } + } else { + if (padding) { + *padding = (skb->len + 1) % align; + *padding = (align - *padding) % align; + } + + /* Complete frame */ + pkts[i].recv(hdev, skb); + skb = NULL; + } + } + + return skb; +} +EXPORT_SYMBOL_GPL(h4_recv_skb); diff --git a/net/bluetooth/hci_sync.c b/net/bluetooth/hci_sync.c index 3f121099eb22..b848be3d33c5 100644 --- a/net/bluetooth/hci_sync.c +++ b/net/bluetooth/hci_sync.c @@ -4797,6 +4797,24 @@ static int hci_le_set_def_rate_sync(struct hci_dev *hdev) cp.cont_num = cpu_to_le16(0x0001); cp.supv_timeout = cpu_to_le16(0x000c); /* 120 ms */ + /* The connection event length recommended in requests by a Peripheral + * uses units of 125 us with a valid range of 0x0001 to 0x7CFF + * (0.125 ms to 3.999875 s), so 0x0000 cannot be used. Also note that + * the Controller is not required to use these values: + * + * BLUETOOTH CORE SPECIFICATION Version 6.2 | Vol 4, Part E + * 7.8.158. LE Set Default Rate Parameters command + * + * The Min_CE_Length and Max_CE_Length parameters provide the + * Controller with the expected minimum and maximum length of the + * connection events. The Controller is not required to use these + * values. + * + * So it is safe to just use the minimum. + */ + cp.min_ce_len = cpu_to_le16(0x0001); + cp.max_ce_len = cpu_to_le16(0x0001); + return __hci_cmd_sync_status(hdev, HCI_OP_LE_SET_DEF_RATE, sizeof(cp), &cp, HCI_CMD_TIMEOUT); } @@ -5358,6 +5376,30 @@ static int hci_dev_init_sync(struct hci_dev *hdev) return ret; } +static void hci_dev_drop_last_cmd_req_and_close(struct hci_dev *hdev) +{ + /* Drop last sent command */ + if (hdev->sent_cmd) { + cancel_delayed_work_sync(&hdev->cmd_timer); + kfree_skb(hdev->sent_cmd); + hdev->sent_cmd = NULL; + } + + /* Drop last request */ + if (hdev->req_skb) { + kfree_skb(hdev->req_skb); + hdev->req_skb = NULL; + hci_dev_clear_flag(hdev, HCI_CMD_PENDING); + } + + clear_bit(HCI_RUNNING, &hdev->flags); + hci_sock_dev_event(hdev, HCI_DEV_CLOSE); + + /* After this point our queues are empty and no tasks are scheduled. */ + hdev->close(hdev); + hdev->flags &= BIT(HCI_RAW); +} + int hci_dev_open_sync(struct hci_dev *hdev) { int ret; @@ -5444,23 +5486,7 @@ int hci_dev_open_sync(struct hci_dev *hdev) if (hdev->flush) hdev->flush(hdev); - if (hdev->sent_cmd) { - cancel_delayed_work_sync(&hdev->cmd_timer); - kfree_skb(hdev->sent_cmd); - hdev->sent_cmd = NULL; - } - - if (hdev->req_skb) { - kfree_skb(hdev->req_skb); - hdev->req_skb = NULL; - hci_dev_clear_flag(hdev, HCI_CMD_PENDING); - } - - clear_bit(HCI_RUNNING, &hdev->flags); - hci_sock_dev_event(hdev, HCI_DEV_CLOSE); - - hdev->close(hdev); - hdev->flags &= BIT(HCI_RAW); + hci_dev_drop_last_cmd_req_and_close(hdev); } done: @@ -5627,28 +5653,10 @@ int hci_dev_close_sync(struct hci_dev *hdev) skb_queue_purge(&hdev->cmd_q); skb_queue_purge(&hdev->raw_q); - /* Drop last sent command */ - if (hdev->sent_cmd) { - cancel_delayed_work_sync(&hdev->cmd_timer); - kfree_skb(hdev->sent_cmd); - hdev->sent_cmd = NULL; - } - - /* Drop last request */ - if (hdev->req_skb) { - kfree_skb(hdev->req_skb); - hdev->req_skb = NULL; - hci_dev_clear_flag(hdev, HCI_CMD_PENDING); - } - - clear_bit(HCI_RUNNING, &hdev->flags); - hci_sock_dev_event(hdev, HCI_DEV_CLOSE); - - /* After this point our queues are empty and no tasks are scheduled. */ - hdev->close(hdev); + /* Drop last sent command, last request and close */ + hci_dev_drop_last_cmd_req_and_close(hdev); /* Clear flags */ - hdev->flags &= BIT(HCI_RAW); hci_dev_clear_volatile_flags(hdev); hci_dev_clear_flag(hdev, HCI_CMD_DRAIN_WORKQUEUE); @@ -7466,8 +7474,24 @@ static int hci_le_conn_rate_request_sync(struct hci_dev *hdev, void *data) cp.max_latency = cpu_to_le16(params->max_latency); cp.cont_num = cpu_to_le16(params->cont_num); cp.supv_timeout = cpu_to_le16(params->rate_supv_timeout); - cp.min_ce_len = cpu_to_le16(0x0000); - cp.max_ce_len = cpu_to_le16(0x0000); + + /* The connection event length recommended in requests by a Peripheral + * uses units of 125 us with a valid range of 0x0001 to 0x7CFF + * (0.125 ms to 3.999875 s), so 0x0000 cannot be used. Also note that + * the Controller is not required to use these values: + * + * BLUETOOTH CORE SPECIFICATION Version 6.2 | Vol 4, Part E + * 7.8.157. LE Connection Rate Request command + * + * The Min_CE_Length and Max_CE_Length parameters provide the + * Controller with the expected minimum and maximum length of the + * connection events. The Controller is not required to use these + * values. + * + * So it is safe to just use the minimum. + */ + cp.min_ce_len = cpu_to_le16(0x0001); + cp.max_ce_len = cpu_to_le16(0x0001); hci_dev_unlock(hdev); diff --git a/net/bluetooth/hci_sysfs.c b/net/bluetooth/hci_sysfs.c index 8957ce7c21b7..c2065abf753e 100644 --- a/net/bluetooth/hci_sysfs.c +++ b/net/bluetooth/hci_sysfs.c @@ -13,7 +13,10 @@ static const struct class bt_class = { static void bt_link_release(struct device *dev) { struct hci_conn *conn = to_hci_conn(dev); + struct device *parent = dev->parent; + kfree(conn); + put_device(parent); } static const struct device_type bt_link = { @@ -21,6 +24,16 @@ static const struct device_type bt_link = { .release = bt_link_release, }; +/* + * The rfcomm tty device will possibly retain even when conn + * is down, and sysfs doesn't support move zombie device, + * so we should move the device before conn device is destroyed. + */ +static int __match_tty(struct device *dev, const void *data) +{ + return !strncmp(dev_name(dev), "rfcomm", 6); +} + void hci_conn_init_sysfs(struct hci_conn *conn) { struct hci_dev *hdev = conn->hdev; @@ -29,7 +42,7 @@ void hci_conn_init_sysfs(struct hci_conn *conn) conn->dev.type = &bt_link; conn->dev.class = &bt_class; - conn->dev.parent = &hdev->dev; + conn->dev.parent = get_device(&hdev->dev); device_initialize(&conn->dev); } @@ -69,7 +82,7 @@ void hci_conn_del_sysfs(struct hci_conn *conn) while (1) { struct device *dev; - dev = device_find_any_child(&conn->dev); + dev = device_find_child(&conn->dev, NULL, __match_tty); if (!dev) break; device_move(dev, NULL, DPM_ORDER_DEV_LAST); diff --git a/net/bluetooth/l2cap_core.c b/net/bluetooth/l2cap_core.c index 644e31160d55..033287c09224 100644 --- a/net/bluetooth/l2cap_core.c +++ b/net/bluetooth/l2cap_core.c @@ -44,8 +44,8 @@ bool enable_ecred = IS_ENABLED(CONFIG_BT_LE_L2CAP_ECRED); static u32 l2cap_feat_mask = L2CAP_FEAT_FIXED_CHAN | L2CAP_FEAT_UCD; -static LIST_HEAD(chan_list); static DEFINE_RWLOCK(chan_list_lock); +static __guarded_by(&chan_list_lock) LIST_HEAD(chan_list); static struct sk_buff *l2cap_build_cmd(struct l2cap_conn *conn, u8 code, u8 ident, u16 dlen, void *data); @@ -59,6 +59,8 @@ static void l2cap_tx(struct l2cap_chan *chan, struct l2cap_ctrl *control, static void l2cap_retrans_timeout(struct work_struct *work); static void l2cap_monitor_timeout(struct work_struct *work); static void l2cap_ack_timeout(struct work_struct *work); +static void __l2cap_chan_close(struct l2cap_chan *chan, int reason) + __must_hold(&chan->lock) __must_hold(&chan->conn->lock); static inline u8 bdaddr_type(u8 link_type, u8 bdaddr_type) { @@ -86,6 +88,7 @@ static inline u8 bdaddr_dst_type(struct hci_conn *hcon) static struct l2cap_chan *__l2cap_get_chan_by_dcid(struct l2cap_conn *conn, u16 cid) + __must_hold(&conn->lock) { struct l2cap_chan *c; @@ -98,6 +101,7 @@ static struct l2cap_chan *__l2cap_get_chan_by_dcid(struct l2cap_conn *conn, static struct l2cap_chan *__l2cap_get_chan_by_scid(struct l2cap_conn *conn, u16 cid) + __must_hold(&conn->lock) { struct l2cap_chan *c; @@ -109,45 +113,40 @@ static struct l2cap_chan *__l2cap_get_chan_by_scid(struct l2cap_conn *conn, } /* Find channel with given SCID. - * Returns a reference locked channel. + * Returns a reference. */ static struct l2cap_chan *l2cap_get_chan_by_scid(struct l2cap_conn *conn, u16 cid) + __must_hold(&conn->lock) { struct l2cap_chan *c; c = __l2cap_get_chan_by_scid(conn, cid); - if (c) { - /* Only lock if chan reference is not 0 */ + if (c) c = l2cap_chan_hold_unless_zero(c); - if (c) - l2cap_chan_lock(c); - } return c; } /* Find channel with given DCID. - * Returns a reference locked channel. + * Returns a reference. */ static struct l2cap_chan *l2cap_get_chan_by_dcid(struct l2cap_conn *conn, u16 cid) + __must_hold(&conn->lock) { struct l2cap_chan *c; c = __l2cap_get_chan_by_dcid(conn, cid); - if (c) { - /* Only lock if chan reference is not 0 */ + if (c) c = l2cap_chan_hold_unless_zero(c); - if (c) - l2cap_chan_lock(c); - } return c; } static struct l2cap_chan *__l2cap_get_chan_by_ident(struct l2cap_conn *conn, u8 ident) + __must_hold(&conn->lock) { struct l2cap_chan *c; @@ -160,6 +159,7 @@ static struct l2cap_chan *__l2cap_get_chan_by_ident(struct l2cap_conn *conn, static struct l2cap_chan *__l2cap_global_chan_by_addr(__le16 psm, bdaddr_t *src, u8 src_type) + __must_hold_shared(&chan_list_lock) { struct l2cap_chan *c; @@ -237,6 +237,7 @@ int l2cap_add_scid(struct l2cap_chan *chan, __u16 scid) } static u16 l2cap_alloc_cid(struct l2cap_conn *conn) + __must_hold(&conn->lock) { u16 cid, dyn_end; @@ -430,7 +431,7 @@ static void l2cap_chan_timeout(struct work_struct *work) else reason = ETIMEDOUT; - l2cap_chan_close(chan, reason); + __l2cap_chan_close(chan, reason); chan->ops->close(chan); @@ -622,6 +623,10 @@ void __l2cap_chan_add(struct l2cap_conn *conn, struct l2cap_chan *chan) BT_DBG("conn %p, psm 0x%2.2x, dcid 0x%4.4x", conn, __le16_to_cpu(chan->psm), chan->dcid); + /* Caller must ensure l2cap_chan is linked to l2cap_conn only once */ + if (WARN_ON_ONCE(chan->conn || test_bit(FLAG_DEL, &chan->flags))) + return; + conn->disc_reason = HCI_ERROR_REMOTE_USER_TERM; chan->conn = l2cap_conn_get(conn); @@ -673,12 +678,16 @@ void __l2cap_chan_add(struct l2cap_conn *conn, struct l2cap_chan *chan) void l2cap_chan_add(struct l2cap_conn *conn, struct l2cap_chan *chan) { mutex_lock(&conn->lock); + l2cap_chan_lock(chan); __l2cap_chan_add(conn, chan); + l2cap_chan_unlock(chan); mutex_unlock(&conn->lock); } void l2cap_chan_del(struct l2cap_chan *chan, int err) { + lockdep_assert(!chan->conn || lockdep_is_held(&chan->conn->lock)); + __clear_chan_timer(chan); BT_DBG("chan %p, err %d, state %s", chan, err, @@ -733,6 +742,7 @@ EXPORT_SYMBOL_GPL(l2cap_chan_del); static void __l2cap_chan_list_id(struct l2cap_conn *conn, u16 id, l2cap_chan_func_t func, void *data) + __must_hold(&conn->lock) { struct l2cap_chan *chan, *l; @@ -744,6 +754,7 @@ static void __l2cap_chan_list_id(struct l2cap_conn *conn, u16 id, static void __l2cap_chan_list(struct l2cap_conn *conn, l2cap_chan_func_t func, void *data) + __must_hold(&conn->lock) { struct l2cap_chan *chan; @@ -808,6 +819,8 @@ static void l2cap_chan_le_connect_reject(struct l2cap_chan *chan) } static void l2cap_chan_ecred_connect_reject(struct l2cap_chan *chan) + __must_hold(&chan->lock) + __must_hold(&chan->conn->lock) { l2cap_state_change(chan, BT_DISCONN); @@ -835,7 +848,7 @@ static void l2cap_chan_connect_reject(struct l2cap_chan *chan) l2cap_send_cmd(conn, chan->ident, L2CAP_CONN_RSP, sizeof(rsp), &rsp); } -void l2cap_chan_close(struct l2cap_chan *chan, int reason) +static void __l2cap_chan_close(struct l2cap_chan *chan, int reason) { struct l2cap_conn *conn = chan->conn; @@ -884,7 +897,54 @@ void l2cap_chan_close(struct l2cap_chan *chan, int reason) break; } } -EXPORT_SYMBOL(l2cap_chan_close); + +/* Take chan->lock. If chan->conn is non-NULL, take new reference on it, take + * chan->conn->lock, and return true. Otherwise return false. + */ +bool l2cap_chan_lock_conn(struct l2cap_chan *chan) + __context_unsafe(/* conditional locking */) +{ + /* Handle conn->lock > chan->lock ordering + race on chan->conn */ + for (;;) { + struct l2cap_conn *conn; + + l2cap_chan_lock(chan); + conn = chan->conn; + if (conn) + l2cap_conn_get(conn); + l2cap_chan_unlock(chan); + + if (conn) + mutex_lock(&conn->lock); + + l2cap_chan_lock(chan); + + if (chan->conn != conn) { + l2cap_chan_unlock(chan); + if (conn) { + mutex_unlock(&conn->lock); + l2cap_conn_put(conn); + } + schedule(); + continue; + } + + return chan->conn; + } +} + +void l2cap_chan_close_unlocked(struct l2cap_chan *chan, int reason) +{ + bool have_conn; + + have_conn = l2cap_chan_lock_conn(chan); + + /* Context analysis: consider chan->conn->lock held also if conn NULL */ + context_unsafe(__l2cap_chan_close(chan, reason)); + + l2cap_chan_unlock_conn(chan, have_conn); +} +EXPORT_SYMBOL(l2cap_chan_close_unlocked); static inline u8 l2cap_get_auth_type(struct l2cap_chan *chan) { @@ -1283,6 +1343,8 @@ void l2cap_send_conn_req(struct l2cap_chan *chan) } static void l2cap_chan_ready(struct l2cap_chan *chan) + __must_hold(&chan->lock) + __must_hold(&chan->conn->lock) { /* The channel may have already been flagged as connected in * case of receiving data before the L2CAP info req/rsp @@ -1384,6 +1446,7 @@ static void l2cap_ecred_defer_connect(struct l2cap_chan *chan, void *data) } static void l2cap_ecred_connect(struct l2cap_chan *chan) + __must_hold(&chan->conn->lock) { struct l2cap_conn *conn = chan->conn; struct l2cap_ecred_conn_data data; @@ -1417,6 +1480,8 @@ static void l2cap_ecred_connect(struct l2cap_chan *chan) } static void l2cap_le_start(struct l2cap_chan *chan) + __must_hold(&chan->lock) + __must_hold(&chan->conn->lock) { struct l2cap_conn *conn = chan->conn; @@ -1437,6 +1502,8 @@ static void l2cap_le_start(struct l2cap_chan *chan) } static void l2cap_start_connection(struct l2cap_chan *chan) + __must_hold(&chan->lock) + __must_hold(&chan->conn->lock) { if (chan->conn->hcon->type == LE_LINK) { l2cap_le_start(chan); @@ -1486,6 +1553,8 @@ static bool l2cap_check_enc_key_size(struct hci_conn *hcon, } static void l2cap_do_start(struct l2cap_chan *chan) + __must_hold(&chan->lock) + __must_hold(&chan->conn->lock) { struct l2cap_conn *conn = chan->conn; @@ -1552,6 +1621,7 @@ static void l2cap_send_disconn_req(struct l2cap_chan *chan, int err) /* ---- L2CAP connections ---- */ static void l2cap_conn_start(struct l2cap_conn *conn) + __must_hold(&conn->lock) { struct l2cap_chan *chan, *tmp; @@ -1560,6 +1630,8 @@ static void l2cap_conn_start(struct l2cap_conn *conn) list_for_each_entry_safe(chan, tmp, &conn->chan_l, list) { l2cap_chan_lock(chan); + lockdep_assert_held(&chan->conn->lock); + if (chan->chan_type != L2CAP_CHAN_CONN_ORIENTED) { l2cap_chan_ready(chan); l2cap_chan_unlock(chan); @@ -1576,7 +1648,7 @@ static void l2cap_conn_start(struct l2cap_conn *conn) if (!l2cap_mode_supported(chan->mode, conn->feat_mask) && test_bit(CONF_STATE2_DEVICE, &chan->conf_state)) { - l2cap_chan_close(chan, ECONNRESET); + __l2cap_chan_close(chan, ECONNRESET); l2cap_chan_unlock(chan); continue; } @@ -1584,7 +1656,7 @@ static void l2cap_conn_start(struct l2cap_conn *conn) if (l2cap_check_enc_key_size(conn->hcon, chan)) l2cap_start_connection(chan); else - l2cap_chan_close(chan, ECONNREFUSED); + __l2cap_chan_close(chan, ECONNREFUSED); } else if (chan->state == BT_CONNECT2) { struct l2cap_conn_rsp rsp; @@ -1676,6 +1748,8 @@ static void l2cap_conn_ready(struct l2cap_conn *conn) l2cap_chan_lock(chan); + lockdep_assert_held(&chan->conn->lock); + if (hcon->type == LE_LINK) { l2cap_le_start(chan); } else if (chan->chan_type != L2CAP_CHAN_CONN_ORIENTED) { @@ -1698,6 +1772,7 @@ static void l2cap_conn_ready(struct l2cap_conn *conn) /* Notify sockets that we cannot guaranty reliability anymore */ static void l2cap_conn_unreliable(struct l2cap_conn *conn, int err) + __must_hold(&conn->lock) { struct l2cap_chan *chan; @@ -1830,6 +1905,8 @@ static void l2cap_conn_del(struct hci_conn *hcon, int err) l2cap_chan_hold(chan); l2cap_chan_lock(chan); + lockdep_assert_held(&chan->conn->lock); + l2cap_chan_del(chan, err); chan->ops->close(chan); @@ -2995,6 +3072,7 @@ static void l2cap_pass_to_tx_fbit(struct l2cap_chan *chan, /* Copy frame to all raw sockets on that connection */ static void l2cap_raw_recv(struct l2cap_conn *conn, struct sk_buff *skb) + __must_hold(&conn->lock) { struct sk_buff *nskb; struct l2cap_chan *chan; @@ -3888,6 +3966,7 @@ static void l2cap_ecred_list_defer(struct l2cap_chan *chan, void *data) } struct l2cap_ecred_rsp_data { + struct l2cap_chan *locked_chan; struct { struct l2cap_ecred_conn_rsp_hdr rsp; __le16 scid[L2CAP_ECRED_MAX_CID]; @@ -3895,11 +3974,45 @@ struct l2cap_ecred_rsp_data { int count; }; +/* Lock @chan if it is not @locked_chan, and has same or lower nesting level. + * + * They must have the same chan->conn, and conn->lock must be held. + * + * Caller must ensure @chan has lock nesting level <= that of @locked_chan, as + * nested locking of l2cap_chan of different levels is allowed also without + * holding conn->lock. + * + * See l2cap.h for the global l2cap_chan locking rules. + */ +static bool l2cap_chan_try_sibling_lock(struct l2cap_chan *chan, + struct l2cap_chan *locked_chan) + __must_hold(&locked_chan->lock) + __must_hold(&locked_chan->conn->lock) + __cond_acquires(true, &chan->lock) +{ + if (chan == locked_chan) + return false; + + if (WARN_ON_ONCE(locked_chan->conn != chan->conn)) + return false; + + if (WARN_ON_ONCE(atomic_read(&locked_chan->nesting) + < atomic_read(&chan->nesting))) + return false; + + mutex_lock_nest_lock(&chan->lock, &locked_chan->conn->lock); + return true; +} + static void l2cap_ecred_rsp_defer(struct l2cap_chan *chan, void *data) { struct l2cap_ecred_rsp_data *rsp = data; struct l2cap_ecred_conn_rsp *rsp_flex = container_of(&rsp->pdu.rsp, struct l2cap_ecred_conn_rsp, hdr); + bool locked; + + if (chan->mode != L2CAP_MODE_EXT_FLOWCTL) + return; if (chan->mode != L2CAP_MODE_EXT_FLOWCTL) return; @@ -3911,6 +4024,22 @@ static void l2cap_ecred_rsp_defer(struct l2cap_chan *chan, void *data) !test_and_clear_bit(FLAG_DEFER_SETUP, &chan->flags)) return; + lockdep_assert_held(&rsp->locked_chan->lock); + lockdep_assert_held(&rsp->locked_chan->conn->lock); + + l2cap_chan_hold(chan); + + locked = l2cap_chan_try_sibling_lock(chan, rsp->locked_chan); + + /* Cannot occur: PARENT channels do not appear in chan_l, and SMP + * channels never have FLAG_DEFER_SETUP. + */ + if (context_unsafe(!locked && chan != rsp->locked_chan)) + goto done; + + lockdep_assert_held(&chan->lock); + lockdep_assert_held(&chan->conn->lock); + /* Reset ident so only one response is sent */ chan->ident = 0; @@ -3923,6 +4052,12 @@ static void l2cap_ecred_rsp_defer(struct l2cap_chan *chan, void *data) rsp_flex->dcid[rsp->count++] = cpu_to_le16(chan->scid); else l2cap_chan_del(chan, ECONNRESET); + +done: + if (locked) + l2cap_chan_unlock(chan); + + l2cap_chan_put(chan); } void __l2cap_ecred_conn_rsp_defer(struct l2cap_chan *chan) @@ -3934,11 +4069,15 @@ void __l2cap_ecred_conn_rsp_defer(struct l2cap_chan *chan) if (!id) return; + if (!test_bit(FLAG_DEFER_SETUP, &chan->flags)) + return; BT_DBG("chan %p id %d", chan, id); memset(&data, 0, sizeof(data)); + data.locked_chan = chan; + data.pdu.rsp.mtu = cpu_to_le16(chan->imtu); data.pdu.rsp.mps = cpu_to_le16(chan->mps); data.pdu.rsp.credits = cpu_to_le16(chan->rx_credits); @@ -4048,6 +4187,7 @@ static void l2cap_conf_rfc_get(struct l2cap_chan *chan, void *rsp, int len) static inline int l2cap_command_rej(struct l2cap_conn *conn, struct l2cap_cmd_hdr *cmd, u16 cmd_len, u8 *data) + __must_hold(&conn->lock) { struct l2cap_cmd_rej_unk *rej = (struct l2cap_cmd_rej_unk *) data; @@ -4080,6 +4220,8 @@ static inline int l2cap_command_rej(struct l2cap_conn *conn, */ static struct l2cap_chan *l2cap_new_connection(struct l2cap_conn *conn, struct l2cap_chan *pchan) + __must_hold(&conn->lock) + __must_hold(&pchan->lock) { struct l2cap_chan *chan; @@ -4087,23 +4229,31 @@ static struct l2cap_chan *l2cap_new_connection(struct l2cap_conn *conn, if (!chan) return NULL; + l2cap_chan_lock(chan); + l2cap_chan_set_defaults(chan, pchan); chan->ops = pchan->ops; __l2cap_chan_add(conn, chan); + lockdep_assert_held(&chan->conn->lock); + if (pchan->ops->new_connection && pchan->ops->new_connection(pchan, chan) < 0) { l2cap_chan_del(chan, 0); + l2cap_chan_unlock(chan); l2cap_chan_put(chan); return NULL; } + l2cap_chan_unlock(chan); + return chan; } static void l2cap_connect(struct l2cap_conn *conn, struct l2cap_cmd_hdr *cmd, u8 *data, u8 rsp_code) + __must_hold(&conn->lock) { struct l2cap_conn_req *req = (struct l2cap_conn_req *) data; struct l2cap_conn_rsp rsp; @@ -4120,7 +4270,13 @@ static void l2cap_connect(struct l2cap_conn *conn, struct l2cap_cmd_hdr *cmd, &conn->hcon->dst, ACL_LINK); if (!pchan) { result = L2CAP_CR_BAD_PSM; - goto response; + + rsp.scid = cpu_to_le16(scid); + rsp.dcid = cpu_to_le16(dcid); + rsp.result = cpu_to_le16(result); + rsp.status = cpu_to_le16(status); + l2cap_send_cmd(conn, cmd->ident, rsp_code, sizeof(rsp), &rsp); + return; } l2cap_chan_lock(pchan); @@ -4202,9 +4358,6 @@ response: rsp.status = cpu_to_le16(status); l2cap_send_cmd(conn, cmd->ident, rsp_code, sizeof(rsp), &rsp); - if (!pchan) - return; - if (result == L2CAP_CR_PEND && status == L2CAP_CS_NO_INFO) { struct l2cap_info_req info; info.type = cpu_to_le16(L2CAP_IT_FEAT_MASK); @@ -4233,6 +4386,7 @@ response: static int l2cap_connect_req(struct l2cap_conn *conn, struct l2cap_cmd_hdr *cmd, u16 cmd_len, u8 *data) + __must_hold(&conn->lock) { if (cmd_len < sizeof(struct l2cap_conn_req)) return -EPROTO; @@ -4244,6 +4398,7 @@ static int l2cap_connect_req(struct l2cap_conn *conn, static int l2cap_connect_create_rsp(struct l2cap_conn *conn, struct l2cap_cmd_hdr *cmd, u16 cmd_len, u8 *data) + __must_hold(&conn->lock) { struct l2cap_conn_rsp *rsp = (struct l2cap_conn_rsp *) data; u16 scid, dcid, result, status; @@ -4284,6 +4439,8 @@ static int l2cap_connect_create_rsp(struct l2cap_conn *conn, l2cap_chan_lock(chan); + lockdep_assert_held(&chan->conn->lock); + switch (result) { case L2CAP_CR_SUCCESS: if (__l2cap_get_chan_by_dcid(conn, dcid)) { @@ -4361,6 +4518,7 @@ static void cmd_reject_invalid_cid(struct l2cap_conn *conn, u8 ident, static inline int l2cap_config_req(struct l2cap_conn *conn, struct l2cap_cmd_hdr *cmd, u16 cmd_len, u8 *data) + __must_hold(&conn->lock) { struct l2cap_conf_req *req = (struct l2cap_conf_req *) data; u16 dcid, flags; @@ -4382,6 +4540,10 @@ static inline int l2cap_config_req(struct l2cap_conn *conn, return 0; } + l2cap_chan_lock(chan); + + lockdep_assert_held(&chan->conn->lock); + if (chan->state != BT_CONFIG && chan->state != BT_CONNECT2 && chan->state != BT_CONNECTED) { cmd_reject_invalid_cid(conn, cmd->ident, chan->scid, @@ -4472,6 +4634,7 @@ unlock: static inline int l2cap_config_rsp(struct l2cap_conn *conn, struct l2cap_cmd_hdr *cmd, u16 cmd_len, u8 *data) + __must_hold(&conn->lock) { struct l2cap_conf_rsp *rsp = (struct l2cap_conf_rsp *)data; u16 scid, flags, result; @@ -4493,6 +4656,10 @@ static inline int l2cap_config_rsp(struct l2cap_conn *conn, if (!chan) return 0; + l2cap_chan_lock(chan); + + lockdep_assert_held(&chan->conn->lock); + switch (result) { case L2CAP_CONF_SUCCESS: l2cap_conf_rfc_get(chan, rsp->data, len); @@ -4579,6 +4746,7 @@ done: static inline int l2cap_disconnect_req(struct l2cap_conn *conn, struct l2cap_cmd_hdr *cmd, u16 cmd_len, u8 *data) + __must_hold(&conn->lock) { struct l2cap_disconn_req *req = (struct l2cap_disconn_req *) data; struct l2cap_disconn_rsp rsp; @@ -4599,6 +4767,10 @@ static inline int l2cap_disconnect_req(struct l2cap_conn *conn, return 0; } + l2cap_chan_lock(chan); + + lockdep_assert_held(&chan->conn->lock); + rsp.dcid = cpu_to_le16(chan->scid); rsp.scid = cpu_to_le16(chan->dcid); l2cap_send_cmd(conn, cmd->ident, L2CAP_DISCONN_RSP, sizeof(rsp), &rsp); @@ -4618,6 +4790,7 @@ static inline int l2cap_disconnect_req(struct l2cap_conn *conn, static inline int l2cap_disconnect_rsp(struct l2cap_conn *conn, struct l2cap_cmd_hdr *cmd, u16 cmd_len, u8 *data) + __must_hold(&conn->lock) { struct l2cap_disconn_rsp *rsp = (struct l2cap_disconn_rsp *) data; u16 dcid, scid; @@ -4636,6 +4809,10 @@ static inline int l2cap_disconnect_rsp(struct l2cap_conn *conn, return 0; } + l2cap_chan_lock(chan); + + lockdep_assert_held(&chan->conn->lock); + if (chan->state != BT_DISCONN) { l2cap_chan_unlock(chan); l2cap_chan_put(chan); @@ -4703,6 +4880,7 @@ static inline int l2cap_information_req(struct l2cap_conn *conn, static inline int l2cap_information_rsp(struct l2cap_conn *conn, struct l2cap_cmd_hdr *cmd, u16 cmd_len, u8 *data) + __must_hold(&conn->lock) { struct l2cap_info_rsp *rsp = (struct l2cap_info_rsp *) data; u16 type, result; @@ -4810,6 +4988,7 @@ static inline int l2cap_conn_param_update_req(struct l2cap_conn *conn, static int l2cap_le_connect_rsp(struct l2cap_conn *conn, struct l2cap_cmd_hdr *cmd, u16 cmd_len, u8 *data) + __must_hold(&conn->lock) { struct l2cap_le_conn_rsp *rsp = (struct l2cap_le_conn_rsp *) data; struct hci_conn *hcon = conn->hcon; @@ -4846,6 +5025,8 @@ static int l2cap_le_connect_rsp(struct l2cap_conn *conn, l2cap_chan_lock(chan); + lockdep_assert_held(&chan->conn->lock); + switch (result) { case L2CAP_CR_LE_SUCCESS: if (__l2cap_get_chan_by_dcid(conn, dcid)) { @@ -4916,6 +5097,7 @@ static void l2cap_put_ident(struct l2cap_conn *conn, u8 code, u8 id) static inline int l2cap_bredr_sig_cmd(struct l2cap_conn *conn, struct l2cap_cmd_hdr *cmd, u16 cmd_len, u8 *data) + __must_hold(&conn->lock) { int err = 0; @@ -4977,6 +5159,7 @@ static inline int l2cap_bredr_sig_cmd(struct l2cap_conn *conn, static int l2cap_le_connect_req(struct l2cap_conn *conn, struct l2cap_cmd_hdr *cmd, u16 cmd_len, u8 *data) + __must_hold(&conn->lock) { struct l2cap_le_conn_req *req = (struct l2cap_le_conn_req *) data; struct l2cap_le_conn_rsp rsp; @@ -5060,6 +5243,10 @@ static int l2cap_le_connect_req(struct l2cap_conn *conn, goto response_unlock; } + l2cap_chan_lock(chan); + + lockdep_assert_held(&chan->conn->lock); + bacpy(&chan->src, &conn->hcon->src); bacpy(&chan->dst, &conn->hcon->dst); chan->src_type = bdaddr_src_type(conn->hcon); @@ -5093,6 +5280,8 @@ static int l2cap_le_connect_req(struct l2cap_conn *conn, result = L2CAP_CR_LE_SUCCESS; } + l2cap_chan_unlock(chan); + response_unlock: l2cap_chan_unlock(pchan); l2cap_chan_put(pchan); @@ -5121,6 +5310,7 @@ response: static inline int l2cap_le_credits(struct l2cap_conn *conn, struct l2cap_cmd_hdr *cmd, u16 cmd_len, u8 *data) + __must_hold(&conn->lock) { struct l2cap_le_credits *pkt; struct l2cap_chan *chan; @@ -5139,6 +5329,8 @@ static inline int l2cap_le_credits(struct l2cap_conn *conn, if (!chan) return -EBADSLT; + l2cap_chan_lock(chan); + max_credits = LE_FLOWCTL_MAX_CREDITS - chan->tx_credits; if (credits > max_credits) { BT_ERR("LE credits overflow"); @@ -5168,6 +5360,7 @@ unlock: static inline int l2cap_ecred_conn_req(struct l2cap_conn *conn, struct l2cap_cmd_hdr *cmd, u16 cmd_len, u8 *data) + __must_hold(&conn->lock) { struct l2cap_ecred_conn_req *req = (void *) data; DEFINE_RAW_FLEX(struct l2cap_ecred_conn_rsp, pdu, dcid, L2CAP_ECRED_MAX_CID); @@ -5283,6 +5476,10 @@ static inline int l2cap_ecred_conn_req(struct l2cap_conn *conn, continue; } + l2cap_chan_lock(chan); + + lockdep_assert_held(&chan->conn->lock); + bacpy(&chan->src, &conn->hcon->src); bacpy(&chan->dst, &conn->hcon->dst); chan->src_type = bdaddr_src_type(conn->hcon); @@ -5315,6 +5512,8 @@ static inline int l2cap_ecred_conn_req(struct l2cap_conn *conn, } else { l2cap_chan_ready(chan); } + + l2cap_chan_unlock(chan); } unlock: @@ -5336,6 +5535,7 @@ response: static inline int l2cap_ecred_conn_rsp(struct l2cap_conn *conn, struct l2cap_cmd_hdr *cmd, u16 cmd_len, u8 *data) + __must_hold(&conn->lock) { struct l2cap_ecred_conn_rsp *rsp = (void *) data; struct hci_conn *hcon = conn->hcon; @@ -5366,12 +5566,16 @@ static inline int l2cap_ecred_conn_rsp(struct l2cap_conn *conn, chan->state == BT_CONNECTED) continue; + l2cap_chan_hold(chan); l2cap_chan_lock(chan); + lockdep_assert_held(&chan->conn->lock); + /* Check that there is a dcid for each pending channel */ if (cmd_len < sizeof(dcid)) { l2cap_chan_del(chan, ECONNREFUSED); l2cap_chan_unlock(chan); + l2cap_chan_put(chan); continue; } @@ -5410,6 +5614,8 @@ static inline int l2cap_ecred_conn_rsp(struct l2cap_conn *conn, __set_chan_timer(orig, 0); l2cap_chan_unlock(orig); } + + l2cap_chan_put(chan); continue; } @@ -5455,6 +5661,7 @@ static inline int l2cap_ecred_conn_rsp(struct l2cap_conn *conn, } l2cap_chan_unlock(chan); + l2cap_chan_put(chan); } return err; @@ -5463,6 +5670,7 @@ static inline int l2cap_ecred_conn_rsp(struct l2cap_conn *conn, static inline int l2cap_ecred_reconf_req(struct l2cap_conn *conn, struct l2cap_cmd_hdr *cmd, u16 cmd_len, u8 *data) + __must_hold(&conn->lock) { struct l2cap_ecred_reconf_req *req = (void *) data; struct l2cap_ecred_reconf_rsp rsp; @@ -5561,6 +5769,7 @@ respond: static inline int l2cap_ecred_reconf_rsp(struct l2cap_conn *conn, struct l2cap_cmd_hdr *cmd, u16 cmd_len, u8 *data) + __must_hold(&conn->lock) { struct l2cap_chan *chan, *tmp; struct l2cap_ecred_reconf_rsp *rsp = (void *)data; @@ -5589,6 +5798,8 @@ static inline int l2cap_ecred_reconf_rsp(struct l2cap_conn *conn, continue; l2cap_chan_lock(chan); + lockdep_assert_held(&chan->conn->lock); + l2cap_chan_del(chan, ECONNRESET); l2cap_chan_unlock(chan); @@ -5601,6 +5812,7 @@ static inline int l2cap_ecred_reconf_rsp(struct l2cap_conn *conn, static inline int l2cap_le_command_rej(struct l2cap_conn *conn, struct l2cap_cmd_hdr *cmd, u16 cmd_len, u8 *data) + __must_hold(&conn->lock) { struct l2cap_cmd_rej_unk *rej = (struct l2cap_cmd_rej_unk *) data; struct l2cap_chan *chan; @@ -5617,6 +5829,7 @@ static inline int l2cap_le_command_rej(struct l2cap_conn *conn, goto done; l2cap_chan_lock(chan); + lockdep_assert_held(&chan->conn->lock); l2cap_chan_del(chan, ECONNREFUSED); l2cap_chan_unlock(chan); l2cap_chan_put(chan); @@ -5628,6 +5841,7 @@ done: static inline int l2cap_le_sig_cmd(struct l2cap_conn *conn, struct l2cap_cmd_hdr *cmd, u16 cmd_len, u8 *data) + __must_hold(&conn->lock) { int err = 0; @@ -5692,6 +5906,7 @@ static inline int l2cap_le_sig_cmd(struct l2cap_conn *conn, static inline void l2cap_le_sig_channel(struct l2cap_conn *conn, struct sk_buff *skb) + __must_hold(&conn->lock) { struct hci_conn *hcon = conn->hcon; struct l2cap_cmd_hdr *cmd; @@ -5750,6 +5965,7 @@ static inline void l2cap_sig_send_mtu_rej(struct l2cap_conn *conn, u8 ident) static inline void l2cap_sig_channel(struct l2cap_conn *conn, struct sk_buff *skb) + __must_hold(&conn->lock) { struct hci_conn *hcon = conn->hcon; struct l2cap_cmd_hdr *cmd; @@ -6987,6 +7203,7 @@ failed: static void l2cap_data_channel(struct l2cap_conn *conn, u16 cid, struct sk_buff *skb) + __must_hold(&conn->lock) { struct l2cap_chan *chan; @@ -6998,6 +7215,10 @@ static void l2cap_data_channel(struct l2cap_conn *conn, u16 cid, return; } + l2cap_chan_lock(chan); + + lockdep_assert_held(&chan->conn->lock); + BT_DBG("chan %p, len %d", chan, skb->len); /* If we receive data on a fixed channel before the info req/rsp @@ -7093,6 +7314,7 @@ free_skb: } static void l2cap_recv_frame(struct l2cap_conn *conn, struct sk_buff *skb) + __must_hold(&conn->lock) { struct l2cap_hdr *lh = (void *) skb->data; struct hci_conn *hcon = conn->hcon; @@ -7202,9 +7424,9 @@ static struct l2cap_conn *l2cap_conn_add(struct hci_conn *hcon) hci_dev_test_flag(hcon->hdev, HCI_FORCE_BREDR_SMP))) conn->local_fixed_chan |= L2CAP_FC_SMP_BREDR; - mutex_init(&conn->lock); - - INIT_LIST_HEAD(&conn->chan_l); + scoped_guard(mutex_init, &conn->lock) { + INIT_LIST_HEAD(&conn->chan_l); + } INIT_LIST_HEAD(&conn->users); INIT_DELAYED_WORK(&conn->info_timer, l2cap_info_timeout); @@ -7408,7 +7630,8 @@ int l2cap_chan_connect(struct l2cap_chan *chan, __le16 psm, u16 cid, } } - if (cid && __l2cap_get_chan_by_dcid(conn, cid)) { + if ((cid && __l2cap_get_chan_by_dcid(conn, cid)) || chan->conn || + test_bit(FLAG_DEL, &chan->flags)) { hci_conn_drop(hcon); err = -EBUSY; goto chan_unlock; @@ -7420,6 +7643,8 @@ int l2cap_chan_connect(struct l2cap_chan *chan, __le16 psm, u16 cid, __l2cap_chan_add(conn, chan); + lockdep_assert_held(&chan->conn->lock); + /* l2cap_chan_add takes its own ref so we can drop this one */ hci_conn_drop(hcon); @@ -7583,6 +7808,8 @@ static void l2cap_connect_cfm(struct hci_conn *hcon, u8 status) * we left off, because the list lock would prevent calling the * potentially sleeping l2cap_chan_lock() function. */ + mutex_lock(&conn->lock); + pchan = l2cap_global_fixed_chan(NULL, hcon); while (pchan) { struct l2cap_chan *chan, *next; @@ -7607,6 +7834,8 @@ next: pchan = next; } + mutex_unlock(&conn->lock); + l2cap_conn_ready(conn); } @@ -7638,6 +7867,8 @@ static void l2cap_disconn_cfm(struct hci_conn *hcon, u8 reason) } static inline void l2cap_check_encryption(struct l2cap_chan *chan, u8 encrypt) + __must_hold(&chan->lock) + __must_hold(&chan->conn->lock) { if (chan->chan_type != L2CAP_CHAN_CONN_ORIENTED) return; @@ -7647,7 +7878,7 @@ static inline void l2cap_check_encryption(struct l2cap_chan *chan, u8 encrypt) __set_chan_timer(chan, L2CAP_ENC_TIMEOUT); } else if (chan->sec_level == BT_SECURITY_HIGH || chan->sec_level == BT_SECURITY_FIPS) - l2cap_chan_close(chan, ECONNREFUSED); + __l2cap_chan_close(chan, ECONNREFUSED); } else { if (chan->sec_level == BT_SECURITY_MEDIUM) __clear_chan_timer(chan); @@ -7670,6 +7901,8 @@ static void l2cap_security_cfm(struct hci_conn *hcon, u8 status, u8 encrypt) list_for_each_entry(chan, &conn->chan_l, list) { l2cap_chan_lock(chan); + lockdep_assert_held(&chan->conn->lock); + BT_DBG("chan %p scid 0x%4.4x state %s", chan, chan->scid, state_to_string(chan->state)); diff --git a/net/bluetooth/l2cap_sock.c b/net/bluetooth/l2cap_sock.c index 1194c37e466f..278adb05c4c9 100644 --- a/net/bluetooth/l2cap_sock.c +++ b/net/bluetooth/l2cap_sock.c @@ -109,6 +109,7 @@ static int l2cap_sock_bind(struct socket *sock, struct sockaddr_unsized *addr, i return -EINVAL; } + l2cap_chan_lock(chan); lock_sock(sk); if (sk->sk_state != BT_OPEN) { @@ -174,6 +175,7 @@ static int l2cap_sock_bind(struct socket *sock, struct sockaddr_unsized *addr, i done: release_sock(sk); + l2cap_chan_unlock(chan); return err; } @@ -1243,40 +1245,68 @@ static void l2cap_publish_rx_avail(struct l2cap_chan *chan) l2cap_chan_rx_avail(chan, -1); } -static int l2cap_sock_recvmsg(struct socket *sock, struct msghdr *msg, - size_t len, int flags) +static int l2cap_sock_defer(struct sock *sk) { - struct sock *sk = sock->sk; - struct l2cap_pinfo *pi = l2cap_pi(sk); - int err; + struct l2cap_chan *chan = l2cap_pi(sk)->chan; + bool have_conn; + int err = 0; - if (unlikely(flags & MSG_ERRQUEUE)) - return sock_recv_errqueue(sk, msg, len, SOL_BLUETOOTH, - BT_SCM_ERROR); + /* Fast path check */ + lock_sock(sk); + if (sk->sk_state != BT_CONNECT2) { + release_sock(sk); + return 0; + } + release_sock(sk); + have_conn = l2cap_chan_lock_conn(chan); lock_sock(sk); if (sk->sk_state == BT_CONNECT2 && test_bit(BT_SK_DEFER_SETUP, &bt_sk(sk)->flags)) { - if (pi->chan->mode == L2CAP_MODE_EXT_FLOWCTL) { + err = 1; + + if (!have_conn) { + release_sock(sk); + err = -ENOTCONN; + } else if (chan->mode == L2CAP_MODE_EXT_FLOWCTL) { sk->sk_state = BT_CONNECTED; - pi->chan->state = BT_CONNECTED; - __l2cap_ecred_conn_rsp_defer(pi->chan); - } else if (bdaddr_type_is_le(pi->chan->src_type)) { + chan->state = BT_CONNECTED; + release_sock(sk); + __l2cap_ecred_conn_rsp_defer(chan); + } else if (bdaddr_type_is_le(chan->src_type)) { sk->sk_state = BT_CONNECTED; - pi->chan->state = BT_CONNECTED; - __l2cap_le_connect_rsp_defer(pi->chan); + chan->state = BT_CONNECTED; + release_sock(sk); + __l2cap_le_connect_rsp_defer(chan); } else { sk->sk_state = BT_CONFIG; - pi->chan->state = BT_CONFIG; - __l2cap_connect_rsp_defer(pi->chan); + chan->state = BT_CONFIG; + release_sock(sk); + __l2cap_connect_rsp_defer(chan); } - - err = 0; - goto done; + } else { + release_sock(sk); } - release_sock(sk); + l2cap_chan_unlock_conn(chan, have_conn); + return err; +} + +static int l2cap_sock_recvmsg(struct socket *sock, struct msghdr *msg, + size_t len, int flags) +{ + struct sock *sk = sock->sk; + struct l2cap_pinfo *pi = l2cap_pi(sk); + int err; + + if (unlikely(flags & MSG_ERRQUEUE)) + return sock_recv_errqueue(sk, msg, len, SOL_BLUETOOTH, + BT_SCM_ERROR); + + err = l2cap_sock_defer(sk); + if (err) + return err < 0 ? err : 0; if (sock->type == SOCK_STREAM) err = bt_sock_stream_recvmsg(sock, msg, len, flags); @@ -1406,7 +1436,6 @@ static int l2cap_sock_shutdown(struct socket *sock, int how) { struct sock *sk = sock->sk; struct l2cap_chan *chan; - struct l2cap_conn *conn; int err = 0; BT_DBG("sock %p, sk %p, how %d", sock, sk, how); @@ -1463,23 +1492,7 @@ static int l2cap_sock_shutdown(struct socket *sock, int how) sk->sk_shutdown |= SEND_SHUTDOWN; release_sock(sk); - l2cap_chan_lock(chan); - /* prevent conn structure from being freed */ - conn = l2cap_conn_hold_unless_zero(chan->conn); - l2cap_chan_unlock(chan); - - if (conn) - /* mutex lock must be taken before l2cap_chan_lock() */ - mutex_lock(&conn->lock); - - l2cap_chan_lock(chan); - l2cap_chan_close(chan, 0); - l2cap_chan_unlock(chan); - - if (conn) { - mutex_unlock(&conn->lock); - l2cap_conn_put(conn); - } + l2cap_chan_close_unlocked(chan, 0); lock_sock(sk); @@ -1784,6 +1797,7 @@ static void l2cap_sock_state_change_cb(struct l2cap_chan *chan, int state, static struct sk_buff *l2cap_sock_alloc_skb_cb(struct l2cap_chan *chan, unsigned long hdr_len, unsigned long len, int nb) + __must_hold(&chan->lock) { struct sock *sk = chan->data; struct sk_buff *skb; diff --git a/net/bluetooth/mgmt.c b/net/bluetooth/mgmt.c index ac4864e56ec7..fd045460e236 100644 --- a/net/bluetooth/mgmt.c +++ b/net/bluetooth/mgmt.c @@ -301,6 +301,8 @@ static u8 mgmt_errno_status(int err) return MGMT_STATUS_ALREADY_CONNECTED; case -ENOTCONN: return MGMT_STATUS_DISCONNECTED; + case -ECANCELED: + return MGMT_STATUS_CANCELLED; } return MGMT_STATUS_FAILED; @@ -5675,8 +5677,18 @@ static void mgmt_remove_adv_monitor_complete(struct hci_dev *hdev, struct mgmt_pending_cmd *cmd = data; struct mgmt_cp_remove_adv_monitor *cp; - if (status == -ECANCELED) + /* Reply to a cancelled command so bluetoothd's serialised mgmt queue + * is not blocked. + */ + if (status == -ECANCELED) { + cp = cmd->param; + rp.monitor_handle = cp->monitor_handle; + + mgmt_cmd_complete(cmd->sk, cmd->hdev->id, cmd->opcode, + mgmt_status(status), &rp, sizeof(rp)); + mgmt_pending_free(cmd); return; + } hci_dev_lock(hdev); diff --git a/scripts/context-analysis-suppression.txt b/scripts/context-analysis-suppression.txt index 1c51b6153f08..d4476d9ed10a 100644 --- a/scripts/context-analysis-suppression.txt +++ b/scripts/context-analysis-suppression.txt @@ -32,3 +32,4 @@ src:*include/linux/seqlock*.h=emit src:*include/linux/spinlock*.h=emit src:*include/linux/srcu*.h=emit src:*include/linux/ww_mutex.h=emit +src:*include/net/bluetooth/*=emit |
