summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMark Brown <broonie@kernel.org>2026-09-07 13:26:23 +0100
committerMark Brown <broonie@kernel.org>2026-09-07 13:26:23 +0100
commit7e615d9c216c1c9c15c8894e3ab507cf4caa7013 (patch)
treed8019a69e1e209110d5618d98d486355ce660757
parent01775b83ba0c2f397024723a1a49490147bc7200 (diff)
parent755cf7adf8dd2d12627cb7de223d35b12228e2f5 (diff)
downloadlinux-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
-rw-r--r--Documentation/devicetree/bindings/net/bluetooth/brcm,bluetooth.yaml1
-rw-r--r--drivers/bluetooth/btbcm.c6
-rw-r--r--drivers/bluetooth/btintel.c7
-rw-r--r--drivers/bluetooth/btintel.h3
-rw-r--r--drivers/bluetooth/btintel_pcie.c1390
-rw-r--r--drivers/bluetooth/btintel_pcie.h254
-rw-r--r--drivers/bluetooth/btmrvl_main.c4
-rw-r--r--drivers/bluetooth/btmtk.c15
-rw-r--r--drivers/bluetooth/btmtk.h1
-rw-r--r--drivers/bluetooth/btmtksdio.c35
-rw-r--r--drivers/bluetooth/btqcomsmd.c6
-rw-r--r--drivers/bluetooth/btrtl.c5
-rw-r--r--drivers/bluetooth/btusb.c322
-rw-r--r--drivers/bluetooth/hci_bcm.c1
-rw-r--r--drivers/bluetooth/hci_h4.c123
-rw-r--r--drivers/bluetooth/hci_serdev.c46
-rw-r--r--drivers/bluetooth/hci_uart.h39
-rw-r--r--drivers/bluetooth/virtio_bt.c21
-rw-r--r--include/net/bluetooth/hci_core.h44
-rw-r--r--include/net/bluetooth/hci_h4.h60
-rw-r--r--include/net/bluetooth/l2cap.h102
-rw-r--r--net/bluetooth/6lowpan.c44
-rw-r--r--net/bluetooth/Makefile2
-rw-r--r--net/bluetooth/bnep/core.c4
-rw-r--r--net/bluetooth/hci_core.c2
-rw-r--r--net/bluetooth/hci_h4.c160
-rw-r--r--net/bluetooth/hci_sync.c102
-rw-r--r--net/bluetooth/hci_sysfs.c17
-rw-r--r--net/bluetooth/l2cap_core.c287
-rw-r--r--net/bluetooth/l2cap_sock.c90
-rw-r--r--net/bluetooth/mgmt.c14
-rw-r--r--scripts/context-analysis-suppression.txt1
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,
+ &regions_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,
+ &regions_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,
+ &regions_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,
+ &regions_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