git: 5036d9652a57 - main - rtlbtfw: Firmware loader for Realtek 87XX/88XX bluetooth USB adaptors

From: Vladimir Kondratyev <wulf_at_FreeBSD.org>
Date: Wed, 06 Nov 2024 23:31:57 UTC
The branch main has been updated by wulf:

URL: https://cgit.FreeBSD.org/src/commit/?id=5036d9652a5701d00e9e40ea942c278e9f77d33d

commit 5036d9652a5701d00e9e40ea942c278e9f77d33d
Author:     Vladimir Kondratyev <wulf@FreeBSD.org>
AuthorDate: 2024-11-06 23:28:49 +0000
Commit:     Vladimir Kondratyev <wulf@FreeBSD.org>
CommitDate: 2024-11-06 23:30:29 +0000

    rtlbtfw: Firmware loader for Realtek 87XX/88XX bluetooth USB adaptors
    
    Firmware files are available in the comms/rtlbt-firmware port.
    
    Sponsored by:   Future Crew LLC
    MFC after:      1 month
    Differential Revision:  https://reviews.freebsd.org/D46739
---
 targets/pseudo/userland/Makefile.depend  |   1 +
 tools/build/mk/OptionalObsoleteFiles.inc |   3 +
 usr.sbin/bluetooth/Makefile              |   1 +
 usr.sbin/bluetooth/rtlbtfw/Makefile      |   9 +
 usr.sbin/bluetooth/rtlbtfw/main.c        | 525 +++++++++++++++++++++++++++++++
 usr.sbin/bluetooth/rtlbtfw/rtlbt_dbg.h   |  46 +++
 usr.sbin/bluetooth/rtlbtfw/rtlbt_fw.c    | 385 +++++++++++++++++++++++
 usr.sbin/bluetooth/rtlbtfw/rtlbt_fw.h    |  92 ++++++
 usr.sbin/bluetooth/rtlbtfw/rtlbt_hw.c    | 236 ++++++++++++++
 usr.sbin/bluetooth/rtlbtfw/rtlbt_hw.h    | 104 ++++++
 usr.sbin/bluetooth/rtlbtfw/rtlbtfw.8     | 100 ++++++
 usr.sbin/bluetooth/rtlbtfw/rtlbtfw.conf  | 373 ++++++++++++++++++++++
 12 files changed, 1875 insertions(+)

diff --git a/targets/pseudo/userland/Makefile.depend b/targets/pseudo/userland/Makefile.depend
index 6a844630c999..698db40042ef 100644
--- a/targets/pseudo/userland/Makefile.depend
+++ b/targets/pseudo/userland/Makefile.depend
@@ -430,6 +430,7 @@ DIRDEPS+= \
 	usr.sbin/bluetooth/l2control \
 	usr.sbin/bluetooth/l2ping \
 	usr.sbin/bluetooth/rfcomm_pppd \
+	usr.sbin/bluetooth/rtlbtfw \
 	usr.sbin/bluetooth/sdpcontrol \
 	usr.sbin/bluetooth/sdpd \
 	usr.sbin/bootparamd/bootparamd \
diff --git a/tools/build/mk/OptionalObsoleteFiles.inc b/tools/build/mk/OptionalObsoleteFiles.inc
index da2f0c15d11e..c03611c976a6 100644
--- a/tools/build/mk/OptionalObsoleteFiles.inc
+++ b/tools/build/mk/OptionalObsoleteFiles.inc
@@ -193,6 +193,7 @@ OLD_FILES+=etc/bluetooth/hosts
 OLD_FILES+=etc/bluetooth/protocols
 OLD_FILES+=etc/defaults/bluetooth.device.conf
 OLD_FILES+=etc/devd/iwmbtfw.conf
+OLD_FILES+=etc/devd/rtlbtfw.conf
 OLD_DIRS+=etc/bluetooth
 OLD_FILES+=etc/rc.d/bluetooth
 OLD_FILES+=etc/rc.d/bthidd
@@ -240,6 +241,7 @@ OLD_FILES+=usr/sbin/iwmbtfw
 OLD_FILES+=usr/sbin/l2control
 OLD_FILES+=usr/sbin/l2ping
 OLD_FILES+=usr/sbin/rfcomm_pppd
+OLD_FILES+=usr/sbin/rtlbtfw
 OLD_FILES+=usr/sbin/sdpcontrol
 OLD_FILES+=usr/sbin/sdpd
 OLD_FILES+=usr/share/examples/etc/defaults/bluetooth.device.conf
@@ -318,6 +320,7 @@ OLD_FILES+=usr/share/man/man8/iwmbtfw.8.gz
 OLD_FILES+=usr/share/man/man8/l2control.8.gz
 OLD_FILES+=usr/share/man/man8/l2ping.8.gz
 OLD_FILES+=usr/share/man/man8/rfcomm_pppd.8.gz
+OLD_FILES+=usr/share/man/man8/rtlbtfw.8.gz
 OLD_FILES+=usr/share/man/man8/sdpcontrol.8.gz
 OLD_FILES+=usr/share/man/man8/sdpd.8.gz
 .endif
diff --git a/usr.sbin/bluetooth/Makefile b/usr.sbin/bluetooth/Makefile
index fb660029b3d1..5afc12450194 100644
--- a/usr.sbin/bluetooth/Makefile
+++ b/usr.sbin/bluetooth/Makefile
@@ -19,6 +19,7 @@ SUBDIR+=	bcmfw
 SUBDIR+=	bthidcontrol
 SUBDIR+=	bthidd
 SUBDIR+=	iwmbtfw
+SUBDIR+=	rtlbtfw
 .endif
 
 .include <bsd.subdir.mk>
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..029c04f98b26
--- /dev/null
+++ b/usr.sbin/bluetooth/rtlbtfw/main.c
@@ -0,0 +1,525 @@
+/*-
+ * 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 8822CU Bluetooth devices */
+	{ .vendor_id = 0x13d3, .product_id = 0x3549 },
+
+	/* 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 },
+
+#ifdef RTLBTFW_SUPPORTS_FW_V2
+	/* 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 },
+#endif
+
+	/* Realtek 8852BE Bluetooth devices */
+	{ .vendor_id = 0x0cb8, .product_id = 0xc559 },
+	{ .vendor_id = 0x0bda, .product_id = 0x887b },
+	{ .vendor_id = 0x13d3, .product_id = 0x3571 },
+
+	/* 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 = 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;
+	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);
+
+	firmware_path = rtlbt_get_fwname(ic->fw_name, firmware_dir, "_fw.bin");
+	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 */
+		r = rtlbt_parse_fwfile_v1(&fw, rom_version);
+		if (r < 0) {
+			rtlbt_err("Parseing 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..bb3d20d79527
--- /dev/null
+++ b/usr.sbin/bluetooth/rtlbtfw/rtlbt_fw.c
@@ -0,0 +1,385 @@
+/*-
+ * 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",
+#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",
+	}, { /* 8851B */
+	    .lmp_subversion = RTLBT_ROM_LMP_8851B,
+	    .hci_revision = 0xb,
+	    .hci_version = 0xc,
+	    .flags = RTLBT_IC_FLAG_MSFT,
+	    .fw_name  = "rtl8851bu",
+#endif
+	},
+};
+
+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 */
+};
+
+/* 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
+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
+#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) {
+		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);
*** 1022 LINES SKIPPED ***