Fixing RTL8761BU firmware loading on Linux for the TP-Link UB600
I picked up the TP-Link UB600
because my old desktop had no Bluetooth support. The adapter showed up in lsusb immediately but scanning found nothing. A few hours of reading btusb.c
later, the root cause was a single missing entry in an internal lookup table.
Problem:#
The TP-Link UB600 Bluetooth 6.0 USB adapter is detected by the kernel but doesn’t function properly.
Note: Tested on Fedora Silverblue
44, kernel 7.0.11-200.fc44.x86_64. TP-Link does not officially support Linux for this adapter.
lsusb confirms the device is present:
$ lsusb | grep -i tp-link
Bus 002 Device 027: ID 37ad:0600 TP-Link Bluetooth USB Adapter
Watching kernel messages on plug-in shows that no firmware is loaded:
$ sudo journalctl -b -k -o cat -f
...
usb 2-2: new full-speed USB device number 49 using xhci_hcd
usb 2-2: New USB device found, idVendor=37ad, idProduct=0600, bcdDevice= 2.00
usb 2-2: New USB device strings: Mfr=1, Product=2, SerialNumber=3
usb 2-2: Product: TP-Link Bluetooth USB Adapter
usb 2-2: Manufacturer:
usb 2-2: SerialNumber: ...
Bluetooth: MGMT ver 1.23
Scanning finds no devices:
$ bluetoothctl scan on
Discovery started
# no devices appear
Root cause:#
The chip used by this adapter is a Realtek RTL8761BU (BU = USB variant), identified via lmp_subver=0x8761 in the kernel log after applying the fix below. The chip operates in a limited ROM-only mode and requires a firmware blob to be uploaded from the host on every plug-in. Firmware upload is handled by the btrtl kernel module, but only when the btusb driver sets the BTUSB_REALTEK flag for the device.
btusb maintains two lookup tables in drivers/bluetooth/btusb.c
:
btusb_table- exported viaMODULE_DEVICE_TABLE, matches any standard Bluetooth USB device by USB class. Used byudevto load the module.quirks_table- internal secondary lookup inbtusb_probe(). Maps specificVID:PID(Vendor ID:Product ID) pairs to driver flags such asBTUSB_REALTEK.
The UB600 uses TP-Link’s vendor ID (0x37ad), which has no entry in quirks_table, so the initialisation path looks like this:
btusb_probe()
-> device matched via generic USB class entry (hci0 appears)
-> usb_match_id(intf, quirks_table) finds nothing for 0x37ad:0x0600
-> BTUSB_REALTEK (BIT(16)) never set
-> btrtl_setup_realtek() never called
-> chip runs on ROM firmware only
-> scan finds no devices
This also explains why lsusb and bluetoothctl operate at different layers: lsusb shows the USB descriptor VID set by TP-Link (0x37ad), while bluetoothctl show reports the HCI manufacturer code (Realtek, 0x005d). The chip exposes a Realtek manufacturer code at the HCI level, but btusb only checks the USB vendor ID:
$ bluetoothctl show
Controller XX:XX:XX:XX:XX:XX (public)
Manufacturer: 0x005d (93)
Version: 0x0a (10)
...
Note: Version: 0x0a (10) corresponds to Bluetooth 5.1 per the Bluetooth Assigned Numbers
specification, not the advertised 6.0. This is due to the firmware blob shipped in the realtek-firmware RPM, not the hardware:
$ rpm -q realtek-firmware
realtek-firmware-20260519-1.fc44.noarch
$ rpm -ql realtek-firmware | grep rtl8761bu
/usr/lib/firmware/rtl_bt/rtl8761bu_config.bin.xz
/usr/lib/firmware/rtl_bt/rtl8761bu_fw.bin.xz
See the Firmware upgrade section below.
The only clean permanent fix would be an upstream kernel patch. Two runtime options exist: one clean but not applicable here, one that works but requires maintaining a patched module.
sysfs
new_idinterface - thenew_idsysfs interface allows adding custom USB IDs to a driver at runtime and could be automated via audevrule. However, copying flags from an existing entry requires a reference inbtusb_table, which contains no entries withBTUSB_REALTEKset. All Realtek entries live inquirks_table, which is not exposed via this interface. Not applicable here.patch
btusb.c- add the missing entry directly toquirks_tableand build a patched out-of-tree module against the running kernel. Requires rebuilding on every kernel update. This is the approach used below.
Fix:#
Download source and headers:
Download
btusb.cand its internal driver headers matching the running kernel. Thekernel-develpackage does not include these. A version mismatch causes symbol errors oninsmod.$ mkdir ~/btusb-patched && cd ~/btusb-patched $ VER="v$(uname -r | cut -d'-' -f1)" $ BASE='https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/plain/drivers/bluetooth' $ for f in btusb.c btintel.h btbcm.h btrtl.h btmtk.h; do curl -fsSL -o $f "$BASE/$f?h=$VER" doneApply the patch:
Save the following as
ub600.patchin~/btusb-patched/:--- a/drivers/bluetooth/btusb.c +++ b/drivers/bluetooth/btusb.c @@ -601,6 +601,9 @@ /* Realtek 8922AE Bluetooth devices */ { USB_DEVICE(0x0bda, 0x8922), .driver_info = BTUSB_REALTEK | BTUSB_WIDEBAND_SPEECH }, + /* TP-Link UB600 (RTL8761BU under TP-Link VID) */ + { USB_DEVICE(0x37ad, 0x0600), .driver_info = BTUSB_REALTEK | + BTUSB_WIDEBAND_SPEECH }, { USB_DEVICE(0x13d3, 0x3617), .driver_info = BTUSB_REALTEK | BTUSB_WIDEBAND_SPEECH }, { USB_DEVICE(0x13d3, 0x3616), .driver_info = BTUSB_REALTEK |$ patch btusb.c ub600.patch patching file btusb.cBuild:
~/btusb-patched/Makefile:obj-m := btusb.o KDIR := /lib/modules/$(shell uname -r)/build all: $(MAKE) -C $(KDIR) M=$(PWD) modules clean: $(MAKE) -C $(KDIR) M=$(PWD) clean$ makeLoad:
$ sudo modprobe -r btusb $ sudo modprobe -a btrtl btintel btbcm btmtk $ sudo insmod ~/btusb-patched/btusb.koConfirm:
Replug the adapter. The kernel log should now show the firmware loading line that was absent before:
$ sudo journalctl -b -k -o cat -f ... Bluetooth: hci0: RTL: examining hci_ver=0a hci_rev=000b lmp_ver=0a lmp_subver=8761 Bluetooth: hci0: RTL: rom_version status=0 version=1 Bluetooth: hci0: RTL: btrtl_initialize: key id 0 Bluetooth: hci0: RTL: loading rtl_bt/rtl8761bu_fw.bin Bluetooth: hci0: RTL: loading rtl_bt/rtl8761bu_config.bin Bluetooth: hci0: RTL: cfg_sz 6, total sz 30210 Bluetooth: hci0: RTL: fw version 0xdfc6d922The
RTL: loading...line confirms firmware is now being loaded.hci_ver=0ais the HCI version reported by the chip before firmware upload, whichbtrtluses for identification only. The adapter scans and connects.This workaround requires rebuilding and reloading the module after every kernel update.
Firmware upgrade (Bluetooth 6.0):#
At the time of writing, the realtek-firmware RPM ships rtl8761bu_fw.bin
, which only enables Bluetooth 5.1. The Windows driver package available from the TP-Link download page
includes a different blob that enables the chip’s full Bluetooth 6.0 capability.
| Linux firmware | Windows firmware | |
|---|---|---|
| Source | realtek-firmware RPM | UB600_V1_2.11.3032.3001 driver package |
| Format | Realtech v1 | RTBTCore v2 |
| Size (on disk) | 44,484 bytes | 29,215 bytes |
| BT version exposed | 5.1 (hci_ver=0x0a) | 6.0 (hci_ver=0x0e) |
| Chip fw version | 0xdfc6d922 | 0x2afb4be5 |
Note: The firmware blob is extracted from the Windows driver package and used here for testing purposes only. Check the license terms before redistributing.
btrtl supports both formats. Since /usr/lib/firmware/ is read-only on Fedora Silverblue, use the kernel’s firmware search path override
instead:
$ sudo mkdir -p /var/lib/firmware/rtl_bt
# xz-compress with CRC32 - the kernel's XZ decoder requires CRC32, not the default CRC64
$ xz --check=crc32 -k -c rtl8761b_mp_chip_bt40_fw_asic_rom_patch_new \
> /var/lib/firmware/rtl_bt/rtl8761bu_fw.bin.xz
$ echo -n /var/lib/firmware | sudo tee /sys/module/firmware_class/parameters/path
Unplug and replug the adapter. The kernel log should now show the new firmware version:
$ sudo journalctl -b -k -o cat -f
...
Bluetooth: hci0: RTL: examining hci_ver=0a hci_rev=000b lmp_ver=0a lmp_subver=8761
Bluetooth: hci0: RTL: rom_version status=0 version=1
Bluetooth: hci0: RTL: btrtl_initialize: key id 0
Bluetooth: hci0: RTL: loading rtl_bt/rtl8761bu_fw.bin
Bluetooth: hci0: RTL: loading rtl_bt/rtl8761bu_config.bin
Bluetooth: hci0: RTL: cfg_sz 6, total sz 29162
Bluetooth: hci0: RTL: fw version 0x2afb4be5
The version 14 field in btmgmt output confirms Bluetooth 6.0 is now active:
$ sudo btmgmt -i hci0 info | grep version
addr XX:XX:XX:XX:XX:XX version 14 manufacturer 93 class 0x7c0104