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 via MODULE_DEVICE_TABLE, matches any standard Bluetooth USB device by USB class. Used by udev to load the module.
  • quirks_table - internal secondary lookup in btusb_probe(). Maps specific VID:PID (Vendor ID:Product ID) pairs to driver flags such as BTUSB_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_id interface - the new_id sysfs interface allows adding custom USB IDs to a driver at runtime and could be automated via a udev rule. However, copying flags from an existing entry requires a reference in btusb_table, which contains no entries with BTUSB_REALTEK set. All Realtek entries live in quirks_table, which is not exposed via this interface. Not applicable here.

  • patch btusb.c - add the missing entry directly to quirks_table and build a patched out-of-tree module against the running kernel. Requires rebuilding on every kernel update. This is the approach used below.

Fix:#

  1. Download source and headers:

    Download btusb.c and its internal driver headers matching the running kernel. The kernel-devel package does not include these. A version mismatch causes symbol errors on insmod.

    $ 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"
      done
    
  2. Apply the patch:

    Save the following as ub600.patch in ~/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.c
    
  3. Build:

    ~/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
    
    $ make
    
  4. Load:

    $ sudo modprobe -r btusb
    $ sudo modprobe -a btrtl btintel btbcm btmtk
    $ sudo insmod ~/btusb-patched/btusb.ko
    
  5. Confirm:

    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 0xdfc6d922
    

    The RTL: loading... line confirms firmware is now being loaded. hci_ver=0a is the HCI version reported by the chip before firmware upload, which btrtl uses 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 firmwareWindows firmware
Sourcerealtek-firmware RPMUB600_V1_2.11.3032.3001 driver package
FormatRealtech v1RTBTCore v2
Size (on disk)44,484 bytes29,215 bytes
BT version exposed5.1 (hci_ver=0x0a)6.0 (hci_ver=0x0e)
Chip fw version0xdfc6d9220x2afb4be5

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