git: 5036d9652a57 - main - rtlbtfw: Firmware loader for Realtek 87XX/88XX bluetooth USB adaptors
- Go to: [ bottom of page ] [ top of archives ] [ this month ]
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 ***