aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVladimir Kondratyev <wulf@FreeBSD.org>2024-11-06 23:28:49 +0000
committerVladimir Kondratyev <wulf@FreeBSD.org>2024-11-06 23:30:29 +0000
commit5036d9652a5701d00e9e40ea942c278e9f77d33d (patch)
treec94e3cf7a140561373701ab98d1cb7e49a04b63d
parent24ae172a50352ad4fd22989477f29ecca5aed6e3 (diff)
downloadsrc-5036d9652a57.tar.gz
src-5036d9652a57.zip
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
-rw-r--r--targets/pseudo/userland/Makefile.depend1
-rw-r--r--tools/build/mk/OptionalObsoleteFiles.inc3
-rw-r--r--usr.sbin/bluetooth/Makefile1
-rw-r--r--usr.sbin/bluetooth/rtlbtfw/Makefile9
-rw-r--r--usr.sbin/bluetooth/rtlbtfw/main.c525
-rw-r--r--usr.sbin/bluetooth/rtlbtfw/rtlbt_dbg.h46
-rw-r--r--usr.sbin/bluetooth/rtlbtfw/rtlbt_fw.c385
-rw-r--r--usr.sbin/bluetooth/rtlbtfw/rtlbt_fw.h92
-rw-r--r--usr.sbin/bluetooth/rtlbtfw/rtlbt_hw.c236
-rw-r--r--usr.sbin/bluetooth/rtlbtfw/rtlbt_hw.h104
-rw-r--r--usr.sbin/bluetooth/rtlbtfw/rtlbtfw.8100
-rw-r--r--usr.sbin/bluetooth/rtlbtfw/rtlbtfw.conf373
12 files changed, 1875 insertions, 0 deletions
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);
+ 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);
+}
+
+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..340abacba759
--- /dev/null
+++ b/usr.sbin/bluetooth/rtlbtfw/rtlbt_fw.h
@@ -0,0 +1,92 @@
+/*-
+ * 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 <stdbool.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
+
+enum rtlbt_fw_type {
+ RTLBT_FW_TYPE_UNKNOWN,
+ RTLBT_FW_TYPE_V1,
+#ifdef RTLBTFW_SUPPORTS_FW_V2
+ RTLBT_FW_TYPE_V2,
+#endif
+};
+
+struct rtlbt_id_table {
+ 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;
+};
+
+struct rtlbt_firmware {
+ char *fwname;
+ size_t len;
+ unsigned char *buf;
+};
+
+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));
+
+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_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..493358294c07
--- /dev/null
+++ b/usr.sbin/bluetooth/rtlbtfw/rtlbt_hw.c
@@ -0,0 +1,236 @@
+/*-
+ * 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_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;
+ int ret, transferred;
+
+ for (i = 0; i < frag_num; i++) {
+
+ rtlbt_debug("download fw (%d/%d)", i + 1, frag_num);
+
+ memset(cmd_buf, 0, sizeof(cmd_buf));
+ cmd->opcode = htole16(0xfc20);
+ if (i > 0x7f)
+ dl_cmd->index = (i & 0x7f) + 1;
+ else
+ dl_cmd->index = i;
+
+ 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..bc7c9fee3f57
--- /dev/null
+++ b/usr.sbin/bluetooth/rtlbtfw/rtlbt_hw.h
@@ -0,0 +1,104 @@
+/*-
+ * 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));
+
+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_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..c3c0b83d97e5
--- /dev/null
+++ b/usr.sbin/bluetooth/rtlbtfw/rtlbtfw.8
@@ -0,0 +1,100 @@
+.\" 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 19, 2023
+.Dt RTLBTFW 8
+.Os
+.Sh NAME
+.Nm rtlbtfw
+.Nd firmware download utility for Realtek 87XX/88XX chip based 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..d544913aaa12
--- /dev/null
+++ b/usr.sbin/bluetooth/rtlbtfw/rtlbtfw.conf
@@ -0,0 +1,373 @@
+#
+# 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 8822CU Bluetooth devices
+notify 100 {
+ match "system" "USB";
+ match "subsystem" "DEVICE";
+ match "type" "ATTACH";
+ match "vendor" "0x13d3";
+ match "product" "0x3549";
+ 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";
+};
+
+# 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" "0x887b";
+ 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" "0x3571";
+ 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" "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";
+};