aboutsummaryrefslogtreecommitdiff
path: root/usr.sbin/bluetooth/rtlbtfw
diff options
context:
space:
mode:
Diffstat (limited to 'usr.sbin/bluetooth/rtlbtfw')
-rw-r--r--usr.sbin/bluetooth/rtlbtfw/Makefile9
-rw-r--r--usr.sbin/bluetooth/rtlbtfw/main.c554
-rw-r--r--usr.sbin/bluetooth/rtlbtfw/rtlbt_dbg.h46
-rw-r--r--usr.sbin/bluetooth/rtlbtfw/rtlbt_fw.c582
-rw-r--r--usr.sbin/bluetooth/rtlbtfw/rtlbt_fw.h140
-rw-r--r--usr.sbin/bluetooth/rtlbtfw/rtlbt_hw.c270
-rw-r--r--usr.sbin/bluetooth/rtlbtfw/rtlbt_hw.h117
-rw-r--r--usr.sbin/bluetooth/rtlbtfw/rtlbtfw.899
-rw-r--r--usr.sbin/bluetooth/rtlbtfw/rtlbtfw.conf433
9 files changed, 2250 insertions, 0 deletions
diff --git a/usr.sbin/bluetooth/rtlbtfw/Makefile b/usr.sbin/bluetooth/rtlbtfw/Makefile
new file mode 100644
index 000000000000..f9c5dfd12b1f
--- /dev/null
+++ b/usr.sbin/bluetooth/rtlbtfw/Makefile
@@ -0,0 +1,9 @@
+PACKAGE= bluetooth
+CONFS= rtlbtfw.conf
+CONFSDIR= /etc/devd
+PROG= rtlbtfw
+MAN= rtlbtfw.8
+LIBADD+= usb
+SRCS= main.c rtlbt_fw.c rtlbt_hw.c
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/bluetooth/rtlbtfw/main.c b/usr.sbin/bluetooth/rtlbtfw/main.c
new file mode 100644
index 000000000000..58503b8087b5
--- /dev/null
+++ b/usr.sbin/bluetooth/rtlbtfw/main.c
@@ -0,0 +1,554 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2013 Adrian Chadd <adrian@freebsd.org>
+ * Copyright (c) 2019 Vladimir Kondratyev <wulf@FreeBSD.org>
+ * Copyright (c) 2023 Future Crew LLC.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/param.h>
+#include <sys/stat.h>
+#include <sys/endian.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <libgen.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <libusb.h>
+
+#include "rtlbt_fw.h"
+#include "rtlbt_hw.h"
+#include "rtlbt_dbg.h"
+
+#define _DEFAULT_RTLBT_FIRMWARE_PATH "/usr/share/firmware/rtlbt"
+
+int rtlbt_do_debug = 0;
+int rtlbt_do_info = 0;
+
+struct rtlbt_devid {
+ uint16_t product_id;
+ uint16_t vendor_id;
+};
+
+static struct rtlbt_devid rtlbt_list[] = {
+ /* Realtek 8821CE Bluetooth devices */
+ { .vendor_id = 0x13d3, .product_id = 0x3529 },
+
+ /* Realtek 8822CE Bluetooth devices */
+ { .vendor_id = 0x0bda, .product_id = 0xb00c },
+ { .vendor_id = 0x0bda, .product_id = 0xc822 },
+
+ /* Realtek 8851BE Bluetooth devices */
+ { .vendor_id = 0x13d3, .product_id = 0x3600 },
+
+ /* Realtek 8852AE Bluetooth devices */
+ { .vendor_id = 0x0bda, .product_id = 0x2852 },
+ { .vendor_id = 0x0bda, .product_id = 0xc852 },
+ { .vendor_id = 0x0bda, .product_id = 0x385a },
+ { .vendor_id = 0x0bda, .product_id = 0x4852 },
+ { .vendor_id = 0x04c5, .product_id = 0x165c },
+ { .vendor_id = 0x04ca, .product_id = 0x4006 },
+ { .vendor_id = 0x0cb8, .product_id = 0xc549 },
+
+ /* Realtek 8852CE Bluetooth devices */
+ { .vendor_id = 0x04ca, .product_id = 0x4007 },
+ { .vendor_id = 0x04c5, .product_id = 0x1675 },
+ { .vendor_id = 0x0cb8, .product_id = 0xc558 },
+ { .vendor_id = 0x13d3, .product_id = 0x3587 },
+ { .vendor_id = 0x13d3, .product_id = 0x3586 },
+ { .vendor_id = 0x13d3, .product_id = 0x3592 },
+ { .vendor_id = 0x0489, .product_id = 0xe122 },
+
+ /* Realtek 8852BE Bluetooth devices */
+ { .vendor_id = 0x0cb8, .product_id = 0xc559 },
+ { .vendor_id = 0x0bda, .product_id = 0x4853 },
+ { .vendor_id = 0x0bda, .product_id = 0x887b },
+ { .vendor_id = 0x0bda, .product_id = 0xb85b },
+ { .vendor_id = 0x13d3, .product_id = 0x3570 },
+ { .vendor_id = 0x13d3, .product_id = 0x3571 },
+ { .vendor_id = 0x13d3, .product_id = 0x3572 },
+ { .vendor_id = 0x13d3, .product_id = 0x3591 },
+ { .vendor_id = 0x0489, .product_id = 0xe123 },
+ { .vendor_id = 0x0489, .product_id = 0xe125 },
+
+ /* Realtek 8852BT/8852BE-VT Bluetooth devices */
+ { .vendor_id = 0x0bda, .product_id = 0x8520 },
+
+ /* Realtek 8922AE Bluetooth devices */
+ { .vendor_id = 0x0bda, .product_id = 0x8922 },
+ { .vendor_id = 0x13d3, .product_id = 0x3617 },
+ { .vendor_id = 0x13d3, .product_id = 0x3616 },
+ { .vendor_id = 0x0489, .product_id = 0xe130 },
+
+ /* Realtek 8723AE Bluetooth devices */
+ { .vendor_id = 0x0930, .product_id = 0x021d },
+ { .vendor_id = 0x13d3, .product_id = 0x3394 },
+
+ /* Realtek 8723BE Bluetooth devices */
+ { .vendor_id = 0x0489, .product_id = 0xe085 },
+ { .vendor_id = 0x0489, .product_id = 0xe08b },
+ { .vendor_id = 0x04f2, .product_id = 0xb49f },
+ { .vendor_id = 0x13d3, .product_id = 0x3410 },
+ { .vendor_id = 0x13d3, .product_id = 0x3416 },
+ { .vendor_id = 0x13d3, .product_id = 0x3459 },
+ { .vendor_id = 0x13d3, .product_id = 0x3494 },
+
+ /* Realtek 8723BU Bluetooth devices */
+ { .vendor_id = 0x7392, .product_id = 0xa611 },
+
+ /* Realtek 8723DE Bluetooth devices */
+ { .vendor_id = 0x0bda, .product_id = 0xb009 },
+ { .vendor_id = 0x2ff8, .product_id = 0xb011 },
+
+ /* Realtek 8761BUV Bluetooth devices */
+ { .vendor_id = 0x2c4e, .product_id = 0x0115 },
+ { .vendor_id = 0x2357, .product_id = 0x0604 },
+ { .vendor_id = 0x0b05, .product_id = 0x190e },
+ { .vendor_id = 0x2550, .product_id = 0x8761 },
+ { .vendor_id = 0x0bda, .product_id = 0x8771 },
+ { .vendor_id = 0x6655, .product_id = 0x8771 },
+ { .vendor_id = 0x7392, .product_id = 0xc611 },
+ { .vendor_id = 0x2b89, .product_id = 0x8761 },
+
+ /* Realtek 8821AE Bluetooth devices */
+ { .vendor_id = 0x0b05, .product_id = 0x17dc },
+ { .vendor_id = 0x13d3, .product_id = 0x3414 },
+ { .vendor_id = 0x13d3, .product_id = 0x3458 },
+ { .vendor_id = 0x13d3, .product_id = 0x3461 },
+ { .vendor_id = 0x13d3, .product_id = 0x3462 },
+
+ /* Realtek 8822BE Bluetooth devices */
+ { .vendor_id = 0x13d3, .product_id = 0x3526 },
+ { .vendor_id = 0x0b05, .product_id = 0x185c },
+
+ /* Realtek 8822CE Bluetooth devices */
+ { .vendor_id = 0x04ca, .product_id = 0x4005 },
+ { .vendor_id = 0x04c5, .product_id = 0x161f },
+ { .vendor_id = 0x0b05, .product_id = 0x18ef },
+ { .vendor_id = 0x13d3, .product_id = 0x3548 },
+ { .vendor_id = 0x13d3, .product_id = 0x3549 },
+ { .vendor_id = 0x13d3, .product_id = 0x3553 },
+ { .vendor_id = 0x13d3, .product_id = 0x3555 },
+ { .vendor_id = 0x2ff8, .product_id = 0x3051 },
+ { .vendor_id = 0x1358, .product_id = 0xc123 },
+ { .vendor_id = 0x0bda, .product_id = 0xc123 },
+ { .vendor_id = 0x0cb5, .product_id = 0xc547 },
+};
+
+static int
+rtlbt_is_realtek(struct libusb_device_descriptor *d)
+{
+ int i;
+
+ /* Search looking for whether it's a Realtek-based device */
+ for (i = 0; i < (int) nitems(rtlbt_list); i++) {
+ if ((rtlbt_list[i].product_id == d->idProduct) &&
+ (rtlbt_list[i].vendor_id == d->idVendor)) {
+ rtlbt_info("found USB Realtek");
+ return (1);
+ }
+ }
+
+ /* Not found */
+ return (0);
+}
+
+static int
+rtlbt_is_bluetooth(struct libusb_device *dev)
+{
+ struct libusb_config_descriptor *cfg;
+ const struct libusb_interface *ifc;
+ const struct libusb_interface_descriptor *d;
+ int r;
+
+ r = libusb_get_active_config_descriptor(dev, &cfg);
+ if (r < 0) {
+ rtlbt_err("Cannot retrieve config descriptor: %s",
+ libusb_error_name(r));
+ return (0);
+ }
+
+ if (cfg->bNumInterfaces != 0) {
+ /* Only 0-th HCI/ACL interface is supported by downloader */
+ ifc = &cfg->interface[0];
+ if (ifc->num_altsetting != 0) {
+ /* BT HCI/ACL interface has no altsettings */
+ d = &ifc->altsetting[0];
+ /* Check if interface is a bluetooth */
+ if (d->bInterfaceClass == LIBUSB_CLASS_WIRELESS &&
+ d->bInterfaceSubClass == 0x01 &&
+ d->bInterfaceProtocol == 0x01) {
+ rtlbt_info("found USB Realtek");
+ libusb_free_config_descriptor(cfg);
+ return (1);
+ }
+ }
+ }
+ libusb_free_config_descriptor(cfg);
+
+ /* Not found */
+ return (0);
+}
+
+static libusb_device *
+rtlbt_find_device(libusb_context *ctx, int bus_id, int dev_id)
+{
+ libusb_device **list, *dev = NULL, *found = NULL;
+ struct libusb_device_descriptor d;
+ ssize_t cnt, i;
+ int r;
+
+ cnt = libusb_get_device_list(ctx, &list);
+ if (cnt < 0) {
+ rtlbt_err("libusb_get_device_list() failed: code %lld",
+ (long long int) cnt);
+ return (NULL);
+ }
+
+ /*
+ * Scan through USB device list.
+ */
+ for (i = 0; i < cnt; i++) {
+ dev = list[i];
+ if (bus_id == libusb_get_bus_number(dev) &&
+ dev_id == libusb_get_device_address(dev)) {
+ /* Get the device descriptor for this device entry */
+ r = libusb_get_device_descriptor(dev, &d);
+ if (r != 0) {
+ rtlbt_err("libusb_get_device_descriptor: %s",
+ libusb_strerror(r));
+ break;
+ }
+
+ /* For non-Realtek match on the vendor/product id */
+ if (rtlbt_is_realtek(&d)) {
+ /*
+ * Take a reference so it's not freed later on.
+ */
+ found = libusb_ref_device(dev);
+ break;
+ }
+ /* For Realtek vendor match on the interface class */
+ if (d.idVendor == 0x0bda && rtlbt_is_bluetooth(dev)) {
+ /*
+ * Take a reference so it's not freed later on.
+ */
+ found = libusb_ref_device(dev);
+ break;
+ }
+ }
+ }
+
+ libusb_free_device_list(list, 1);
+ return (found);
+}
+
+static void
+rtlbt_dump_version(ng_hci_read_local_ver_rp *ver)
+{
+ rtlbt_info("hci_version 0x%02x", ver->hci_version);
+ rtlbt_info("hci_revision 0x%04x", le16toh(ver->hci_revision));
+ rtlbt_info("lmp_version 0x%02x", ver->lmp_version);
+ rtlbt_info("lmp_subversion 0x%04x", le16toh(ver->lmp_subversion));
+}
+
+/*
+ * Parse ugen name and extract device's bus and address
+ */
+
+static int
+parse_ugen_name(char const *ugen, uint8_t *bus, uint8_t *addr)
+{
+ char *ep;
+
+ if (strncmp(ugen, "ugen", 4) != 0)
+ return (-1);
+
+ *bus = (uint8_t) strtoul(ugen + 4, &ep, 10);
+ if (*ep != '.')
+ return (-1);
+
+ *addr = (uint8_t) strtoul(ep + 1, &ep, 10);
+ if (*ep != '\0')
+ return (-1);
+
+ return (0);
+}
+
+static void
+usage(void)
+{
+ fprintf(stderr,
+ "Usage: rtlbtfw (-D) -d ugenX.Y (-f firmware path) (-I)\n");
+ fprintf(stderr, " -D: enable debugging\n");
+ fprintf(stderr, " -d: device to operate upon\n");
+ fprintf(stderr, " -f: firmware path, if not default\n");
+ fprintf(stderr, " -I: enable informational output\n");
+ exit(127);
+}
+
+int
+main(int argc, char *argv[])
+{
+ libusb_context *ctx = NULL;
+ libusb_device *dev = NULL;
+ libusb_device_handle *hdl = NULL;
+ ng_hci_read_local_ver_rp ver;
+ int r;
+ uint8_t bus_id = 0, dev_id = 0;
+ int devid_set = 0;
+ int n;
+ 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;
+ struct rtlbt_firmware fw, cfg;
+ enum rtlbt_fw_type fw_type;
+ uint16_t fw_lmp_subversion;
+
+ /* Parse command line arguments */
+ while ((n = getopt(argc, argv, "Dd:f:hIm:p:v:")) != -1) {
+ switch (n) {
+ case 'd': /* ugen device name */
+ devid_set = 1;
+ if (parse_ugen_name(optarg, &bus_id, &dev_id) < 0)
+ usage();
+ break;
+ case 'D':
+ rtlbt_do_debug = 1;
+ break;
+ case 'f': /* firmware dir */
+ if (firmware_dir)
+ free(firmware_dir);
+ firmware_dir = strdup(optarg);
+ break;
+ case 'I':
+ rtlbt_do_info = 1;
+ break;
+ case 'h':
+ default:
+ usage();
+ break;
+ /* NOT REACHED */
+ }
+ }
+
+ /* Ensure the devid was given! */
+ if (devid_set == 0) {
+ usage();
+ /* NOTREACHED */
+ }
+
+ /* libusb setup */
+ r = libusb_init(&ctx);
+ if (r != 0) {
+ rtlbt_err("libusb_init failed: code %d", r);
+ exit(127);
+ }
+
+ rtlbt_debug("opening dev %d.%d", (int) bus_id, (int) dev_id);
+
+ /* Find a device based on the bus/dev id */
+ dev = rtlbt_find_device(ctx, bus_id, dev_id);
+ if (dev == NULL) {
+ rtlbt_err("device not found");
+ goto shutdown;
+ }
+
+ /* XXX enforce that bInterfaceNumber is 0 */
+
+ /* XXX enforce the device/product id if they're non-zero */
+
+ /* Grab device handle */
+ r = libusb_open(dev, &hdl);
+ if (r != 0) {
+ rtlbt_err("libusb_open() failed: code %d", r);
+ goto shutdown;
+ }
+
+ /* Check if ng_ubt is attached */
+ r = libusb_kernel_driver_active(hdl, 0);
+ if (r < 0) {
+ rtlbt_err("libusb_kernel_driver_active() failed: code %d", r);
+ goto shutdown;
+ }
+ if (r > 0) {
+ rtlbt_info("Firmware has already been downloaded");
+ retcode = 0;
+ goto shutdown;
+ }
+
+ /* Get local version */
+ r = rtlbt_read_local_ver(hdl, &ver);
+ if (r < 0) {
+ rtlbt_err("rtlbt_read_local_ver() failed code %d", r);
+ goto shutdown;
+ }
+ rtlbt_dump_version(&ver);
+
+ ic = rtlbt_get_ic(ver.lmp_subversion, ver.hci_revision,
+ ver.hci_version);
+ if (ic == NULL) {
+ rtlbt_err("rtlbt_get_ic() failed: Unknown IC");
+ goto shutdown;
+ }
+
+ /* Default the firmware path */
+ if (firmware_dir == NULL)
+ firmware_dir = strdup(_DEFAULT_RTLBT_FIRMWARE_PATH);
+
+ 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;
+
+ rtlbt_debug("firmware_path = %s", firmware_path);
+
+ rtlbt_info("loading firmware %s", firmware_path);
+
+ /* Read in the firmware */
+ if (rtlbt_fw_read(&fw, firmware_path) <= 0) {
+ rtlbt_debug("rtlbt_fw_read() failed");
+ return (-1);
+ }
+
+ fw_type = rtlbt_get_fw_type(&fw, &fw_lmp_subversion);
+ if (fw_type == RTLBT_FW_TYPE_UNKNOWN &&
+ (ic->flags & RTLBT_IC_FLAG_SIMPLE) == 0) {
+ rtlbt_debug("Unknown firmware type");
+ goto shutdown;
+ }
+
+ if (fw_type != RTLBT_FW_TYPE_UNKNOWN) {
+
+ /* Match hardware and firmware lmp_subversion */
+ if (fw_lmp_subversion != ver.lmp_subversion) {
+ rtlbt_err("firmware is for %x but this is a %x",
+ fw_lmp_subversion, ver.lmp_subversion);
+ goto shutdown;
+ }
+
+ /* Query a ROM version */
+ r = rtlbt_read_rom_ver(hdl, &rom_version);
+ if (r < 0) {
+ rtlbt_err("rtlbt_read_rom_ver() failed code %d", r);
+ goto shutdown;
+ }
+ rtlbt_debug("rom_version = %d", rom_version);
+
+ /* Load in the firmware */
+ 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("Parsing firmware file failed");
+ goto shutdown;
+ }
+
+ config_path = rtlbt_get_fwname(ic->fw_name, firmware_dir,
+ "_config.bin");
+ if (config_path == NULL)
+ goto shutdown;
+
+ rtlbt_info("loading config %s", config_path);
+
+ /* Read in the config file */
+ if (rtlbt_fw_read(&cfg, config_path) <= 0) {
+ rtlbt_err("rtlbt_fw_read() failed");
+ if ((ic->flags & RTLBT_IC_FLAG_CONFIG) != 0)
+ goto shutdown;
+ } else {
+ r = rtlbt_append_fwfile(&fw, &cfg);
+ rtlbt_fw_free(&cfg);
+ if (r < 0) {
+ rtlbt_err("Appending config file failed");
+ goto shutdown;
+ }
+ }
+ }
+
+ r = rtlbt_load_fwfile(hdl, &fw);
+ if (r < 0) {
+ rtlbt_debug("Loading firmware file failed");
+ goto shutdown;
+ }
+
+ /* free it */
+ rtlbt_fw_free(&fw);
+
+ rtlbt_info("Firmware download complete");
+
+ /* Execute Read Local Version one more time */
+ r = rtlbt_read_local_ver(hdl, &ver);
+ if (r < 0) {
+ rtlbt_err("rtlbt_read_local_ver() failed code %d", r);
+ goto shutdown;
+ }
+ rtlbt_dump_version(&ver);
+
+ retcode = 0;
+
+ /* Ask kernel driver to probe and attach device again */
+ r = libusb_reset_device(hdl);
+ if (r != 0)
+ rtlbt_err("libusb_reset_device() failed: %s",
+ libusb_strerror(r));
+
+shutdown:
+
+ /* Shutdown */
+
+ if (hdl != NULL)
+ libusb_close(hdl);
+
+ if (dev != NULL)
+ libusb_unref_device(dev);
+
+ if (ctx != NULL)
+ libusb_exit(ctx);
+
+ if (retcode == 0)
+ rtlbt_info("Firmware download is successful!");
+ else
+ rtlbt_err("Firmware download failed!");
+
+ return (retcode);
+}
diff --git a/usr.sbin/bluetooth/rtlbtfw/rtlbt_dbg.h b/usr.sbin/bluetooth/rtlbtfw/rtlbt_dbg.h
new file mode 100644
index 000000000000..54c982119d40
--- /dev/null
+++ b/usr.sbin/bluetooth/rtlbtfw/rtlbt_dbg.h
@@ -0,0 +1,46 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2013 Adrian Chadd <adrian@freebsd.org>
+ * Copyright (c) 2019 Vladimir Kondratyev <wulf@FreeBSD.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef __RTLBT_DEBUG_H__
+#define __RTLBT_DEBUG_H__
+
+extern int rtlbt_do_debug;
+extern int rtlbt_do_info;
+
+#define rtlbt_err(fmt, ...) \
+ fprintf(stderr, "rtlbtfw: %s: "fmt"\n", __func__, ##__VA_ARGS__)
+#define rtlbt_info(fmt, ...) do { \
+ if (rtlbt_do_info) \
+ fprintf(stderr, "%s: "fmt"\n", __func__, ##__VA_ARGS__);\
+} while (0)
+#define rtlbt_debug(fmt, ...) do { \
+ if (rtlbt_do_debug) \
+ fprintf(stderr, "%s: "fmt"\n", __func__, ##__VA_ARGS__);\
+} while (0)
+
+#endif
diff --git a/usr.sbin/bluetooth/rtlbtfw/rtlbt_fw.c b/usr.sbin/bluetooth/rtlbtfw/rtlbt_fw.c
new file mode 100644
index 000000000000..d7e9f2f939c6
--- /dev/null
+++ b/usr.sbin/bluetooth/rtlbtfw/rtlbt_fw.c
@@ -0,0 +1,582 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2013 Adrian Chadd <adrian@freebsd.org>
+ * Copyright (c) 2019 Vladimir Kondratyev <wulf@FreeBSD.org>
+ * Copyright (c) 2023 Future Crew LLC.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/param.h>
+#include <sys/endian.h>
+#include <sys/stat.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "rtlbt_fw.h"
+#include "rtlbt_dbg.h"
+
+static const struct rtlbt_id_table rtlbt_ic_id_table[] = {
+ { /* 8723A */
+ .lmp_subversion = RTLBT_ROM_LMP_8723A,
+ .hci_revision = 0xb,
+ .hci_version = 0x6,
+ .flags = RTLBT_IC_FLAG_SIMPLE,
+ .fw_name = "rtl8723a",
+ }, { /* 8723B */
+ .lmp_subversion = RTLBT_ROM_LMP_8723B,
+ .hci_revision = 0xb,
+ .hci_version = 0x6,
+ .fw_name = "rtl8723b",
+ }, { /* 8723D */
+ .lmp_subversion = RTLBT_ROM_LMP_8723B,
+ .hci_revision = 0xd,
+ .hci_version = 0x8,
+ .flags = RTLBT_IC_FLAG_CONFIG,
+ .fw_name = "rtl8723d",
+ }, { /* 8821A */
+ .lmp_subversion = RTLBT_ROM_LMP_8821A,
+ .hci_revision = 0xa,
+ .hci_version = 0x6,
+ .fw_name = "rtl8821a",
+ }, { /* 8821C */
+ .lmp_subversion = RTLBT_ROM_LMP_8821A,
+ .hci_revision = 0xc,
+ .hci_version = 0x8,
+ .flags = RTLBT_IC_FLAG_MSFT,
+ .fw_name = "rtl8821c",
+ }, { /* 8761A */
+ .lmp_subversion = RTLBT_ROM_LMP_8761A,
+ .hci_revision = 0xa,
+ .hci_version = 0x6,
+ .fw_name = "rtl8761a",
+ }, { /* 8761BU */
+ .lmp_subversion = RTLBT_ROM_LMP_8761A,
+ .hci_revision = 0xb,
+ .hci_version = 0xa,
+ .fw_name = "rtl8761bu",
+ }, { /* 8822C with USB interface */
+ .lmp_subversion = RTLBT_ROM_LMP_8822B,
+ .hci_revision = 0xc,
+ .hci_version = 0xa,
+ .flags = RTLBT_IC_FLAG_MSFT,
+ .fw_name = "rtl8822cu",
+ }, { /* 8822B */
+ .lmp_subversion = RTLBT_ROM_LMP_8822B,
+ .hci_revision = 0xb,
+ .hci_version = 0x7,
+ .flags = RTLBT_IC_FLAG_CONFIG | RTLBT_IC_FLAG_MSFT,
+ .fw_name = "rtl8822b",
+ }, { /* 8852A */
+ .lmp_subversion = RTLBT_ROM_LMP_8852A,
+ .hci_revision = 0xa,
+ .hci_version = 0xb,
+ .flags = RTLBT_IC_FLAG_MSFT,
+ .fw_name = "rtl8852au",
+ }, { /* 8852B */
+ .lmp_subversion = RTLBT_ROM_LMP_8852A,
+ .hci_revision = 0xb,
+ .hci_version = 0xb,
+ .flags = RTLBT_IC_FLAG_MSFT,
+ .fw_name = "rtl8852bu",
+ }, { /* 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",
+ }, { /* 8922A */
+ .lmp_subversion = RTLBT_ROM_LMP_8922A,
+ .hci_revision = 0xa,
+ .hci_version = 0xc,
+ .flags = RTLBT_IC_FLAG_MSFT,
+ .fw_name = "rtl8922au",
+ }, { /* 8852BT/8852BE-VT */
+ .lmp_subversion = RTLBT_ROM_LMP_8852A,
+ .hci_revision = 0x87,
+ .hci_version = 0xc,
+ .flags = RTLBT_IC_FLAG_MSFT,
+ .fw_name = "rtl8852btu",
+ },
+};
+
+static const uint16_t project_ids[] = {
+ [ 0 ] = RTLBT_ROM_LMP_8723A,
+ [ 1 ] = RTLBT_ROM_LMP_8723B,
+ [ 2 ] = RTLBT_ROM_LMP_8821A,
+ [ 3 ] = RTLBT_ROM_LMP_8761A,
+ [ 7 ] = RTLBT_ROM_LMP_8703B,
+ [ 8 ] = RTLBT_ROM_LMP_8822B,
+ [ 9 ] = RTLBT_ROM_LMP_8723B, /* 8723DU */
+ [ 10 ] = RTLBT_ROM_LMP_8821A, /* 8821CU */
+ [ 13 ] = RTLBT_ROM_LMP_8822B, /* 8822CU */
+ [ 14 ] = RTLBT_ROM_LMP_8761A, /* 8761BU */
+ [ 18 ] = RTLBT_ROM_LMP_8852A, /* 8852AU */
+ [ 19 ] = RTLBT_ROM_LMP_8723B, /* 8723FU */
+ [ 20 ] = RTLBT_ROM_LMP_8852A, /* 8852BU */
+ [ 25 ] = RTLBT_ROM_LMP_8852A, /* 8852CU */
+ [ 33 ] = RTLBT_ROM_LMP_8822B, /* 8822EU */
+ [ 36 ] = RTLBT_ROM_LMP_8851B, /* 8851BU */
+ [ 44 ] = RTLBT_ROM_LMP_8922A, /* 8922A */
+ [ 47 ] = RTLBT_ROM_LMP_8852A, /* 8852BT */
+};
+
+/* Signatures */
+static const uint8_t fw_header_sig_v1[8] =
+ {0x52, 0x65, 0x61, 0x6C, 0x74, 0x65, 0x63, 0x68}; /* Realtech */
+static const uint8_t fw_header_sig_v2[8] =
+ {0x52, 0x54, 0x42, 0x54, 0x43, 0x6F, 0x72, 0x65}; /* RTBTCore */
+static const uint8_t fw_ext_sig[4] = {0x51, 0x04, 0xFD, 0x77};
+
+int
+rtlbt_fw_read(struct rtlbt_firmware *fw, const char *fwname)
+{
+ int fd;
+ struct stat sb;
+ unsigned char *buf;
+ ssize_t r;
+
+ fd = open(fwname, O_RDONLY);
+ if (fd < 0) {
+ warn("%s: open: %s", __func__, fwname);
+ return (0);
+ }
+
+ if (fstat(fd, &sb) != 0) {
+ warn("%s: stat: %s", __func__, fwname);
+ close(fd);
+ return (0);
+ }
+
+ buf = calloc(1, sb.st_size);
+ if (buf == NULL) {
+ warn("%s: calloc", __func__);
+ close(fd);
+ return (0);
+ }
+
+ /* XXX handle partial reads */
+ r = read(fd, buf, sb.st_size);
+ if (r < 0) {
+ warn("%s: read", __func__);
+ free(buf);
+ close(fd);
+ return (0);
+ }
+
+ if (r != sb.st_size) {
+ rtlbt_err("read len %d != file size %d",
+ (int) r,
+ (int) sb.st_size);
+ free(buf);
+ close(fd);
+ return (0);
+ }
+
+ /* We have everything, so! */
+
+ memset(fw, 0, sizeof(*fw));
+
+ fw->fwname = strdup(fwname);
+ fw->len = sb.st_size;
+ fw->buf = buf;
+
+ close(fd);
+ return (1);
+}
+
+void
+rtlbt_fw_free(struct rtlbt_firmware *fw)
+{
+ if (fw->fwname)
+ free(fw->fwname);
+ if (fw->buf)
+ free(fw->buf);
+ memset(fw, 0, sizeof(*fw));
+}
+
+char *
+rtlbt_get_fwname(const char *fw_name, const char *prefix, const char *suffix)
+{
+ char *fwname;
+
+ asprintf(&fwname, "%s/%s%s", prefix, fw_name, suffix);
+
+ return (fwname);
+}
+
+const struct rtlbt_id_table *
+rtlbt_get_ic(uint16_t lmp_subversion, uint16_t hci_revision,
+ uint8_t hci_version)
+{
+ unsigned int i;
+
+ for (i = 0; i < nitems(rtlbt_ic_id_table); i++) {
+ if (rtlbt_ic_id_table[i].lmp_subversion == lmp_subversion &&
+ rtlbt_ic_id_table[i].hci_revision == hci_revision &&
+ rtlbt_ic_id_table[i].hci_version == hci_version)
+ return (rtlbt_ic_id_table + i);
+ }
+
+ return (NULL);
+}
+
+enum rtlbt_fw_type
+rtlbt_get_fw_type(struct rtlbt_firmware *fw, uint16_t *fw_lmp_subversion)
+{
+ enum rtlbt_fw_type fw_type;
+ size_t fw_header_len;
+ uint8_t *ptr;
+ uint8_t opcode, oplen, project_id;
+
+ if (fw->len < 8) {
+ rtlbt_err("firmware file too small");
+ return (RTLBT_FW_TYPE_UNKNOWN);
+ }
+
+ if (memcmp(fw->buf, fw_header_sig_v1, sizeof(fw_header_sig_v1)) == 0) {
+ fw_type = RTLBT_FW_TYPE_V1;
+ fw_header_len = sizeof(struct rtlbt_fw_header_v1);
+ } else
+ 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
+ return (RTLBT_FW_TYPE_UNKNOWN);
+
+ if (fw->len < fw_header_len + sizeof(fw_ext_sig) + 4) {
+ rtlbt_err("firmware file too small");
+ return (RTLBT_FW_TYPE_UNKNOWN);
+ }
+
+ ptr = fw->buf + fw->len - sizeof(fw_ext_sig);
+ if (memcmp(ptr, fw_ext_sig, sizeof(fw_ext_sig)) != 0) {
+ rtlbt_err("invalid extension section signature");
+ return (RTLBT_FW_TYPE_UNKNOWN);
+ }
+
+ do {
+ opcode = *--ptr;
+ oplen = *--ptr;
+ ptr -= oplen;
+
+ rtlbt_debug("code=%x len=%x", opcode, oplen);
+
+ if (opcode == 0x00) {
+ if (oplen != 1) {
+ rtlbt_err("invalid instruction length");
+ return (RTLBT_FW_TYPE_UNKNOWN);
+ }
+ project_id = *ptr;
+ rtlbt_debug("project_id=%x", project_id);
+ if (project_id >= nitems(project_ids) ||
+ project_ids[project_id] == 0) {
+ rtlbt_err("unknown project id %x", project_id);
+ return (RTLBT_FW_TYPE_UNKNOWN);
+ }
+ *fw_lmp_subversion = project_ids[project_id];
+ return (fw_type);
+ }
+ } while (opcode != 0xff && ptr > fw->buf + fw_header_len);
+
+ rtlbt_err("can not find project id instruction");
+ return (RTLBT_FW_TYPE_UNKNOWN);
+};
+
+int
+rtlbt_parse_fwfile_v1(struct rtlbt_firmware *fw, uint8_t rom_version)
+{
+ struct rtlbt_fw_header_v1 *fw_header;
+ uint8_t *patch_buf;
+ unsigned int i;
+ const uint8_t *chip_id_base;
+ uint32_t patch_offset;
+ uint16_t patch_length, num_patches;
+
+ fw_header = (struct rtlbt_fw_header_v1 *)fw->buf;
+ num_patches = le16toh(fw_header->num_patches);
+ rtlbt_debug("fw_version=%x, num_patches=%d",
+ le32toh(fw_header->fw_version), num_patches);
+
+ /* Find a right patch for the chip. */
+ if (fw->len < sizeof(struct rtlbt_fw_header_v1) +
+ sizeof(fw_ext_sig) + 4 + 8 * num_patches) {
+ errno = EINVAL;
+ return (-1);
+ }
+
+ chip_id_base = fw->buf + sizeof(struct rtlbt_fw_header_v1);
+ for (i = 0; i < num_patches; i++) {
+ if (le16dec(chip_id_base + i * 2) != rom_version + 1)
+ continue;
+ patch_length = le16dec(chip_id_base + 2 * (num_patches + i));
+ patch_offset = le32dec(chip_id_base + 4 * (num_patches + i));
+ break;
+ }
+
+ if (i >= num_patches) {
+ rtlbt_err("can not find patch for chip id %d", rom_version);
+ errno = EINVAL;
+ return (-1);
+ }
+
+ rtlbt_debug(
+ "index=%d length=%x offset=%x", i, patch_length, patch_offset);
+ if (fw->len < patch_offset + patch_length) {
+ errno = EINVAL;
+ return (-1);
+ }
+
+ patch_buf = malloc(patch_length);
+ if (patch_buf == NULL) {
+ errno = ENOMEM;
+ return (-1);
+ }
+
+ memcpy(patch_buf, fw->buf + patch_offset, patch_length - 4);
+ memcpy(patch_buf + patch_length - 4, &fw_header->fw_version, 4);
+
+ free(fw->buf);
+ fw->buf = patch_buf;
+ fw->len = patch_length;
+
+ 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)
+{
+ uint8_t *buf;
+ int len;
+
+ len = fw->len + opt->len;
+ buf = realloc(fw->buf, len);
+ if (buf == NULL)
+ return (-1);
+ memcpy(buf + fw->len, opt->buf, opt->len);
+ fw->buf = buf;
+ fw->len = len;
+
+ return (0);
+}
diff --git a/usr.sbin/bluetooth/rtlbtfw/rtlbt_fw.h b/usr.sbin/bluetooth/rtlbtfw/rtlbt_fw.h
new file mode 100644
index 000000000000..e9af6c93950e
--- /dev/null
+++ b/usr.sbin/bluetooth/rtlbtfw/rtlbt_fw.h
@@ -0,0 +1,140 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2013 Adrian Chadd <adrian@freebsd.org>
+ * Copyright (c) 2019 Vladimir Kondratyev <wulf@FreeBSD.org>
+ * Copyright (c) 2023 Future Crew LLC.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef __RTLBT_FW_H__
+#define __RTLBT_FW_H__
+
+#include <sys/queue.h>
+#include <sys/queue_mergesort.h>
+
+#define RTLBT_ROM_LMP_8703B 0x8703
+#define RTLBT_ROM_LMP_8723A 0x1200
+#define RTLBT_ROM_LMP_8723B 0x8723
+#define RTLBT_ROM_LMP_8821A 0x8821
+#define RTLBT_ROM_LMP_8761A 0x8761
+#define RTLBT_ROM_LMP_8822B 0x8822
+#define RTLBT_ROM_LMP_8852A 0x8852
+#define RTLBT_ROM_LMP_8851B 0x8851
+#define RTLBT_ROM_LMP_8922A 0x8922
+
+#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,
+ RTLBT_FW_TYPE_V2,
+};
+
+struct rtlbt_id_table {
+ uint16_t lmp_subversion;
+ uint16_t hci_revision;
+ uint8_t hci_version;
+ uint8_t flags;
+#define RTLBT_IC_FLAG_SIMPLE (0 << 1)
+#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 {
+ char *fwname;
+ size_t len;
+ 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;
+ uint16_t num_patches;
+} __attribute__ ((packed));
+
+struct rtlbt_fw_header_v2 {
+ uint8_t signature[8];
+ uint8_t fw_version[8];
+ 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,
+ const char *suffix);
+const struct rtlbt_id_table *rtlbt_get_ic(uint16_t lmp_subversion,
+ uint16_t hci_revision, uint8_t hci_version);
+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
new file mode 100644
index 000000000000..82e22d406ea9
--- /dev/null
+++ b/usr.sbin/bluetooth/rtlbtfw/rtlbt_hw.c
@@ -0,0 +1,270 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2019 Vladimir Kondratyev <wulf@FreeBSD.org>
+ * Copyright (c) 2023 Future Crew LLC.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/param.h>
+#include <sys/endian.h>
+#include <sys/stat.h>
+
+#include <netgraph/bluetooth/include/ng_hci.h>
+
+#include <err.h>
+#include <errno.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <libusb.h>
+
+#include "rtlbt_fw.h"
+#include "rtlbt_hw.h"
+#include "rtlbt_dbg.h"
+
+static int
+rtlbt_hci_command(struct libusb_device_handle *hdl, struct rtlbt_hci_cmd *cmd,
+ void *event, int size, int *transferred, int timeout)
+{
+ struct timespec to, now, remains;
+ int ret;
+
+ ret = libusb_control_transfer(hdl,
+ LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_DEVICE,
+ 0,
+ 0,
+ 0,
+ (uint8_t *)cmd,
+ RTLBT_HCI_CMD_SIZE(cmd),
+ timeout);
+
+ if (ret < 0) {
+ rtlbt_err("libusb_control_transfer() failed: err=%s",
+ libusb_strerror(ret));
+ return (ret);
+ }
+
+ clock_gettime(CLOCK_MONOTONIC, &now);
+ to = RTLBT_MSEC2TS(timeout);
+ timespecadd(&to, &now, &to);
+
+ do {
+ timespecsub(&to, &now, &remains);
+ ret = libusb_interrupt_transfer(hdl,
+ RTLBT_INTERRUPT_ENDPOINT_ADDR,
+ event,
+ size,
+ transferred,
+ RTLBT_TS2MSEC(remains) + 1);
+
+ if (ret < 0) {
+ rtlbt_err("libusb_interrupt_transfer() failed: err=%s",
+ libusb_strerror(ret));
+ return (ret);
+ }
+
+ switch (((struct rtlbt_hci_event *)event)->header.event) {
+ case NG_HCI_EVENT_COMMAND_COMPL:
+ if (*transferred <
+ (int)offsetof(struct rtlbt_hci_event_cmd_compl, data))
+ break;
+ if (cmd->opcode !=
+ ((struct rtlbt_hci_event_cmd_compl *)event)->opcode)
+ break;
+ return (0);
+ default:
+ break;
+ }
+ rtlbt_debug("Stray HCI event: %x",
+ ((struct rtlbt_hci_event *)event)->header.event);
+ } while (timespeccmp(&to, &now, >));
+
+ rtlbt_err("libusb_interrupt_transfer() failed: err=%s",
+ libusb_strerror(LIBUSB_ERROR_TIMEOUT));
+
+ return (LIBUSB_ERROR_TIMEOUT);
+}
+
+int
+rtlbt_read_local_ver(struct libusb_device_handle *hdl,
+ ng_hci_read_local_ver_rp *ver)
+{
+ int ret, transferred;
+ struct rtlbt_hci_event_cmd_compl *event;
+ struct rtlbt_hci_cmd cmd = {
+ .opcode = htole16(NG_HCI_OPCODE(NG_HCI_OGF_INFO,
+ NG_HCI_OCF_READ_LOCAL_VER)),
+ .length = 0,
+ };
+ uint8_t buf[RTLBT_HCI_EVT_COMPL_SIZE(ng_hci_read_local_ver_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 local version: code=%d, size=%d",
+ ret,
+ transferred);
+ return (-1);
+ }
+
+ event = (struct rtlbt_hci_event_cmd_compl *)buf;
+ memcpy(ver, event->data, sizeof(ng_hci_read_local_ver_rp));
+
+ return (0);
+}
+
+int
+rtlbt_read_rom_ver(struct libusb_device_handle *hdl, uint8_t *ver)
+{
+ int ret, transferred;
+ struct rtlbt_hci_event_cmd_compl *event;
+ struct rtlbt_hci_cmd cmd = {
+ .opcode = htole16(0xfc6d),
+ .length = 0,
+ };
+ uint8_t buf[RTLBT_HCI_EVT_COMPL_SIZE(struct rtlbt_rom_ver_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 ROM version: code=%d, size=%d",
+ ret,
+ transferred);
+ return (-1);
+ }
+
+ event = (struct rtlbt_hci_event_cmd_compl *)buf;
+ *ver = ((struct rtlbt_rom_ver_rp *)event->data)->version;
+
+ 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)
+{
+ uint8_t cmd_buf[RTLBT_HCI_MAX_CMD_SIZE];
+ struct rtlbt_hci_cmd *cmd = (struct rtlbt_hci_cmd *)cmd_buf;
+ struct rtlbt_hci_dl_cmd *dl_cmd = (struct rtlbt_hci_dl_cmd *)cmd->data;
+ uint8_t evt_buf[RTLBT_HCI_EVT_COMPL_SIZE(struct rtlbt_hci_dl_rp)];
+ uint8_t *data = fw->buf;
+ int frag_num = fw->len / RTLBT_MAX_CMD_DATA_LEN + 1;
+ int frag_len = RTLBT_MAX_CMD_DATA_LEN;
+ int i, j;
+ int ret, transferred;
+
+ for (i = 0, j = 0; i < frag_num; i++, j++) {
+
+ rtlbt_debug("download fw (%d/%d)", i + 1, frag_num);
+
+ memset(cmd_buf, 0, sizeof(cmd_buf));
+ cmd->opcode = htole16(0xfc20);
+ if (j > 0x7f)
+ j = 1;
+ dl_cmd->index = j;
+
+ if (i == (frag_num - 1)) {
+ dl_cmd->index |= 0x80; /* data end */
+ frag_len = fw->len % RTLBT_MAX_CMD_DATA_LEN;
+ }
+ cmd->length = frag_len + 1;
+ memcpy(dl_cmd->data, data, frag_len);
+
+ /* Send download command */
+ ret = rtlbt_hci_command(hdl,
+ cmd,
+ evt_buf,
+ sizeof(evt_buf),
+ &transferred,
+ RTLBT_HCI_CMD_TIMEOUT);
+ if (ret < 0) {
+ rtlbt_err("download fw command failed (%d)", errno);
+ goto out;
+ }
+ if (transferred != sizeof(evt_buf)) {
+ rtlbt_err("download fw event length mismatch");
+ errno = EIO;
+ ret = -1;
+ goto out;
+ }
+
+ data += RTLBT_MAX_CMD_DATA_LEN;
+ }
+
+out:
+ return (ret);
+}
diff --git a/usr.sbin/bluetooth/rtlbtfw/rtlbt_hw.h b/usr.sbin/bluetooth/rtlbtfw/rtlbt_hw.h
new file mode 100644
index 000000000000..a7200a440272
--- /dev/null
+++ b/usr.sbin/bluetooth/rtlbtfw/rtlbt_hw.h
@@ -0,0 +1,117 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2019 Vladimir Kondratyev <wulf@FreeBSD.org>
+ * Copyright (c) 2023 Future Crew LLC.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+#ifndef __RTLBT_HW_H__
+#define __RTLBT_HW_H__
+
+#include <netgraph/bluetooth/include/ng_hci.h>
+
+/* USB control request (HCI command) structure */
+struct rtlbt_hci_cmd {
+ uint16_t opcode;
+ uint8_t length;
+ uint8_t data[];
+} __attribute__ ((packed));
+
+#define RTLBT_HCI_CMD_SIZE(cmd) \
+ ((cmd)->length + offsetof(struct rtlbt_hci_cmd, data))
+
+/* USB interrupt transfer HCI event header structure */
+struct rtlbt_hci_evhdr {
+ uint8_t event;
+ uint8_t length;
+} __attribute__ ((packed));
+
+/* USB interrupt transfer (generic HCI event) structure */
+struct rtlbt_hci_event {
+ struct rtlbt_hci_evhdr header;
+ uint8_t data[];
+} __attribute__ ((packed));
+
+/* USB interrupt transfer (HCI command completion event) structure */
+struct rtlbt_hci_event_cmd_compl {
+ struct rtlbt_hci_evhdr header;
+ uint8_t numpkt;
+ uint16_t opcode;
+ uint8_t data[];
+} __attribute__ ((packed));
+
+#define RTLBT_HCI_EVT_COMPL_SIZE(payload) \
+ (offsetof(struct rtlbt_hci_event_cmd_compl, data) + sizeof(payload))
+
+#define RTLBT_CONTROL_ENDPOINT_ADDR 0x00
+#define RTLBT_INTERRUPT_ENDPOINT_ADDR 0x81
+
+#define RTLBT_HCI_MAX_CMD_SIZE 256
+#define RTLBT_HCI_MAX_EVENT_SIZE 16
+
+#define RTLBT_MSEC2TS(msec) \
+ (struct timespec) { \
+ .tv_sec = (msec) / 1000, \
+ .tv_nsec = ((msec) % 1000) * 1000000 \
+ };
+#define RTLBT_TS2MSEC(ts) ((ts).tv_sec * 1000 + (ts).tv_nsec / 1000000)
+#define RTLBT_HCI_CMD_TIMEOUT 2000 /* ms */
+#define RTLBT_LOADCMPL_TIMEOUT 5000 /* ms */
+
+#define RTLBT_MAX_CMD_DATA_LEN 252
+
+struct rtlbt_rom_ver_rp {
+ uint8_t status;
+ uint8_t version;
+} __attribute__ ((packed));
+
+struct rtlbt_hci_dl_cmd {
+ uint8_t index;
+ uint8_t data[RTLBT_MAX_CMD_DATA_LEN];
+} __attribute__ ((packed));
+
+struct rtlbt_hci_dl_rp {
+ uint8_t status;
+ 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);
+
+#endif
diff --git a/usr.sbin/bluetooth/rtlbtfw/rtlbtfw.8 b/usr.sbin/bluetooth/rtlbtfw/rtlbtfw.8
new file mode 100644
index 000000000000..5cae9c9d288d
--- /dev/null
+++ b/usr.sbin/bluetooth/rtlbtfw/rtlbtfw.8
@@ -0,0 +1,99 @@
+.\" Copyright (c) 2013, 2016 Adrian Chadd <adrian@freebsd.org>
+.\" Copyright (c) 2019 Vladimir Kondratyev <wulf@FreeBSD.org>
+.\" Copyright (c) 2023 Future Crew LLC.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.Dd July 15, 2025
+.Dt RTLBTFW 8
+.Os
+.Sh NAME
+.Nm rtlbtfw
+.Nd load firmware for Realtek 87XX/88XX Bluetooth USB devices
+.Sh SYNOPSIS
+.Nm
+.Fl d Ar device_name
+.Fl f Ar firmware_path
+.Nm
+.Fl h
+.Sh DESCRIPTION
+The
+.Nm
+utility downloads the specified firmware file to the specified
+.Xr ugen 4
+device.
+.Pp
+This utility will
+.Em only
+work with Realtek 87XX/88XX chip based Bluetooth USB devices and some of
+their successors.
+The identification is currently based on USB vendor ID/product ID pair and
+interface class.
+For Realtek devices the vendor ID should be 0x0bda
+.Pq Dv USB_VENDOR_REALTEK
+and the 0-th interface class/subclass/protocol should be a Bluetooth RF
+Wireless Controller.
+Non-Realtek devices are identified based on USB vendor ID/product ID pair.
+.Pp
+Firmware files are available in the
+.Pa comms/rtlbt-firmware
+port.
+.Pp
+The
+.Nm
+utility will query the device to determine which firmware image and board
+configuration to load in at runtime.
+.Pp
+The options are as follows:
+.Bl -tag -width indent
+.It Fl D
+Enable verbose debugging.
+.It Fl d Ar device_name
+Specify
+.Xr ugen 4
+device name.
+.It Fl I
+Enable informational debugging.
+.It Fl f Ar firmware_path
+Specify the directory containing the firmware files to search and upload.
+.It Fl h
+Display usage message and exit.
+.El
+.Sh EXIT STATUS
+.Ex -std
+.Sh SEE ALSO
+.Xr libusb 3 ,
+.Xr ng_ubt 4 ,
+.Xr ugen 4 ,
+.Xr devd 8
+.Sh AUTHORS
+.Nm
+is based on
+.Xr ath3kfw 8
+utility used as firmware downloader template and on Linux btrtl driver
+source code.
+It is written by
+.An Vladimir Kondratyev Aq Mt wulf@FreeBSD.org
+under sponsorship from Future Crew LLC.
+.Sh BUGS
+Most likely.
+Please report if found.
diff --git a/usr.sbin/bluetooth/rtlbtfw/rtlbtfw.conf b/usr.sbin/bluetooth/rtlbtfw/rtlbtfw.conf
new file mode 100644
index 000000000000..2ef56d2af93a
--- /dev/null
+++ b/usr.sbin/bluetooth/rtlbtfw/rtlbtfw.conf
@@ -0,0 +1,433 @@
+#
+# Download Realtek 87XX/88XX bluetooth adaptor firmware
+#
+
+# Generic Realtek vendor Bluetooth devices
+notify 100 {
+ match "system" "USB";
+ match "subsystem" "INTERFACE";
+ match "type" "ATTACH";
+ match "vendor" "0x0bda";
+ # only interface 0 is supported by rtlbtfw
+ match "interface" "0";
+ match "intclass" "0xe0";
+ match "intsubclass" "0x01";
+ match "intprotocol" "0x01";
+ action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware";
+};
+
+# Realtek 8821CE Bluetooth devices
+notify 100 {
+ match "system" "USB";
+ match "subsystem" "DEVICE";
+ match "type" "ATTACH";
+ match "vendor" "0x13d3";
+ match "product" "0x3529";
+ action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware";
+};
+
+# Realtek 8822CE Bluetooth devices
+notify 100 {
+ match "system" "USB";
+ match "subsystem" "DEVICE";
+ match "type" "ATTACH";
+ match "vendor" "0x0bda";
+ match "product" "(0xb00c|0xc822)";
+ action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware";
+};
+
+# Realtek 8851BE Bluetooth devices
+notify 100 {
+ match "system" "USB";
+ match "subsystem" "DEVICE";
+ match "type" "ATTACH";
+ match "vendor" "0x13d3";
+ match "product" "0x3600";
+ action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware";
+};
+
+# Realtek 8852AE Bluetooth devices
+notify 100 {
+ match "system" "USB";
+ match "subsystem" "DEVICE";
+ match "type" "ATTACH";
+ match "vendor" "0x0bda";
+ match "product" "(0x2852|0xc852|0x385a|0x4852)";
+ action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware";
+};
+notify 100 {
+ match "system" "USB";
+ match "subsystem" "DEVICE";
+ match "type" "ATTACH";
+ match "vendor" "0x04c5";
+ match "product" "0x165c";
+ action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware";
+};
+notify 100 {
+ match "system" "USB";
+ match "subsystem" "DEVICE";
+ match "type" "ATTACH";
+ match "vendor" "0x04ca";
+ match "product" "0x4006";
+ action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware";
+};
+notify 100 {
+ match "system" "USB";
+ match "subsystem" "DEVICE";
+ match "type" "ATTACH";
+ match "vendor" "0x0cb8";
+ match "product" "0xc549";
+ action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware";
+};
+
+# Realtek 8852CE Bluetooth devices
+notify 100 {
+ match "system" "USB";
+ match "subsystem" "DEVICE";
+ match "type" "ATTACH";
+ match "vendor" "0x04ca";
+ match "product" "0x4007";
+ action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware";
+};
+notify 100 {
+ match "system" "USB";
+ match "subsystem" "DEVICE";
+ match "type" "ATTACH";
+ match "vendor" "0x04c5";
+ match "product" "0x1675";
+ action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware";
+};
+notify 100 {
+ match "system" "USB";
+ match "subsystem" "DEVICE";
+ match "type" "ATTACH";
+ match "vendor" "0x0cb8";
+ match "product" "0xc558";
+ action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware";
+};
+notify 100 {
+ match "system" "USB";
+ match "subsystem" "DEVICE";
+ match "type" "ATTACH";
+ match "vendor" "0x13d3";
+ match "product" "(0x3587|0x3586|0x3592)";
+ action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware";
+};
+notify 100 {
+ match "system" "USB";
+ match "subsystem" "DEVICE";
+ match "type" "ATTACH";
+ match "vendor" "0x0489";
+ match "product" "0xe122";
+ action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware";
+};
+
+# Realtek 8852BE Bluetooth devices
+notify 100 {
+ match "system" "USB";
+ match "subsystem" "DEVICE";
+ match "type" "ATTACH";
+ match "vendor" "0x0cb8";
+ match "product" "0xc559";
+ action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware";
+};
+notify 100 {
+ match "system" "USB";
+ match "subsystem" "DEVICE";
+ match "type" "ATTACH";
+ match "vendor" "0x0bda";
+ match "product" "(0x4853|0x887b|0xb85b)";
+ action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware";
+};
+notify 100 {
+ match "system" "USB";
+ match "subsystem" "DEVICE";
+ match "type" "ATTACH";
+ match "vendor" "0x13d3";
+ match "product" "(0x3570|0x3571|0x3572|0x3591)";
+ action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware";
+};
+notify 100 {
+ match "system" "USB";
+ match "subsystem" "DEVICE";
+ match "type" "ATTACH";
+ match "vendor" "0x0489";
+ match "product" "(0xe123|0xe125)";
+ action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware";
+};
+
+# Realtek 8852BT/8852BE-VT Bluetooth devices
+notify 100 {
+ match "system" "USB";
+ match "subsystem" "DEVICE";
+ match "type" "ATTACH";
+ match "vendor" "0x0bda";
+ match "product" "0x8520";
+ action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware";
+};
+
+# Realtek 8922AE Bluetooth devices
+notify 100 {
+ match "system" "USB";
+ match "subsystem" "DEVICE";
+ match "type" "ATTACH";
+ match "vendor" "0x0bda";
+ match "product" "0x8922";
+ action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware";
+};
+notify 100 {
+ match "system" "USB";
+ match "subsystem" "DEVICE";
+ match "type" "ATTACH";
+ match "vendor" "0x13d3";
+ match "product" "(0x3617|0x3616)";
+ action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware";
+};
+notify 100 {
+ match "system" "USB";
+ match "subsystem" "DEVICE";
+ match "type" "ATTACH";
+ match "vendor" "0x0489";
+ match "product" "0xe130";
+ action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware";
+};
+
+# Realtek 8723AE Bluetooth devices
+notify 100 {
+ match "system" "USB";
+ match "subsystem" "DEVICE";
+ match "type" "ATTACH";
+ match "vendor" "0x0930";
+ match "product" "0x021d";
+ action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware";
+};
+notify 100 {
+ match "system" "USB";
+ match "subsystem" "DEVICE";
+ match "type" "ATTACH";
+ match "vendor" "0x13d3";
+ match "product" "0x3394";
+ action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware";
+};
+
+# Realtek 8723BE Bluetooth devices
+notify 100 {
+ match "system" "USB";
+ match "subsystem" "DEVICE";
+ match "type" "ATTACH";
+ match "vendor" "0x0489";
+ match "product" "(0xe085|0xe08b)";
+ action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware";
+};
+notify 100 {
+ match "system" "USB";
+ match "subsystem" "DEVICE";
+ match "type" "ATTACH";
+ match "vendor" "0x04f2";
+ match "product" "0xb49f";
+ action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware";
+};
+notify 100 {
+ match "system" "USB";
+ match "subsystem" "DEVICE";
+ match "type" "ATTACH";
+ match "vendor" "0x13d3";
+ match "product" "(0x3410|0x3416|0x3459|0x3494)";
+ action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware";
+};
+
+# Realtek 8723BU Bluetooth devices
+notify 100 {
+ match "system" "USB";
+ match "subsystem" "DEVICE";
+ match "type" "ATTACH";
+ match "vendor" "0x7392";
+ match "product" "0xa611";
+ action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware";
+};
+
+# Realtek 8723DE Bluetooth devices
+notify 100 {
+ match "system" "USB";
+ match "subsystem" "DEVICE";
+ match "type" "ATTACH";
+ match "vendor" "0x0bda";
+ match "product" "0xb009";
+ action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware";
+};
+notify 100 {
+ match "system" "USB";
+ match "subsystem" "DEVICE";
+ match "type" "ATTACH";
+ match "vendor" "0x2ff8";
+ match "product" "0xb011";
+ action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware";
+};
+
+# Realtek 8761BUV Bluetooth devices
+notify 100 {
+ match "system" "USB";
+ match "subsystem" "DEVICE";
+ match "type" "ATTACH";
+ match "vendor" "0x2c4e";
+ match "product" "0x0115";
+ action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware";
+};
+notify 100 {
+ match "system" "USB";
+ match "subsystem" "DEVICE";
+ match "type" "ATTACH";
+ match "vendor" "0x2357";
+ match "product" "0x0604";
+ action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware";
+};
+notify 100 {
+ match "system" "USB";
+ match "subsystem" "DEVICE";
+ match "type" "ATTACH";
+ match "vendor" "0x0b05";
+ match "product" "0x190e";
+ action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware";
+};
+notify 100 {
+ match "system" "USB";
+ match "subsystem" "DEVICE";
+ match "type" "ATTACH";
+ match "vendor" "0x2550";
+ match "product" "0x8761";
+ action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware";
+};
+notify 100 {
+ match "system" "USB";
+ match "subsystem" "DEVICE";
+ match "type" "ATTACH";
+ match "vendor" "0x0bda";
+ match "product" "0x8771";
+ action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware";
+};
+notify 100 {
+ match "system" "USB";
+ match "subsystem" "DEVICE";
+ match "type" "ATTACH";
+ match "vendor" "0x6655";
+ match "product" "0x8771";
+ action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware";
+};
+notify 100 {
+ match "system" "USB";
+ match "subsystem" "DEVICE";
+ match "type" "ATTACH";
+ match "vendor" "0x7392";
+ match "product" "0xc611";
+ action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware";
+};
+notify 100 {
+ match "system" "USB";
+ match "subsystem" "DEVICE";
+ match "type" "ATTACH";
+ match "vendor" "0x2b89";
+ match "product" "0x8761";
+ action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware";
+};
+
+# Realtek 8821AE Bluetooth devices
+notify 100 {
+ match "system" "USB";
+ match "subsystem" "DEVICE";
+ match "type" "ATTACH";
+ match "vendor" "0x0b05";
+ match "product" "0x17dc";
+ action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware";
+};
+notify 100 {
+ match "system" "USB";
+ match "subsystem" "DEVICE";
+ match "type" "ATTACH";
+ match "vendor" "0x13d3";
+ match "product" "(0x3414|0x3458|0x3461|0x3462)";
+ action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware";
+};
+
+# Realtek 8822BE Bluetooth devices
+notify 100 {
+ match "system" "USB";
+ match "subsystem" "DEVICE";
+ match "type" "ATTACH";
+ match "vendor" "0x13d3";
+ match "product" "0x3526";
+ action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware";
+};
+notify 100 {
+ match "system" "USB";
+ match "subsystem" "DEVICE";
+ match "type" "ATTACH";
+ match "vendor" "0x0b05";
+ match "product" "0x185c";
+ action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware";
+};
+
+# Realtek 8822CE Bluetooth devices
+notify 100 {
+ match "system" "USB";
+ match "subsystem" "DEVICE";
+ match "type" "ATTACH";
+ match "vendor" "0x04ca";
+ match "product" "0x4005";
+ action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware";
+};
+notify 100 {
+ match "system" "USB";
+ match "subsystem" "DEVICE";
+ match "type" "ATTACH";
+ match "vendor" "0x04c5";
+ match "product" "0x161f";
+ action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware";
+};
+notify 100 {
+ match "system" "USB";
+ match "subsystem" "DEVICE";
+ match "type" "ATTACH";
+ match "vendor" "0x0b05";
+ match "product" "0x18ef";
+ action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware";
+};
+notify 100 {
+ match "system" "USB";
+ match "subsystem" "DEVICE";
+ match "type" "ATTACH";
+ match "vendor" "0x13d3";
+ match "product" "(0x3548|0x3549|0x3553|0x3555)";
+ action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware";
+};
+notify 100 {
+ match "system" "USB";
+ match "subsystem" "DEVICE";
+ match "type" "ATTACH";
+ match "vendor" "0x2ff8";
+ match "product" "0x3051";
+ action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware";
+};
+notify 100 {
+ match "system" "USB";
+ match "subsystem" "DEVICE";
+ match "type" "ATTACH";
+ match "vendor" "0x1358";
+ match "product" "0xc123";
+ action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware";
+};
+notify 100 {
+ match "system" "USB";
+ match "subsystem" "DEVICE";
+ match "type" "ATTACH";
+ match "vendor" "0x0bda";
+ match "product" "0xc123";
+ action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware";
+};
+notify 100 {
+ match "system" "USB";
+ match "subsystem" "DEVICE";
+ match "type" "ATTACH";
+ match "vendor" "0x0cb5";
+ match "product" "0xc547";
+ action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware";
+};