[PATCH v6] Bluetooth: btrtl: Add firmware format v3 support
Hilda Wu <[email protected]>
| Newsgroups | org.kernel.vger.linux-bluetooth,org.kernel.vger.linux-kernel |
|---|---|
| Message-ID | <[email protected]> |
Realtek has introduced a new Bluetooth firmware image format, firmware format v3. This is a Realtek-internal specification; the format differences from v2 are documented inline in this patch. This patch extends the btrtl driver to recognize and parse the new v3 file format, including: - New signature string and image ID definitions - Extension of btrtl_device_info to store v3-specific metadata - Logic to extract and load firmware data out of v3 images - Maintains compatibility with the existing v2 firmware format The RTL8922D is the first IC to use firmware format v3. The firmware image (rtl8922du_fw.bin) payload is approximately 267 KB (0x40a7c bytes), and on a typical boot the full sequence from rtl_load_file() to rtl_finalize_download() completing takes average ~483 ms. The following is RTL8922D log as expected fw format v3 output: [ 1.945295] Bluetooth: btrtl_read_chip_id() hci0: RTL: chip_id status=0x00 id=0x3e [ 1.945547] Bluetooth: btrtl_initialize() hci0: RTL: examining hci_ver=0e hci_rev=000d lmp_ver=0e lmp_subver=8922 [ 1.945789] Bluetooth: rtl_read_rom_version() hci0: RTL: rom_version status=0 version=0 [ 1.946034] Bluetooth: btrtl_initialize() hci0: RTL: btrtl_initialize: key id 0 [ 1.946038] Bluetooth: rtl_load_file() hci0: RTL: loading rtl_bt/rtl8922du_fw.bin [ 1.950522] Bluetooth: rtl_load_file() hci0: RTL: loading rtl_bt/rtl8922du_config.bin [ 1.950837] Bluetooth: rtlbt_parse_firmware_v3() hci0: RTL: key id 0 [ 1.950844] Bluetooth: rtlbt_parse_section_v3() hci0: RTL: image (f000:00), chip id 62, cut 0x01, len 0000c704 [ 1.950862] Bluetooth: rtlbt_parse_section_v3() hci0: RTL: image version: c5900782 [ 1.950865] Bluetooth: rtlbt_parse_config() hci0: RTL: config file: rtl_bt/rtl8922du_config_f000.bin [ 1.950882] Bluetooth: rtlbt_parse_section_v3() hci0: RTL: image (f002:00), chip id 62, cut 0x01, len 00034378 [ 1.950922] Bluetooth: rtlbt_parse_section_v3() hci0: RTL: image version: 2b84a8b2 [ 1.950925] Bluetooth: rtlbt_parse_config() hci0: RTL: config file: rtl_bt/rtl8922du_config_f002.bin [ 1.951157] Bluetooth: rtlbt_parse_config() hci0: RTL: config file: rtl_bt/rtl8922du_config_f002.bin found [ 1.951164] Bluetooth: rtlbt_parse_section_v3() hci0: RTL: image (f000:00), chip id 62, cut 0x01, len 0000c704 [ 1.951169] Bluetooth: rtlbt_parse_section_v3() hci0: RTL: skip image, key_id mismatch (1, 0) [ 1.951177] Bluetooth: rtlbt_parse_section_v3() hci0: RTL: image (f002:00), chip id 62, cut 0x01, len 00034378 [ 1.951179] Bluetooth: rtlbt_parse_section_v3() hci0: RTL: skip image, key_id mismatch (1, 0) [ 1.951181] Bluetooth: rtlbt_parse_section_v3() hci0: RTL: image (f000:00), chip id 62, cut 0x01, len 0000c704 [ 1.951184] Bluetooth: rtlbt_parse_section_v3() hci0: RTL: skip image, key_id mismatch (2, 0) [ 1.951185] Bluetooth: rtlbt_parse_section_v3() hci0: RTL: image (f002:00), chip id 62, cut 0x01, len 00034378 [ 1.951187] Bluetooth: rtlbt_parse_section_v3() hci0: RTL: skip image, key_id mismatch (2, 0) [ 1.951189] Bluetooth: rtlbt_parse_firmware_v3() hci0: RTL: image payload total len: 0x00040a7c [ 2.429887] Bluetooth: rtl_finalize_download() hci0: RTL: Watchdog reset status 00 [ 2.430162] Bluetooth: rtl_finalize_download() hci0: RTL: fw version 0x2b84a8b2 Signed-off-by: Alex Lu <[email protected]> Signed-off-by: Zoey Zhou <[email protected]> Signed-off-by: Hilda Wu <[email protected]> --- V5 -> V6: - Add missing parentheses around the entire is_v3_fw() macro expression to avoid incorrect evaluation with the logical NOT operator on legacy firmware types - Add NULL check on ic_info->cfg_name before use in rtlbt_parse_config() to avoid NULL pointer dereference when a crafted firmware specifies FW_TYPE_V3_2 on legacy chips without configuration - Add bounds check to reject patch_image_len < 4 before reading the image version, preventing an out-of-bounds read - Propagate the return value of rtlbt_parse_config() and abort parsing on failure instead of silently continuing without the required configuration - Fix a race condition in rtl_check_download_state() by setting the REALTEK_DOWNLOADING flag before issuing the check-download command, instead of after, to avoid missing the completion event - Split v3 firmware parse/download logic into new btrtl_enh.c to keep btrtl.c size manageable V4 -> V5: - Add independent support for RTL8922D section - Introduce macros to improve code readability - Document firmware format v3 and its differences - Align implementation with reviewer feedback V3 -> V4: - Rework skb->data access and add clarifying comments - Fix latent issues V2 -> V3: - Address coccinelle warning V1 -> V2: - Add missing symbols - Resolve build warnings --- --- drivers/bluetooth/Makefile | 2 +- drivers/bluetooth/btrtl.c | 217 ++++++--- drivers/bluetooth/btrtl.h | 109 ++++- drivers/bluetooth/btrtl_enh.c | 809 ++++++++++++++++++++++++++++++++++ drivers/bluetooth/btusb.c | 3 + 5 files changed, 1081 insertions(+), 59 deletions(-) create mode 100644 drivers/bluetooth/btrtl_enh.c diff --git a/drivers/bluetooth/Makefile b/drivers/bluetooth/Makefile index e6b1c1180d1d..29cfbe046365 100644 --- a/drivers/bluetooth/Makefile +++ b/drivers/bluetooth/Makefile @@ -22,7 +22,7 @@ obj-$(CONFIG_BT_MTKSDIO) += btmtksdio.o obj-$(CONFIG_BT_MTKUART) += btmtkuart.o obj-$(CONFIG_BT_QCOMSMD) += btqcomsmd.o obj-$(CONFIG_BT_BCM) += btbcm.o -obj-$(CONFIG_BT_RTL) += btrtl.o +obj-$(CONFIG_BT_RTL) += btrtl_enh.o btrtl.o obj-$(CONFIG_BT_QCA) += btqca.o obj-$(CONFIG_BT_MTK) += btmtk.o diff --git a/drivers/bluetooth/btrtl.c b/drivers/bluetooth/btrtl.c index 7f54d2d2d13a..52a6d5d3c5e6 100644 --- a/drivers/bluetooth/btrtl.c +++ b/drivers/bluetooth/btrtl.c @@ -22,6 +22,7 @@ #define RTL_CHIP_8723CS_XX 5 #define RTL_EPATCH_SIGNATURE "Realtech" #define RTL_EPATCH_SIGNATURE_V2 "RTBTCore" +#define RTL_EPATCH_SIGNATURE_V3 "BTNIC003" #define RTL_ROM_LMP_8703B 0x8703 #define RTL_ROM_LMP_8723A 0x1200 #define RTL_ROM_LMP_8723B 0x8723 @@ -33,16 +34,14 @@ #define RTL_ROM_LMP_8922A 0x8922 #define RTL_CONFIG_MAGIC 0x8723ab55 -#define RTL_VSC_OP_COREDUMP 0xfcff - #define IC_MATCH_FL_LMPSUBV (1 << 0) #define IC_MATCH_FL_HCIREV (1 << 1) #define IC_MATCH_FL_HCIVER (1 << 2) #define IC_MATCH_FL_HCIBUS (1 << 3) #define IC_MATCH_FL_CHIP_TYPE (1 << 4) #define IC_INFO(lmps, hcir, hciv, bus) \ - .match_flags = IC_MATCH_FL_LMPSUBV | IC_MATCH_FL_HCIREV | \ - IC_MATCH_FL_HCIVER | IC_MATCH_FL_HCIBUS, \ + .match_flags = (IC_MATCH_FL_LMPSUBV | IC_MATCH_FL_HCIREV | \ + IC_MATCH_FL_HCIVER | IC_MATCH_FL_HCIBUS), \ .lmp_subver = (lmps), \ .hci_rev = (hcir), \ .hci_ver = (hciv), \ @@ -50,7 +49,8 @@ #define RTL_CHIP_SUBVER (&(struct rtl_vendor_cmd) {{0x10, 0x38, 0x04, 0x28, 0x80}}) #define RTL_CHIP_REV (&(struct rtl_vendor_cmd) {{0x10, 0x3A, 0x04, 0x28, 0x80}}) -#define RTL_SEC_PROJ (&(struct rtl_vendor_cmd) {{0x10, 0xA4, 0xAD, 0x00, 0xb0}}) +#define RTL_SEC_PROJ_V2 (&(struct rtl_vendor_cmd) {{0x10, 0xA4, 0xAD, 0x00, 0xb0}}) +#define RTL_SEC_PROJ_V3 (&(struct rtl_vendor_cmd) {{0x10, 0xA4, 0x0D, 0x01, 0xa0}}) #define RTL_PATCH_SNIPPETS 0x01 #define RTL_PATCH_DUMMY_HEADER 0x02 @@ -75,34 +75,6 @@ enum btrtl_chip_id { CHIP_ID_8761C = 51, }; -struct id_table { - __u16 match_flags; - __u16 lmp_subver; - __u16 hci_rev; - __u8 hci_ver; - __u8 hci_bus; - __u8 chip_type; - bool config_needed; - bool has_rom_version; - bool has_msft_ext; - char *fw_name; - char *cfg_name; - char *hw_info; -}; - -struct btrtl_device_info { - const struct id_table *ic_info; - u8 rom_version; - u8 *fw_data; - int fw_len; - u8 *cfg_data; - int cfg_len; - bool drop_fw; - int project_id; - u8 key_id; - struct list_head patch_subsecs; -}; - static const struct id_table ic_id_table[] = { /* 8723A */ { IC_INFO(RTL_ROM_LMP_8723A, 0xb, 0x6, HCI_USB), @@ -337,6 +309,7 @@ static const struct id_table ic_id_table[] = { .fw_name = "rtl_bt/rtl8852btu_fw", .cfg_name = "rtl_bt/rtl8852btu_config", .hw_info = "rtl8852btu" }, + }; static const struct id_table *btrtl_match_ic(u16 lmp_subver, u16 hci_rev, @@ -371,7 +344,34 @@ static const struct id_table *btrtl_match_ic(u16 lmp_subver, u16 hci_rev, return &ic_id_table[i]; } -static struct sk_buff *btrtl_read_local_version(struct hci_dev *hdev) +int btrtl_read_chip_id(struct hci_dev *hdev, u8 *chip_id) +{ + struct rtl_rp_read_chip_id *rp; + struct sk_buff *skb; + int ret = 0; + + skb = __hci_cmd_sync(hdev, RTL_VSC_OP_READ_CHIP_ID, 0, NULL, HCI_INIT_TIMEOUT); + if (IS_ERR(skb)) + return PTR_ERR(skb); + + rp = skb_pull_data(skb, sizeof(*rp)); + if (!rp) { + ret = -EIO; + goto out; + } + + rtl_dev_info(hdev, "chip_id status=0x%02x id=0x%02x", + rp->status, rp->chip_id); + + if (chip_id) + *chip_id = rp->chip_id; + +out: + kfree_skb(skb); + return ret; +} + +struct sk_buff *btrtl_read_local_version(struct hci_dev *hdev) { struct sk_buff *skb; @@ -391,14 +391,14 @@ static struct sk_buff *btrtl_read_local_version(struct hci_dev *hdev) return skb; } +EXPORT_SYMBOL_GPL(btrtl_read_local_version); static int rtl_read_rom_version(struct hci_dev *hdev, u8 *version) { struct rtl_rom_version_evt *rom_version; struct sk_buff *skb; - /* Read RTL ROM version command */ - skb = __hci_cmd_sync(hdev, 0xfc6d, 0, NULL, HCI_INIT_TIMEOUT); + skb = __hci_cmd_sync(hdev, RTL_VSC_OP_READ_ROM_VER, 0, NULL, HCI_INIT_TIMEOUT); if (IS_ERR(skb)) { rtl_dev_err(hdev, "Read ROM version failed (%ld)", PTR_ERR(skb)); @@ -427,7 +427,7 @@ static int btrtl_vendor_read_reg16(struct hci_dev *hdev, struct sk_buff *skb; int err = 0; - skb = __hci_cmd_sync(hdev, 0xfc61, sizeof(*cmd), cmd, + skb = __hci_cmd_sync(hdev, RTL_VSC_OP_READ_VENDER, sizeof(*cmd), cmd, HCI_INIT_TIMEOUT); if (IS_ERR(skb)) { err = PTR_ERR(skb); @@ -449,7 +449,7 @@ static int btrtl_vendor_read_reg16(struct hci_dev *hdev, return 0; } -static void *rtl_iov_pull_data(struct rtl_iovec *iov, u32 len) +void *rtl_iov_pull_data(struct rtl_iovec *iov, u32 len) { void *data = iov->data; @@ -461,6 +461,7 @@ static void *rtl_iov_pull_data(struct rtl_iovec *iov, u32 len) return data; } +EXPORT_SYMBOL_GPL(rtl_iov_pull_data); static void btrtl_insert_ordered_subsec(struct rtl_subsection *node, struct btrtl_device_info *btrtl_dev) @@ -633,6 +634,7 @@ static int rtlbt_parse_firmware_v2(struct hci_dev *hdev, } *_buf = ptr; + btrtl_dev->fw_type = FW_TYPE_V2; return len; } @@ -678,6 +680,22 @@ static int rtlbt_parse_firmware(struct hci_dev *hdev, if (btrtl_dev->fw_len <= 8) return -EINVAL; + if (!memcmp(btrtl_dev->fw_data, RTL_EPATCH_SIGNATURE_V3, 8)) { + struct btrtl_enh_ops *ops = symbol_get(rtl_enh_ops); + int ret; + + if (!ops) + return -EINVAL; + + if (ops->parse_firmware_v3) + ret = ops->parse_firmware_v3(hdev, btrtl_dev); + else + ret = -EINVAL; + + symbol_put(rtl_enh_ops); + return ret; + } + if (!memcmp(btrtl_dev->fw_data, RTL_EPATCH_SIGNATURE, 8)) min_size = sizeof(struct rtl_epatch_header) + sizeof(extension_sig) + 3; @@ -798,8 +816,8 @@ static int rtlbt_parse_firmware(struct hci_dev *hdev, BT_DBG("length=%x offset=%x index %d", patch_length, patch_offset, i); if (patch_length < sizeof(epatch_info->fw_version) || - patch_offset > btrtl_dev->fw_len || - patch_length > btrtl_dev->fw_len - patch_offset) + check_add_overflow((size_t)patch_offset, (size_t)patch_length, &min_size) || + btrtl_dev->fw_len < min_size) return -EINVAL; /* Copy the firmware into a new buffer and write the version at @@ -814,11 +832,12 @@ static int rtlbt_parse_firmware(struct hci_dev *hdev, memcpy(buf + patch_length - 4, &epatch_info->fw_version, 4); *_buf = buf; + btrtl_dev->fw_type = FW_TYPE_V1; return len; } -static int rtl_download_firmware(struct hci_dev *hdev, - const unsigned char *data, int fw_len) +int rtl_download_firmware(struct hci_dev *hdev, u8 fw_type, + const unsigned char *data, int fw_len) { struct rtl_download_cmd *dl_cmd; int frag_num = fw_len / RTL_FRAG_LEN + 1; @@ -828,6 +847,13 @@ static int rtl_download_firmware(struct hci_dev *hdev, int j = 0; struct sk_buff *skb; struct hci_rp_read_local_version *rp; + u8 dl_rp_len = sizeof(struct rtl_download_response); + + if (is_v3_fw(fw_type)) { + j = 1; + if (fw_type == FW_TYPE_V3_2) + dl_rp_len++; + } dl_cmd = kmalloc_obj(*dl_cmd); if (!dl_cmd) @@ -841,15 +867,15 @@ static int rtl_download_firmware(struct hci_dev *hdev, j = 1; if (i == (frag_num - 1)) { - dl_cmd->index |= 0x80; /* data end */ + if (!is_v3_fw(fw_type)) + dl_cmd->index |= 0x80; /* data end */ frag_len = fw_len % RTL_FRAG_LEN; } rtl_dev_dbg(hdev, "download fw (%d/%d). index = %d", i, frag_num, dl_cmd->index); memcpy(dl_cmd->data, data, frag_len); - /* Send download command */ - skb = __hci_cmd_sync(hdev, 0xfc20, frag_len + 1, dl_cmd, + skb = __hci_cmd_sync(hdev, RTL_VSC_OP_DOWNLOAD_CMD, frag_len + 1, dl_cmd, HCI_INIT_TIMEOUT); if (IS_ERR(skb)) { rtl_dev_err(hdev, "download fw command failed (%ld)", @@ -858,7 +884,7 @@ static int rtl_download_firmware(struct hci_dev *hdev, goto out; } - if (skb->len != sizeof(struct rtl_download_response)) { + if (skb->len != dl_rp_len) { rtl_dev_err(hdev, "download fw event length mismatch"); kfree_skb(skb); ret = -EIO; @@ -869,6 +895,9 @@ static int rtl_download_firmware(struct hci_dev *hdev, data += RTL_FRAG_LEN; } + if (is_v3_fw(fw_type)) + goto out; + skb = btrtl_read_local_version(hdev); if (IS_ERR(skb)) { ret = PTR_ERR(skb); @@ -885,6 +914,7 @@ static int rtl_download_firmware(struct hci_dev *hdev, kfree(dl_cmd); return ret; } +EXPORT_SYMBOL_GPL(rtl_download_firmware); static int rtl_load_file(struct hci_dev *hdev, const char *name, u8 **buff) { @@ -919,7 +949,7 @@ static int btrtl_setup_rtl8723a(struct hci_dev *hdev, return -EINVAL; } - return rtl_download_firmware(hdev, btrtl_dev->fw_data, + return rtl_download_firmware(hdev, FW_TYPE_V0, btrtl_dev->fw_data, btrtl_dev->fw_len); } @@ -934,7 +964,7 @@ static int btrtl_setup_rtl8723b(struct hci_dev *hdev, if (ret < 0) goto out; - if (btrtl_dev->cfg_len > 0) { + if (!is_v3_fw(btrtl_dev->fw_type) && btrtl_dev->cfg_len > 0) { tbuff = kvzalloc(ret + btrtl_dev->cfg_len, GFP_KERNEL); if (!tbuff) { ret = -ENOMEM; @@ -950,9 +980,25 @@ static int btrtl_setup_rtl8723b(struct hci_dev *hdev, fw_data = tbuff; } + if (is_v3_fw(btrtl_dev->fw_type)) { + struct btrtl_enh_ops *ops = symbol_get(rtl_enh_ops); + + if (!ops) + ret = -EOPNOTSUPP; + else if (ops->download_firmware_v3) + ret = ops->download_firmware_v3(hdev, btrtl_dev); + else + ret = -EOPNOTSUPP; + + if (ops) + symbol_put(rtl_enh_ops); + + goto out; + } + rtl_dev_info(hdev, "cfg_sz %d, total sz %d", btrtl_dev->cfg_len, ret); - ret = rtl_download_firmware(hdev, fw_data, ret); + ret = rtl_download_firmware(hdev, btrtl_dev->fw_type, fw_data, ret); out: kvfree(fw_data); @@ -1022,7 +1068,7 @@ static int rtl_read_chip_type(struct hci_dev *hdev, u8 *type) const unsigned char cmd_buf[] = {0x00, 0x94, 0xa0, 0x00, 0xb0}; /* Read RTL chip type command */ - skb = __hci_cmd_sync(hdev, 0xfc61, 5, cmd_buf, HCI_INIT_TIMEOUT); + skb = __hci_cmd_sync(hdev, RTL_VSC_OP_READ_VENDER, 5, cmd_buf, HCI_INIT_TIMEOUT); if (IS_ERR(skb)) { rtl_dev_err(hdev, "Read chip type failed (%ld)", PTR_ERR(skb)); @@ -1057,6 +1103,16 @@ void btrtl_free(struct btrtl_device_info *btrtl_dev) kfree(entry); } + if (btrtl_dev) { + struct btrtl_enh_ops *ops = symbol_get(rtl_enh_ops); + + if (ops) { + if (ops->free_patch_images) + ops->free_patch_images(btrtl_dev); + symbol_put(rtl_enh_ops); + } + } + kfree(btrtl_dev); } EXPORT_SYMBOL_GPL(btrtl_free); @@ -1064,7 +1120,7 @@ EXPORT_SYMBOL_GPL(btrtl_free); struct btrtl_device_info *btrtl_initialize(struct hci_dev *hdev, const char *postfix) { - struct btrealtek_data *coredump_info = hci_get_priv(hdev); + struct btrealtek_data *btrtl_data = hci_get_priv(hdev); struct btrtl_device_info *btrtl_dev; struct sk_buff *skb; struct hci_rp_read_local_version *resp; @@ -1075,8 +1131,9 @@ struct btrtl_device_info *btrtl_initialize(struct hci_dev *hdev, u8 hci_ver, lmp_ver, chip_type = 0; int ret; int rc; - u8 key_id; + u8 key_id = 0; u8 reg_val[2]; + u8 chip_id = 0; btrtl_dev = kzalloc_obj(*btrtl_dev); if (!btrtl_dev) { @@ -1085,8 +1142,15 @@ struct btrtl_device_info *btrtl_initialize(struct hci_dev *hdev, } INIT_LIST_HEAD(&btrtl_dev->patch_subsecs); + INIT_LIST_HEAD(&btrtl_dev->patch_images); check_version: + ret = btrtl_read_chip_id(hdev, &chip_id); + if (!ret && chip_id >= CHIP_ID_V3_BASE) { + btrtl_dev->project_id = chip_id; + goto read_local_ver; + } + ret = btrtl_vendor_read_reg16(hdev, RTL_CHIP_SUBVER, reg_val); if (ret < 0) goto err_free; @@ -1109,6 +1173,7 @@ struct btrtl_device_info *btrtl_initialize(struct hci_dev *hdev, } } +read_local_ver: skb = btrtl_read_local_version(hdev); if (IS_ERR(skb)) { ret = PTR_ERR(skb); @@ -1186,11 +1251,18 @@ struct btrtl_device_info *btrtl_initialize(struct hci_dev *hdev, goto err_free; } - rc = btrtl_vendor_read_reg16(hdev, RTL_SEC_PROJ, reg_val); - if (rc < 0) - goto err_free; + if (btrtl_dev->project_id >= CHIP_ID_V3_BASE) { + rc = btrtl_vendor_read_reg16(hdev, RTL_SEC_PROJ_V3, reg_val); + if (rc < 0) + goto err_free; + key_id = reg_val[0]; + } else if (lmp_subver == RTL_ROM_LMP_8922A) { + rc = btrtl_vendor_read_reg16(hdev, RTL_SEC_PROJ_V2, reg_val); + if (rc < 0) + goto err_free; + key_id = reg_val[0]; + } - key_id = reg_val[0]; btrtl_dev->key_id = key_id; rtl_dev_info(hdev, "%s: key id %u", __func__, key_id); @@ -1244,7 +1316,7 @@ struct btrtl_device_info *btrtl_initialize(struct hci_dev *hdev, hci_set_msft_opcode(hdev, 0xFCF0); if (btrtl_dev->ic_info) - coredump_info->rtl_dump.controller = btrtl_dev->ic_info->hw_info; + btrtl_data->rtl_dump.controller = btrtl_dev->ic_info->hw_info; return btrtl_dev; @@ -1528,7 +1600,38 @@ EXPORT_SYMBOL_GPL(btrtl_get_uart_settings); MODULE_AUTHOR("Daniel Drake <[email protected]>"); MODULE_DESCRIPTION("Bluetooth support for Realtek devices ver " VERSION); MODULE_VERSION(VERSION); + + +int btrtl_recv_event(struct hci_dev *hdev, struct sk_buff *skb) +{ + struct sk_buff *clone = skb_clone(skb, GFP_ATOMIC); + struct hci_event_hdr *hdr; + u8 *p; + + if (!clone) + goto out; + + hdr = skb_pull_data(clone, sizeof(*hdr)); + if (!hdr || hdr->evt != HCI_VENDOR_PKT) + goto out; + + p = skb_pull_data(clone, 1); + if (!p) + goto out; + switch (*p) { + case 0x77: + if (btrealtek_test_and_clear_flag(hdev, REALTEK_DOWNLOADING)) + btrealtek_wake_up_flag(hdev, REALTEK_DOWNLOADING); + break; + } +out: + consume_skb(clone); + return hci_recv_frame(hdev, skb); +} +EXPORT_SYMBOL_GPL(btrtl_recv_event); + MODULE_LICENSE("GPL"); +MODULE_SOFTDEP("post: btrtl_enh"); MODULE_FIRMWARE("rtl_bt/rtl8723a_fw.bin"); MODULE_FIRMWARE("rtl_bt/rtl8723b_fw.bin"); MODULE_FIRMWARE("rtl_bt/rtl8723b_config.bin"); diff --git a/drivers/bluetooth/btrtl.h b/drivers/bluetooth/btrtl.h index a2d9d34f9fb0..061f8efe122d 100644 --- a/drivers/bluetooth/btrtl.h +++ b/drivers/bluetooth/btrtl.h @@ -12,7 +12,30 @@ #define rtl_dev_info(dev, fmt, ...) bt_dev_info(dev, "RTL: " fmt, ##__VA_ARGS__) #define rtl_dev_dbg(dev, fmt, ...) bt_dev_dbg(dev, "RTL: " fmt, ##__VA_ARGS__) -struct btrtl_device_info; +#ifndef kzalloc_obj +#define kzalloc_obj(obj) kzalloc(sizeof(obj), GFP_KERNEL) +#endif +#ifndef kmalloc_obj +#define kmalloc_obj(obj) kmalloc(sizeof(obj), GFP_KERNEL) +#endif + + +#define RTL_VSC_OP_DOWNLOAD_CMD 0xfc20 +#define RTL_VSC_OP_READ_VENDER 0xfc61 +#define RTL_VSC_OP_WRITE_VENDOR 0xfc62 +#define RTL_VSC_OP_READ_ROM_VER 0xfc6d +#define RTL_VSC_OP_READ_CHIP_ID 0xfc6f +#define RTL_VSC_OP_COREDUMP 0xfcff +#define RTL_VSC_OP_CHECK_DOWNLOAD_STATE 0xfdcf +#define RTL_VSC_OP_WDG_RESET_CMD 0xfc8e + +#define FW_TYPE_V0 0 +#define FW_TYPE_V1 1 +#define FW_TYPE_V2 2 +#define FW_TYPE_V3_1 3 +#define FW_TYPE_V3_2 4 +#define is_v3_fw(type) ((type) == FW_TYPE_V3_1 || (type) == FW_TYPE_V3_2) +#define CHIP_ID_V3_BASE 55 struct rtl_chip_type_evt { __u8 status; @@ -103,8 +126,14 @@ struct rtl_vendor_cmd { __u8 param[5]; } __packed; +struct rtl_rp_read_chip_id { + __u8 status; + __u8 chip_id; +} __packed; + enum { REALTEK_ALT6_CONTINUOUS_TX_CHIP, + REALTEK_DOWNLOADING, __REALTEK_NUM_FLAGS, }; @@ -130,7 +159,79 @@ struct btrealtek_data { #define btrealtek_get_flag(hdev) \ (((struct btrealtek_data *)hci_get_priv(hdev))->flags) +#define btrealtek_wake_up_flag(hdev, nr) \ + do { \ + struct btrealtek_data *rtl = hci_get_priv((hdev)); \ + wake_up_bit(rtl->flags, (nr)); \ + } while (0) #define btrealtek_test_flag(hdev, nr) test_bit((nr), btrealtek_get_flag(hdev)) +#define btrealtek_test_and_clear_flag(hdev, nr) \ + test_and_clear_bit((nr), btrealtek_get_flag(hdev)) +#define btrealtek_wait_on_flag_timeout(hdev, nr, m, to) \ + wait_on_bit_timeout(btrealtek_get_flag(hdev), (nr), m, to) +#define btrealtek_clear_flag(hdev, nr) \ + do { \ + struct btrealtek_data *rtl = hci_get_priv((hdev)); \ + clear_bit((nr), rtl->flags); \ + } while (0) + +struct id_table { + __u16 match_flags; + __u16 lmp_subver; + __u16 hci_rev; + __u8 hci_ver; + __u8 hci_bus; + __u8 chip_type; + bool config_needed; + bool has_rom_version; + bool has_msft_ext; + char *fw_name; + char *cfg_name; + char *hw_info; +}; + +struct btrtl_device_info { + const struct id_table *ic_info; + u8 rom_version; + u8 *fw_data; + int fw_len; + u8 *cfg_data; + int cfg_len; + bool drop_fw; + int project_id; + u32 opcode; + u8 fw_type; + u8 key_id; + struct list_head patch_subsecs; + struct list_head patch_images; +}; + +struct btrtl_enh_ops { + int (*parse_firmware_v3)(struct hci_dev *hdev, + struct btrtl_device_info *btrtl_dev); + int (*download_firmware_v3)(struct hci_dev *hdev, + struct btrtl_device_info *btrtl_dev); + void (*free_patch_images)(struct btrtl_device_info *btrtl_dev); + int (*recv_event)(struct hci_dev *hdev, struct sk_buff *skb); +}; +/* Symbol exported by btrtl_enh.ko for symbol_get/symbol_put */ +extern struct btrtl_enh_ops rtl_enh_ops; + + + + + +/* Internal functions shared between btrtl_core.c and btrtl_enh.c */ +void btrtl_free_patch_images(struct btrtl_device_info *btrtl_dev); +void *rtl_iov_pull_data(struct rtl_iovec *iov, u32 len); +struct sk_buff *btrtl_read_local_version(struct hci_dev *hdev); +int btrtl_read_chip_id(struct hci_dev *hdev, u8 *chip_id); +int rtl_download_firmware(struct hci_dev *hdev, u8 fw_type, + const unsigned char *data, int fw_len); +int rtlbt_parse_firmware_v3(struct hci_dev *hdev, + struct btrtl_device_info *btrtl_dev); +int rtl_download_firmware_v3(struct hci_dev *hdev, + struct btrtl_device_info *btrtl_dev); #if IS_ENABLED(CONFIG_BT_RTL) @@ -148,6 +249,7 @@ int btrtl_get_uart_settings(struct hci_dev *hdev, unsigned int *controller_baudrate, u32 *device_baudrate, bool *flow_control); void btrtl_set_driver_name(struct hci_dev *hdev, const char *driver_name); +int btrtl_recv_event(struct hci_dev *hdev, struct sk_buff *skb); #else @@ -157,6 +259,11 @@ static inline struct btrtl_device_info *btrtl_initialize(struct hci_dev *hdev, return ERR_PTR(-EOPNOTSUPP); } +static inline int btrtl_recv_event(struct hci_dev *hdev, struct sk_buff *skb) +{ + return -EOPNOTSUPP; +} + static inline void btrtl_free(struct btrtl_device_info *btrtl_dev) { } diff --git a/drivers/bluetooth/btrtl_enh.c b/drivers/bluetooth/btrtl_enh.c new file mode 100644 index 000000000000..ea962166c074 --- /dev/null +++ b/drivers/bluetooth/btrtl_enh.c @@ -0,0 +1,809 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Bluetooth support for Realtek devices + * + * Copyright (C) 2026 Realtek Semiconductor Corporation. + */ + +#include <linux/firmware.h> +#include <linux/unaligned.h> + +#include <net/bluetooth/bluetooth.h> +#include <net/bluetooth/hci_core.h> + +#include "btrtl.h" + +#define RTL_VENDOR_WRITEB_TYPE 0x00 +#define RTL_VENDOR_WRITE_TYPE 0x21 +#define RTL_VENDOR_REG32_TYPE 0x20 +#define RTL_CHIP_7090A 62 +#define RTL_WRZM_CNT 48 +#define RTL_WRZM_ADDR_A 0x00403BAC +#define RTL_WRZM_ADDR_B 0x00400018 +#define RTL_WRZM_ADDR_C 0x00400014 +#define RTL_PATCH_V3_1 0x01 +#define RTL_PATCH_V3_2 0x02 +#define IMAGE_ID_F000 0xf000 +#define IMAGE_ID_F001 0xf001 +#define IMAGE_ID_F002 0xf002 + +#define DL_FIX_CI_ID 0 +#define DL_FIX_CI_ADDR 1 +#define DL_FIX_PATCH_ADDR 2 +#define DL_FIX_SEC_HDR_ADDR 3 +#define DL_FIX_ADDR_MAX 4 + +struct rtl_vendor_write_cmd { + u8 type; + __le32 addr; + __le32 val; +} __packed; + +struct rtl_vendor_writeb_cmd { + u8 type; + __le32 addr; + u8 val; +} __packed; + +struct rtl_vendor_read_cmd { + u8 type; + __le32 addr; +} __packed; + +struct rtl_vendor_read_rsp { + u8 status; + __le32 val; +} __packed; + +struct rtl_rp_dl_v3 { + __u8 status; + __u8 index; + __u8 err; +} __packed; + +struct rtl_epatch_header_v3 { + __u8 signature[8]; + __u8 timestamp[8]; + __le32 ver_rsvd; + __le32 num_sections; +} __packed; + +struct rtl_section_v3 { + __le32 opcode; + __le64 len; + u8 data[]; +} __packed; + +struct rtl_addr_fix { + u32 addr; + u32 value; +}; + +struct rtl_section_patch_image { + u16 image_id; + u8 index; + u8 config_rule; + u8 need_config; + + struct rtl_addr_fix fix[DL_FIX_ADDR_MAX]; + + u32 image_len; + u8 *image_data; + u32 image_ver; + + u8 *cfg_buf; + u16 cfg_len; + + struct list_head list; +}; + +struct rtl_patch_image_hdr { + __le16 chip_id; + u8 ic_cut; + u8 key_id; + u8 enable_ota; + __le16 image_id; + u8 config_rule; + u8 need_config; + u8 rsv[950]; + + __le64 addr_fix[DL_FIX_ADDR_MAX * 2]; + u8 index; + + __le64 patch_image_len; + __u8 data[]; +} __packed; + +static int btrtl_vendor_write_mem(struct hci_dev *hdev, u32 addr, u32 val) +{ + struct rtl_vendor_write_cmd cp; + struct sk_buff *skb; + int err = 0; + + cp.type = RTL_VENDOR_WRITE_TYPE; + cp.addr = cpu_to_le32(addr); + cp.val = cpu_to_le32(val); + skb = __hci_cmd_sync(hdev, RTL_VSC_OP_WRITE_VENDOR, sizeof(cp), &cp, HCI_INIT_TIMEOUT); + if (IS_ERR(skb)) { + err = PTR_ERR(skb); + bt_dev_err(hdev, "RTL: Write mem32 failed (%d)", err); + return err; + } + + kfree_skb(skb); + return 0; +} + +static int btrtl_vendor_read_reg32(struct hci_dev *hdev, u32 addr, u32 *val) +{ + struct rtl_vendor_read_cmd cp; + struct rtl_vendor_read_rsp *rp; + struct sk_buff *skb; + + cp.type = RTL_VENDOR_REG32_TYPE; + cp.addr = cpu_to_le32(addr); + skb = __hci_cmd_sync(hdev, RTL_VSC_OP_READ_VENDER, + sizeof(cp), &cp, HCI_INIT_TIMEOUT); + if (IS_ERR(skb)) + return PTR_ERR(skb); + + rp = skb_pull_data(skb, sizeof(*rp)); + if (rp && !rp->status) + *val = le32_to_cpu(rp->val); + kfree_skb(skb); + + if (!rp || rp->status) + return -EIO; + + return 0; +} + +static int btrtl_vendor_write_reg32(struct hci_dev *hdev, u32 addr, u32 val) +{ + struct rtl_vendor_write_cmd cp; + struct sk_buff *skb; + + cp.type = RTL_VENDOR_REG32_TYPE; + cp.addr = cpu_to_le32(addr); + cp.val = cpu_to_le32(val); + skb = __hci_cmd_sync(hdev, RTL_VSC_OP_WRITE_VENDOR, + sizeof(cp), &cp, HCI_INIT_TIMEOUT); + if (IS_ERR(skb)) + return PTR_ERR(skb); + kfree_skb(skb); + return 0; +} + +static int btrtl_vendor_write_reg8(struct hci_dev *hdev, u32 addr, u8 val) +{ + struct rtl_vendor_writeb_cmd cp; + struct sk_buff *skb; + + cp.type = RTL_VENDOR_WRITEB_TYPE; + cp.addr = cpu_to_le32(addr); + cp.val = val; + skb = __hci_cmd_sync(hdev, RTL_VSC_OP_WRITE_VENDOR, + sizeof(cp), &cp, HCI_INIT_TIMEOUT); + if (IS_ERR(skb)) + return PTR_ERR(skb); + kfree_skb(skb); + return 0; +} + +static int btrtl_wrzm(struct hci_dev *hdev, + struct btrtl_device_info *btrtl_dev) +{ + u32 val; + int ret; + int i; + + for (i = 0; i < RTL_WRZM_CNT; i++) { + ret = btrtl_vendor_read_reg32(hdev, RTL_WRZM_ADDR_A + i * 4, &val); + if (ret) { + rtl_dev_err(hdev, "WRZM: read[%d] failed (%d)", i, ret); + return ret; + } + ret = btrtl_vendor_write_reg32(hdev, RTL_WRZM_ADDR_B + i * 4, val); + if (ret) { + rtl_dev_err(hdev, "WRZM: write[%d] failed (%d)", i, ret); + return ret; + } + } + + ret = btrtl_vendor_read_reg32(hdev, RTL_WRZM_ADDR_C, &val); + if (ret) { + rtl_dev_err(hdev, "WRZM: read (part2) failed (%d)", ret); + return ret; + } + + val |= 0x10; + + ret = btrtl_vendor_write_reg8(hdev, RTL_WRZM_ADDR_C, val); + if (ret) { + rtl_dev_err(hdev, "WRZM: write (part2) failed (%d)", ret); + return ret; + } + + return 0; +} + +static void btrtl_insert_ordered_patch_image(struct rtl_section_patch_image *image, + struct btrtl_device_info *btrtl_dev) +{ + struct list_head *pos; + struct list_head *next; + struct rtl_section_patch_image *node; + + list_for_each_safe(pos, next, &btrtl_dev->patch_images) { + node = list_entry(pos, struct rtl_section_patch_image, list); + + if (node->image_id > image->image_id) { + __list_add(&image->list, pos->prev, pos); + return; + } + + if (node->image_id == image->image_id && + node->index > image->index) { + __list_add(&image->list, pos->prev, pos); + return; + } + } + __list_add(&image->list, pos->prev, pos); +} + +static int rtlbt_parse_config(struct hci_dev *hdev, + struct rtl_section_patch_image *patch_image, + struct btrtl_device_info *btrtl_dev) +{ + const struct id_table *ic_info = NULL; + const struct firmware *fw; + char tmp_name[32]; + char filename[64]; + u8 *cfg_buf; + char *str; + char *p; + size_t len; + int ret; + + if (btrtl_dev && btrtl_dev->ic_info) + ic_info = btrtl_dev->ic_info; + + if (!ic_info) + return -EINVAL; + + str = ic_info->cfg_name; + if (!str) + return -EINVAL; + + if (btrtl_dev->fw_type == FW_TYPE_V3_1) { + if (!patch_image->image_id && !patch_image->index) { + snprintf(filename, sizeof(filename), "%s.bin", str); + goto load_fw; + } + goto done; + } + + len = strlen(str); + if (len > sizeof(tmp_name) - 1) + len = sizeof(tmp_name) - 1; + memcpy(tmp_name, str, len); + tmp_name[len] = '\0'; + + str = tmp_name; + p = strsep(&str, "."); + + ret = snprintf(filename, sizeof(filename), "%s", p); + if (patch_image->config_rule && patch_image->need_config) { + switch (patch_image->image_id) { + case IMAGE_ID_F000: + case IMAGE_ID_F001: + case IMAGE_ID_F002: + ret += snprintf(filename + ret, sizeof(filename) - ret, + "_%04x", patch_image->image_id); + break; + default: + goto done; + } + } else { + goto done; + } + + snprintf(filename + ret, sizeof(filename) - ret, ".%s", str ? str : "bin"); + +load_fw: + rtl_dev_info(hdev, "config file: %s", filename); + ret = request_firmware(&fw, filename, &hdev->dev); + if (ret < 0) { + if (btrtl_dev->fw_type == FW_TYPE_V3_2) { + len = 4; + cfg_buf = kvmalloc(len, GFP_KERNEL); + if (!cfg_buf) + return -ENOMEM; + + memset(cfg_buf, 0xff, len); + patch_image->cfg_buf = cfg_buf; + patch_image->cfg_len = len; + return 0; + } + goto err_req_fw; + } + rtl_dev_info(hdev, "config file: %s found", filename); + cfg_buf = kvmalloc(fw->size, GFP_KERNEL); + if (!cfg_buf) { + ret = -ENOMEM; + goto err; + } + memcpy(cfg_buf, fw->data, fw->size); + len = fw->size; + release_firmware(fw); + + patch_image->cfg_buf = cfg_buf; + patch_image->cfg_len = len; +done: + return 0; +err: + release_firmware(fw); +err_req_fw: + rtl_dev_info(hdev, "config file: [%s] not found", filename); + return ret; +} + +static int rtlbt_parse_section_v3(struct hci_dev *hdev, + struct btrtl_device_info *btrtl_dev, + u32 opcode, u8 *data, u32 len) +{ + struct rtl_section_patch_image *patch_image; + struct rtl_patch_image_hdr *hdr; + u16 image_id; + u16 chip_id; + size_t patch_image_len; + u8 *ptr; + int ret = 0; + size_t i; + struct rtl_iovec iov = { + .data = data, + .len = len, + }; + + hdr = rtl_iov_pull_data(&iov, sizeof(*hdr)); + if (!hdr) + return -EINVAL; + + if (btrtl_dev->opcode && btrtl_dev->opcode != opcode) { + rtl_dev_err(hdev, "invalid opcode 0x%02x", opcode); + return -EINVAL; + } + + if (!btrtl_dev->opcode) { + btrtl_dev->opcode = opcode; + switch (btrtl_dev->opcode) { + case RTL_PATCH_V3_1: + btrtl_dev->fw_type = FW_TYPE_V3_1; + break; + case RTL_PATCH_V3_2: + btrtl_dev->fw_type = FW_TYPE_V3_2; + break; + default: + return -EINVAL; + } + } + + patch_image_len = (u32)le64_to_cpu(hdr->patch_image_len); + chip_id = le16_to_cpu(hdr->chip_id); + image_id = le16_to_cpu(hdr->image_id); + rtl_dev_info(hdev, "image (%04x:%02x), chip id %u, cut 0x%02x, len %08zx" + , image_id, hdr->index, chip_id, hdr->ic_cut, + patch_image_len); + + if (btrtl_dev->key_id != hdr->key_id) { + rtl_dev_info(hdev, "skip image, key_id mismatch (%u, %u)", + hdr->key_id, btrtl_dev->key_id); + return 0; + } + + if (hdr->ic_cut != btrtl_dev->rom_version + 1) { + rtl_dev_info(hdev, "skip image, ic_cut mismatch (%u, %u)", + hdr->ic_cut, btrtl_dev->rom_version + 1); + return 0; + } + + if (btrtl_dev->fw_type == FW_TYPE_V3_1 && !btrtl_dev->project_id) + btrtl_dev->project_id = chip_id; + + if (btrtl_dev->fw_type == FW_TYPE_V3_2 && + chip_id != btrtl_dev->project_id) { + rtl_dev_info(hdev, "skip image, chip_id mismatch (%u, %d)", chip_id, + btrtl_dev->project_id); + return 0; + } + + ptr = rtl_iov_pull_data(&iov, patch_image_len); + if (!ptr) + return -ENODATA; + + patch_image = kzalloc_obj(*patch_image); + if (!patch_image) + return -ENOMEM; + patch_image->index = hdr->index; + patch_image->image_id = image_id; + patch_image->config_rule = hdr->config_rule; + patch_image->need_config = hdr->need_config; + + for (i = 0; i < DL_FIX_ADDR_MAX; i++) { + patch_image->fix[i].addr = + (u32)le64_to_cpu(hdr->addr_fix[i * 2]); + patch_image->fix[i].value = + (u32)le64_to_cpu(hdr->addr_fix[i * 2 + 1]); + } + + patch_image->image_len = patch_image_len; + + if (patch_image_len < 4) { + rtl_dev_err(hdev, "image payload too short (%zu)", + patch_image_len); + ret = -EINVAL; + goto err; + } + + patch_image->image_data = kvmalloc(patch_image_len, GFP_KERNEL); + if (!patch_image->image_data) { + ret = -ENOMEM; + goto err; + } + memcpy(patch_image->image_data, ptr, patch_image_len); + patch_image->image_ver = + get_unaligned_le32(ptr + patch_image->image_len - 4); + rtl_dev_info(hdev, "image version: %08x", patch_image->image_ver); + + ret = rtlbt_parse_config(hdev, patch_image, btrtl_dev); + if (ret) { + rtl_dev_err(hdev, "config parse failed (%d)", ret); + goto err; + } + + ret = patch_image->image_len; + + btrtl_insert_ordered_patch_image(patch_image, btrtl_dev); + + return ret; +err: + kvfree(patch_image->image_data); + kvfree(patch_image->cfg_buf); + kfree(patch_image); + return ret; +} + +int rtlbt_parse_firmware_v3(struct hci_dev *hdev, + struct btrtl_device_info *btrtl_dev) +{ + struct rtl_epatch_header_v3 *hdr; + int rc; + u32 num_sections; + struct rtl_section_v3 *section; + u32 section_len; + u32 opcode; + int len = 0; + int i; + u8 *ptr; + struct rtl_iovec iov = { + .data = btrtl_dev->fw_data, + .len = btrtl_dev->fw_len, + }; + + rtl_dev_info(hdev, "key id %u", btrtl_dev->key_id); + + hdr = rtl_iov_pull_data(&iov, sizeof(*hdr)); + if (!hdr) + return -EINVAL; + num_sections = le32_to_cpu(hdr->num_sections); + + rtl_dev_dbg(hdev, "timpstamp %08x-%08x", *((u32 *)hdr->timestamp), + *((u32 *)(hdr->timestamp + 4))); + + for (i = 0; i < num_sections; i++) { + section = rtl_iov_pull_data(&iov, sizeof(*section)); + if (!section) + break; + + section_len = (u32)le64_to_cpu(section->len); + opcode = le32_to_cpu(section->opcode); + + rtl_dev_dbg(hdev, "opcode 0x%04x", section->opcode); + + ptr = rtl_iov_pull_data(&iov, section_len); + if (!ptr) + break; + + rc = 0; + switch (opcode) { + case RTL_PATCH_V3_1: + case RTL_PATCH_V3_2: + rc = rtlbt_parse_section_v3(hdev, btrtl_dev, opcode, + ptr, section_len); + break; + default: + rtl_dev_warn(hdev, "Unknown opcode %08x", opcode); + break; + } + if (rc < 0) { + rtl_dev_err(hdev, "Parse section (%u) err (%d)", + opcode, rc); + continue; + } + len += rc; + } + + rtl_dev_info(hdev, "image payload total len: 0x%08x", len); + if (!len) { + rtl_dev_err(hdev, "no matching firmware section found"); + return -ENODATA; + } + + return len; +} + +static int rtl_check_download_state(struct hci_dev *hdev, + struct btrtl_device_info *btrtl_dev) +{ + struct sk_buff *skb; + int ret = 0; + u8 *state; + + btrealtek_set_flag(hdev, REALTEK_DOWNLOADING); + + skb = __hci_cmd_sync(hdev, RTL_VSC_OP_CHECK_DOWNLOAD_STATE, 0, NULL, HCI_CMD_TIMEOUT); + if (IS_ERR(skb)) { + btrealtek_clear_flag(hdev, REALTEK_DOWNLOADING); + rtl_dev_err(hdev, "write tb error %lu", PTR_ERR(skb)); + return -EIO; + } + + /* Other driver might be downloading the combined firmware. */ + state = skb_pull_data(skb, sizeof(*state)); + if (state && *state == 0x03) { + ret = btrealtek_wait_on_flag_timeout(hdev, REALTEK_DOWNLOADING, + TASK_INTERRUPTIBLE, + msecs_to_jiffies(5000)); + if (ret == -EINTR) { + bt_dev_err(hdev, "Firmware loading interrupted"); + goto out; + } + + if (ret) { + bt_dev_err(hdev, "Firmware loading timeout"); + ret = -ETIMEDOUT; + } else { + ret = -EALREADY; + } + + } else { + btrealtek_clear_flag(hdev, REALTEK_DOWNLOADING); + } + +out: + kfree_skb(skb); + return ret; +} + +static int rtl_finalize_download(struct hci_dev *hdev, + struct btrtl_device_info *btrtl_dev) +{ + struct hci_rp_read_local_version *rp_ver; + u8 params[2] = { 0x03, 0xb2 }; + struct sk_buff *skb; + int ret = 0; + u16 opcode; + u32 len; + u8 *p; + + opcode = RTL_VSC_OP_WDG_RESET_CMD; + len = 2; + if (btrtl_dev->opcode == RTL_PATCH_V3_1) { + opcode = RTL_VSC_OP_DOWNLOAD_CMD; + params[0] = 0x80; + len = 1; + } + skb = __hci_cmd_sync(hdev, opcode, len, params, HCI_CMD_TIMEOUT); + if (IS_ERR(skb)) { + rtl_dev_err(hdev, "Watchdog reset err (%ld)", PTR_ERR(skb)); + return -EIO; + } + p = skb_pull_data(skb, 1); + if (!p) { + ret = -ENODATA; + goto out; + } + rtl_dev_info(hdev, "Watchdog reset status %02x", *p); + kfree_skb(skb); + + skb = btrtl_read_local_version(hdev); + if (IS_ERR(skb)) { + ret = PTR_ERR(skb); + rtl_dev_err(hdev, "read local version failed (%d)", ret); + return ret; + } + + rp_ver = skb_pull_data(skb, sizeof(*rp_ver)); + if (rp_ver) + rtl_dev_info(hdev, "fw version 0x%04x%04x", + __le16_to_cpu(rp_ver->hci_rev), + __le16_to_cpu(rp_ver->lmp_subver)); +out: + kfree_skb(skb); + return ret; +} + +static int rtl_security_check(struct hci_dev *hdev, + struct btrtl_device_info *btrtl_dev) +{ + struct rtl_section_patch_image *tmp = NULL; + struct rtl_section_patch_image *image = NULL; + u32 val; + int ret; + + list_for_each_entry_reverse(tmp, &btrtl_dev->patch_images, list) { + /* Check security hdr */ + if (!tmp->fix[DL_FIX_SEC_HDR_ADDR].value || + !tmp->fix[DL_FIX_SEC_HDR_ADDR].addr || + tmp->fix[DL_FIX_SEC_HDR_ADDR].addr == 0xffffffff) + continue; + rtl_dev_info(hdev, "addr 0x%08x, value 0x%08x", + tmp->fix[DL_FIX_SEC_HDR_ADDR].addr, + tmp->fix[DL_FIX_SEC_HDR_ADDR].value); + image = tmp; + break; + } + + if (!image) + return 0; + + rtl_dev_info(hdev, "sec image (%04x:%02x)", image->image_id, + image->index); + val = image->fix[DL_FIX_PATCH_ADDR].value + image->image_len - + image->fix[DL_FIX_SEC_HDR_ADDR].value; + ret = btrtl_vendor_write_mem(hdev, image->fix[DL_FIX_PATCH_ADDR].addr, + val); + if (ret) { + rtl_dev_err(hdev, "write sec reg failed (%d)", ret); + return ret; + } + return 0; +} + +int rtl_download_firmware_v3(struct hci_dev *hdev, + struct btrtl_device_info *btrtl_dev) +{ + struct rtl_section_patch_image *image, *tmp; + struct rtl_rp_dl_v3 *rp; + struct sk_buff *skb; + u8 *fw_data; + int fw_len; + int ret = 0; + u8 i; + + if (btrtl_dev->project_id == RTL_CHIP_7090A) { + ret = btrtl_wrzm(hdev, btrtl_dev); + if (ret) { + rtl_dev_err(hdev, "v3 WRZM failed (%d)", ret); + return ret; + } + } + + if (btrtl_dev->fw_type == FW_TYPE_V3_2) { + ret = rtl_check_download_state(hdev, btrtl_dev); + if (ret) { + if (ret == -EALREADY) + return 0; + return ret; + } + } + + list_for_each_entry_safe(image, tmp, &btrtl_dev->patch_images, list) { + rtl_dev_dbg(hdev, "image (%04x:%02x)", image->image_id, + image->index); + + for (i = DL_FIX_CI_ID; i < DL_FIX_ADDR_MAX; i++) { + if (!image->fix[i].addr || + image->fix[i].addr == 0xffffffff) { + rtl_dev_dbg(hdev, "no need to write addr %08x", + image->fix[i].addr); + continue; + } + rtl_dev_dbg(hdev, "write addr and val, 0x%08x, 0x%08x", + image->fix[i].addr, image->fix[i].value); + if (btrtl_vendor_write_mem(hdev, image->fix[i].addr, + image->fix[i].value)) { + rtl_dev_err(hdev, "write reg failed"); + ret = -EIO; + goto done; + } + } + + fw_len = image->image_len + image->cfg_len; + fw_data = kvmalloc(fw_len, GFP_KERNEL); + if (!fw_data) { + rtl_dev_err(hdev, "Couldn't alloc buf for image data"); + ret = -ENOMEM; + goto done; + } + memcpy(fw_data, image->image_data, image->image_len); + if (image->cfg_len > 0) + memcpy(fw_data + image->image_len, image->cfg_buf, + image->cfg_len); + + rtl_dev_dbg(hdev, "patch image (%04x:%02x). len: %d", + image->image_id, image->index, fw_len); + rtl_dev_dbg(hdev, "fw_data %p, image buf %p, len %u", fw_data, + image->image_data, image->image_len); + + ret = rtl_download_firmware(hdev, btrtl_dev->fw_type, fw_data, + fw_len); + kvfree(fw_data); + if (ret < 0) { + rtl_dev_err(hdev, "download firmware failed (%d)", ret); + goto done; + } + + if (image->list.next != &btrtl_dev->patch_images && + image->image_id == tmp->image_id) + continue; + + if (btrtl_dev->fw_type == FW_TYPE_V3_1) + continue; + + i = 0x80; + skb = __hci_cmd_sync(hdev, RTL_VSC_OP_DOWNLOAD_CMD, 1, &i, HCI_CMD_TIMEOUT); + if (IS_ERR(skb)) { + ret = -EIO; + rtl_dev_err(hdev, "Failed to issue last cmd fc20, %ld", + PTR_ERR(skb)); + goto done; + } + ret = 2; + rp = skb_pull_data(skb, sizeof(*rp)); + if (rp) + ret = rp->err; + kfree_skb(skb); + if (ret == 2) { + /* Verification failure */ + ret = -EFAULT; + goto done; + } + } + + if (btrtl_dev->fw_type == FW_TYPE_V3_1) { + ret = rtl_security_check(hdev, btrtl_dev); + if (ret) { + rtl_dev_err(hdev, "Security check failed (%d)", ret); + goto done; + } + } + + ret = rtl_finalize_download(hdev, btrtl_dev); + +done: + return ret; +} + +void btrtl_free_patch_images(struct btrtl_device_info *btrtl_dev) +{ + struct rtl_section_patch_image *image, *next; + + list_for_each_entry_safe(image, next, &btrtl_dev->patch_images, list) { + list_del(&image->list); + kvfree(image->image_data); + kvfree(image->cfg_buf); + kfree(image); + } +} + + +struct btrtl_enh_ops rtl_enh_ops = { + .parse_firmware_v3 = rtlbt_parse_firmware_v3, + .download_firmware_v3 = rtl_download_firmware_v3, + .free_patch_images = btrtl_free_patch_images, +}; +EXPORT_SYMBOL_GPL(rtl_enh_ops); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Realtek Bluetooth firmware v3+ support"); diff --git a/drivers/bluetooth/btusb.c b/drivers/bluetooth/btusb.c index be82bbbc1b5c..520bc3572ae8 100644 --- a/drivers/bluetooth/btusb.c +++ b/drivers/bluetooth/btusb.c @@ -2825,6 +2825,9 @@ static int btusb_recv_event_realtek(struct hci_dev *hdev, struct sk_buff *skb) return 0; } + if (skb->data[0] == HCI_VENDOR_PKT) + return btrtl_recv_event(hdev, skb); + return hci_recv_frame(hdev, skb); } -- 2.34.1