git: b87a926098b2 - main - rtlbtfw(8): Add support for firmware file format V2
- Go to: [ bottom of page ] [ top of archives ] [ this month ]
Date: Wed, 14 May 2025 01:11:33 UTC
The branch main has been updated by wulf:
URL: https://cgit.FreeBSD.org/src/commit/?id=b87a926098b291e2baf45ffc13c076ba0b0f0d74
commit b87a926098b291e2baf45ffc13c076ba0b0f0d74
Author: Vladimir Kondratyev <wulf@FreeBSD.org>
AuthorDate: 2025-05-14 01:09:40 +0000
Commit: Vladimir Kondratyev <wulf@FreeBSD.org>
CommitDate: 2025-05-14 01:09:40 +0000
rtlbtfw(8): Add support for firmware file format V2
As Realtek changed format of the firmware files for recent adaptors.
Sponsored by: Future Crew, LLC
MFC after: 1 month
Differential Revision: https://reviews.freebsd.org/D50082
---
usr.sbin/bluetooth/rtlbtfw/main.c | 19 +++-
usr.sbin/bluetooth/rtlbtfw/rtlbt_fw.c | 195 ++++++++++++++++++++++++++++++++--
usr.sbin/bluetooth/rtlbtfw/rtlbt_fw.h | 53 ++++++++-
usr.sbin/bluetooth/rtlbtfw/rtlbt_hw.c | 35 ++++++
usr.sbin/bluetooth/rtlbtfw/rtlbt_hw.h | 13 +++
5 files changed, 302 insertions(+), 13 deletions(-)
diff --git a/usr.sbin/bluetooth/rtlbtfw/main.c b/usr.sbin/bluetooth/rtlbtfw/main.c
index e0445726c3a2..700b9b43bafa 100644
--- a/usr.sbin/bluetooth/rtlbtfw/main.c
+++ b/usr.sbin/bluetooth/rtlbtfw/main.c
@@ -76,7 +76,6 @@ static struct rtlbt_devid rtlbt_list[] = {
{ .vendor_id = 0x04ca, .product_id = 0x4006 },
{ .vendor_id = 0x0cb8, .product_id = 0xc549 },
-#ifdef RTLBTFW_SUPPORTS_FW_V2
/* Realtek 8852CE Bluetooth devices */
{ .vendor_id = 0x04ca, .product_id = 0x4007 },
{ .vendor_id = 0x04c5, .product_id = 0x1675 },
@@ -84,7 +83,6 @@ static struct rtlbt_devid rtlbt_list[] = {
{ .vendor_id = 0x13d3, .product_id = 0x3587 },
{ .vendor_id = 0x13d3, .product_id = 0x3586 },
{ .vendor_id = 0x13d3, .product_id = 0x3592 },
-#endif
/* Realtek 8852BE Bluetooth devices */
{ .vendor_id = 0x0cb8, .product_id = 0xc559 },
@@ -312,6 +310,7 @@ main(int argc, char *argv[])
char *firmware_dir = NULL;
char *firmware_path = NULL;
char *config_path = NULL;
+ const char *fw_suffix;
int retcode = 1;
const struct rtlbt_id_table *ic;
uint8_t rom_version;
@@ -410,7 +409,8 @@ main(int argc, char *argv[])
if (firmware_dir == NULL)
firmware_dir = strdup(_DEFAULT_RTLBT_FIRMWARE_PATH);
- firmware_path = rtlbt_get_fwname(ic->fw_name, firmware_dir, "_fw.bin");
+ fw_suffix = ic->fw_suffix == NULL ? "_fw.bin" : ic->fw_suffix;
+ firmware_path = rtlbt_get_fwname(ic->fw_name, firmware_dir, fw_suffix);
if (firmware_path == NULL)
goto shutdown;
@@ -449,7 +449,18 @@ main(int argc, char *argv[])
rtlbt_debug("rom_version = %d", rom_version);
/* Load in the firmware */
- r = rtlbt_parse_fwfile_v1(&fw, rom_version);
+ if (fw_type == RTLBT_FW_TYPE_V2) {
+ uint8_t key_id, reg_val[2];
+ r = rtlbt_read_reg16(hdl, RTLBT_SEC_PROJ, reg_val);
+ if (r < 0) {
+ rtlbt_err("rtlbt_read_reg16() failed code %d", r);
+ goto shutdown;
+ }
+ key_id = reg_val[0];
+ rtlbt_debug("key_id = %d", key_id);
+ r = rtlbt_parse_fwfile_v2(&fw, rom_version, key_id);
+ } else
+ r = rtlbt_parse_fwfile_v1(&fw, rom_version);
if (r < 0) {
rtlbt_err("Parseing firmware file failed");
goto shutdown;
diff --git a/usr.sbin/bluetooth/rtlbtfw/rtlbt_fw.c b/usr.sbin/bluetooth/rtlbtfw/rtlbt_fw.c
index bb3d20d79527..c58a8feb41a4 100644
--- a/usr.sbin/bluetooth/rtlbtfw/rtlbt_fw.c
+++ b/usr.sbin/bluetooth/rtlbtfw/rtlbt_fw.c
@@ -105,20 +105,19 @@ static const struct rtlbt_id_table rtlbt_ic_id_table[] = {
.hci_version = 0xb,
.flags = RTLBT_IC_FLAG_MSFT,
.fw_name = "rtl8852bu",
-#ifdef RTLBTFW_SUPPORTS_FW_V2
}, { /* 8852C */
.lmp_subversion = RTLBT_ROM_LMP_8852A,
.hci_revision = 0xc,
.hci_version = 0xc,
.flags = RTLBT_IC_FLAG_MSFT,
.fw_name = "rtl8852cu",
+ .fw_suffix = "_fw_v2.bin",
}, { /* 8851B */
.lmp_subversion = RTLBT_ROM_LMP_8851B,
.hci_revision = 0xb,
.hci_version = 0xc,
.flags = RTLBT_IC_FLAG_MSFT,
.fw_name = "rtl8851bu",
-#endif
},
};
@@ -144,10 +143,8 @@ static const uint16_t project_ids[] = {
/* Signatures */
static const uint8_t fw_header_sig_v1[8] =
{0x52, 0x65, 0x61, 0x6C, 0x74, 0x65, 0x63, 0x68}; /* Realtech */
-#ifdef RTLBTFW_SUPPORTS_FW_V2
static const uint8_t fw_header_sig_v2[8] =
{0x52, 0x54, 0x42, 0x54, 0x43, 0x6F, 0x72, 0x65}; /* RTBTCore */
-#endif
static const uint8_t fw_ext_sig[4] = {0x51, 0x04, 0xFD, 0x77};
int
@@ -260,12 +257,10 @@ rtlbt_get_fw_type(struct rtlbt_firmware *fw, uint16_t *fw_lmp_subversion)
fw_type = RTLBT_FW_TYPE_V1;
fw_header_len = sizeof(struct rtlbt_fw_header_v1);
} else
-#ifdef RTLBTFW_SUPPORTS_FW_V2
if (memcmp(fw->buf, fw_header_sig_v2, sizeof(fw_header_sig_v2)) == 0) {
fw_type = RTLBT_FW_TYPE_V2;
fw_header_len = sizeof(struct rtlbt_fw_header_v2);
} else
-#endif
return (RTLBT_FW_TYPE_UNKNOWN);
if (fw->len < fw_header_len + sizeof(fw_ext_sig) + 4) {
@@ -367,6 +362,194 @@ rtlbt_parse_fwfile_v1(struct rtlbt_firmware *fw, uint8_t rom_version)
return (0);
}
+static void *
+rtlbt_iov_fetch(struct rtlbt_iov *iov, uint32_t len)
+{
+ void *data = NULL;
+
+ if (iov->len >= len) {
+ data = iov->data;
+ iov->data += len;
+ iov->len -= len;
+ }
+
+ return (data);
+}
+
+static int
+rtlbt_patch_entry_cmp(struct rtlbt_patch_entry *a, struct rtlbt_patch_entry *b,
+ void *thunk __unused)
+{
+ return ((a->prio > b->prio) - (a->prio < b->prio));
+}
+
+static int
+rtlbt_parse_section(struct rtlbt_patch_list *patch_list, uint32_t opcode,
+ uint8_t *data, uint32_t len, uint8_t rom_version, uint8_t key_id)
+{
+ struct rtlbt_sec_hdr *hdr;
+ struct rtlbt_patch_entry *entry;
+ struct rtlbt_subsec_hdr *subsec_hdr;
+ struct rtlbt_subsec_security_hdr *subsec_security_hdr;
+ uint16_t num_subsecs;
+ uint8_t *subsec_data;
+ uint32_t subsec_len;
+ int i, sec_len = 0;
+ struct rtlbt_iov iov = {
+ .data = data,
+ .len = len,
+ };
+
+ hdr = rtlbt_iov_fetch(&iov, sizeof(*hdr));
+ if (hdr == NULL) {
+ errno = EINVAL;
+ return (-1);
+ }
+ num_subsecs = le16toh(hdr->num);
+
+ for (i = 0; i < num_subsecs; i++) {
+ subsec_hdr = rtlbt_iov_fetch(&iov, sizeof(*subsec_hdr));
+ if (subsec_hdr == NULL)
+ break;
+ subsec_len = le32toh(subsec_hdr->len);
+
+ rtlbt_debug("subsection, eco 0x%02x, prio 0x%02x, len 0x%x",
+ subsec_hdr->eco, subsec_hdr->prio, subsec_len);
+
+ subsec_data = rtlbt_iov_fetch(&iov, subsec_len);
+ if (subsec_data == NULL)
+ break;
+
+ if (subsec_hdr->eco == rom_version + 1) {
+ if (opcode == RTLBT_PATCH_SECURITY_HEADER) {
+ subsec_security_hdr = (void *)subsec_hdr;
+ if (subsec_security_hdr->key_id == key_id)
+ break;
+ continue;
+ }
+
+ entry = calloc(1, sizeof(*entry));
+ if (entry == NULL) {
+ errno = ENOMEM;
+ return (-1);
+ }
+ *entry = (struct rtlbt_patch_entry) {
+ .opcode = opcode,
+ .len = subsec_len,
+ .prio = subsec_hdr->prio,
+ .data = subsec_data,
+ };
+ SLIST_INSERT_HEAD(patch_list, entry, next);
+ sec_len += subsec_len;
+ }
+ }
+
+ return (sec_len);
+}
+
+int
+rtlbt_parse_fwfile_v2(struct rtlbt_firmware *fw, uint8_t rom_version,
+ uint8_t key_id)
+{
+ struct rtlbt_fw_header_v2 *hdr;
+ struct rtlbt_section *section;
+ struct rtlbt_patch_entry *entry;
+ uint32_t num_sections;
+ uint32_t section_len;
+ uint32_t opcode;
+ int seclen, len = 0, patch_len = 0;
+ uint32_t i;
+ uint8_t *section_data, *patch_buf;
+ struct rtlbt_patch_list patch_list =
+ SLIST_HEAD_INITIALIZER(patch_list);
+ struct rtlbt_iov iov = {
+ .data = fw->buf,
+ .len = fw->len - 7,
+ };
+
+ hdr = rtlbt_iov_fetch(&iov, sizeof(*hdr));
+ if (hdr == NULL) {
+ errno = EINVAL;
+ return (-1);
+ }
+ num_sections = le32toh(hdr->num_sections);
+
+ rtlbt_debug("FW version %02x%02x%02x%02x-%02x%02x%02x%02x",
+ hdr->fw_version[0], hdr->fw_version[1],
+ hdr->fw_version[2], hdr->fw_version[3],
+ hdr->fw_version[4], hdr->fw_version[5],
+ hdr->fw_version[6], hdr->fw_version[7]);
+
+ for (i = 0; i < num_sections; i++) {
+ section = rtlbt_iov_fetch(&iov, sizeof(*section));
+ if (section == NULL)
+ break;
+ section_len = le32toh(section->len);
+ opcode = le32toh(section->opcode);
+
+ rtlbt_debug("section, opcode 0x%08x", section->opcode);
+
+ section_data = rtlbt_iov_fetch(&iov, section_len);
+ if (section_data == NULL)
+ break;
+
+ seclen = 0;
+ switch (opcode) {
+ case RTLBT_PATCH_SECURITY_HEADER:
+ if (key_id == 0)
+ break;
+ /* FALLTHROUGH */
+ case RTLBT_PATCH_SNIPPETS:
+ case RTLBT_PATCH_DUMMY_HEADER:
+ seclen = rtlbt_parse_section(&patch_list, opcode,
+ section_data, section_len, rom_version, key_id);
+ break;
+ default:
+ break;
+ }
+ if (seclen < 0) {
+ rtlbt_err("Section parse (0x%08x) failed. err %d",
+ opcode, errno);
+ return (-1);
+ }
+ len += seclen;
+ }
+
+ if (len == 0) {
+ errno = ENOMSG;
+ return (-1);
+ }
+
+ patch_buf = calloc(1, len);
+ if (patch_buf == NULL) {
+ errno = ENOMEM;
+ return (-1);
+ }
+
+ SLIST_MERGESORT(&patch_list, NULL,
+ rtlbt_patch_entry_cmp, rtlbt_patch_entry, next);
+ while (!SLIST_EMPTY(&patch_list)) {
+ entry = SLIST_FIRST(&patch_list);
+ rtlbt_debug("opcode 0x%08x, addr 0x%p, len 0x%x",
+ entry->opcode, entry->data, entry->len);
+ memcpy(patch_buf + patch_len, entry->data, entry->len);
+ patch_len += entry->len;
+ SLIST_REMOVE_HEAD(&patch_list, next);
+ free(entry);
+ }
+
+ if (patch_len == 0) {
+ errno = EPERM;
+ return (-1);
+ }
+
+ free(fw->buf);
+ fw->buf = patch_buf;
+ fw->len = patch_len;
+
+ return (0);
+}
+
int
rtlbt_append_fwfile(struct rtlbt_firmware *fw, struct rtlbt_firmware *opt)
{
diff --git a/usr.sbin/bluetooth/rtlbtfw/rtlbt_fw.h b/usr.sbin/bluetooth/rtlbtfw/rtlbt_fw.h
index 340abacba759..48b30cb2289b 100644
--- a/usr.sbin/bluetooth/rtlbtfw/rtlbt_fw.h
+++ b/usr.sbin/bluetooth/rtlbtfw/rtlbt_fw.h
@@ -30,7 +30,8 @@
#ifndef __RTLBT_FW_H__
#define __RTLBT_FW_H__
-#include <stdbool.h>
+#include <sys/queue.h>
+#include <sys/queue_mergesort.h>
#define RTLBT_ROM_LMP_8703B 0x8703
#define RTLBT_ROM_LMP_8723A 0x1200
@@ -41,12 +42,14 @@
#define RTLBT_ROM_LMP_8852A 0x8852
#define RTLBT_ROM_LMP_8851B 0x8851
+#define RTLBT_PATCH_SNIPPETS 0x01
+#define RTLBT_PATCH_DUMMY_HEADER 0x02
+#define RTLBT_PATCH_SECURITY_HEADER 0x03
+
enum rtlbt_fw_type {
RTLBT_FW_TYPE_UNKNOWN,
RTLBT_FW_TYPE_V1,
-#ifdef RTLBTFW_SUPPORTS_FW_V2
RTLBT_FW_TYPE_V2,
-#endif
};
struct rtlbt_id_table {
@@ -58,6 +61,7 @@ struct rtlbt_id_table {
#define RTLBT_IC_FLAG_CONFIG (1 << 1)
#define RTLBT_IC_FLAG_MSFT (2 << 1)
const char *fw_name;
+ const char *fw_suffix;
};
struct rtlbt_firmware {
@@ -66,6 +70,21 @@ struct rtlbt_firmware {
unsigned char *buf;
};
+SLIST_HEAD(rtlbt_patch_list, rtlbt_patch_entry);
+
+struct rtlbt_patch_entry {
+ SLIST_ENTRY(rtlbt_patch_entry) next;
+ uint32_t opcode;
+ uint32_t len;
+ uint8_t prio;
+ uint8_t *data;
+};
+
+struct rtlbt_iov {
+ uint8_t *data;
+ uint32_t len;
+};
+
struct rtlbt_fw_header_v1 {
uint8_t signature[8];
uint32_t fw_version;
@@ -78,6 +97,32 @@ struct rtlbt_fw_header_v2 {
uint32_t num_sections;
} __attribute__ ((packed));
+struct rtlbt_section {
+ uint32_t opcode;
+ uint32_t len;
+ uint8_t data[];
+} __attribute__ ((packed));
+
+struct rtlbt_sec_hdr {
+ uint16_t num;
+ uint16_t reserved;
+} __attribute__ ((packed));
+
+struct rtlbt_subsec_hdr {
+ uint8_t eco;
+ uint8_t prio;
+ uint8_t cb[2];
+ uint32_t len;
+} __attribute__ ((packed));
+
+struct rtlbt_subsec_security_hdr {
+ uint8_t eco;
+ uint8_t prio;
+ uint8_t key_id;
+ uint8_t reserved;
+ uint32_t len;
+} __attribute__ ((packed));
+
int rtlbt_fw_read(struct rtlbt_firmware *fw, const char *fwname);
void rtlbt_fw_free(struct rtlbt_firmware *fw);
char *rtlbt_get_fwname(const char *fw_name, const char *prefix,
@@ -87,6 +132,8 @@ const struct rtlbt_id_table *rtlbt_get_ic(uint16_t lmp_subversion,
enum rtlbt_fw_type rtlbt_get_fw_type(struct rtlbt_firmware *fw,
uint16_t *fw_lmp_subversion);
int rtlbt_parse_fwfile_v1(struct rtlbt_firmware *fw, uint8_t rom_version);
+int rtlbt_parse_fwfile_v2(struct rtlbt_firmware *fw, uint8_t rom_version,
+ uint8_t reg_id);
int rtlbt_append_fwfile(struct rtlbt_firmware *fw, struct rtlbt_firmware *opt);
#endif
diff --git a/usr.sbin/bluetooth/rtlbtfw/rtlbt_hw.c b/usr.sbin/bluetooth/rtlbtfw/rtlbt_hw.c
index 21f2c3e2804f..82e22d406ea9 100644
--- a/usr.sbin/bluetooth/rtlbtfw/rtlbt_hw.c
+++ b/usr.sbin/bluetooth/rtlbtfw/rtlbt_hw.c
@@ -178,6 +178,41 @@ rtlbt_read_rom_ver(struct libusb_device_handle *hdl, uint8_t *ver)
return (0);
}
+int
+rtlbt_read_reg16(struct libusb_device_handle *hdl,
+ struct rtlbt_vendor_cmd *vcmd, uint8_t *resp)
+{
+ int ret, transferred;
+ struct rtlbt_hci_event_cmd_compl *event;
+ uint8_t cmd_buf[offsetof(struct rtlbt_hci_cmd, data) + sizeof(*vcmd)];
+ struct rtlbt_hci_cmd *cmd = (struct rtlbt_hci_cmd *)cmd_buf;
+ cmd->opcode = htole16(0xfc61);
+ cmd->length = sizeof(struct rtlbt_vendor_cmd);
+ memcpy(cmd->data, vcmd, sizeof(struct rtlbt_vendor_cmd));
+ uint8_t buf[RTLBT_HCI_EVT_COMPL_SIZE(struct rtlbt_vendor_rp)];
+
+ memset(buf, 0, sizeof(buf));
+
+ ret = rtlbt_hci_command(hdl,
+ cmd,
+ buf,
+ sizeof(buf),
+ &transferred,
+ RTLBT_HCI_CMD_TIMEOUT);
+
+ if (ret < 0 || transferred != sizeof(buf)) {
+ rtlbt_debug("Can't read reg16: code=%d, size=%d",
+ ret,
+ transferred);
+ return (-1);
+ }
+
+ event = (struct rtlbt_hci_event_cmd_compl *)buf;
+ memcpy(resp, &(((struct rtlbt_vendor_rp *)event->data)->data), 2);
+
+ return (0);
+}
+
int
rtlbt_load_fwfile(struct libusb_device_handle *hdl,
const struct rtlbt_firmware *fw)
diff --git a/usr.sbin/bluetooth/rtlbtfw/rtlbt_hw.h b/usr.sbin/bluetooth/rtlbtfw/rtlbt_hw.h
index bc7c9fee3f57..a7200a440272 100644
--- a/usr.sbin/bluetooth/rtlbtfw/rtlbt_hw.h
+++ b/usr.sbin/bluetooth/rtlbtfw/rtlbt_hw.h
@@ -95,9 +95,22 @@ struct rtlbt_hci_dl_rp {
uint8_t index;
} __attribute__ ((packed));
+/* Vendor USB request payload */
+struct rtlbt_vendor_cmd {
+ uint8_t data[5];
+} __attribute__ ((packed));
+#define RTLBT_SEC_PROJ (&(struct rtlbt_vendor_cmd) {{0x10, 0xA4, 0x0D, 0x00, 0xb0}})
+
+struct rtlbt_vendor_rp {
+ uint8_t status;
+ uint8_t data[2];
+};
+
int rtlbt_read_local_ver(struct libusb_device_handle *hdl,
ng_hci_read_local_ver_rp *ver);
int rtlbt_read_rom_ver(struct libusb_device_handle *hdl, uint8_t *ver);
+int rtlbt_read_reg16(struct libusb_device_handle *hdl,
+ struct rtlbt_vendor_cmd *cmd, uint8_t *resp);
int rtlbt_load_fwfile(struct libusb_device_handle *hdl,
const struct rtlbt_firmware *fw);