How to include and use external firmware in nixos config (Bluetooth for QCA6390)

Context

I’m currently attempting to get Bluetooth working on my Dell XPS 13 9310.

The connectivity chip inside my particular device is the QCA6390, responsible for both wifi and bluetooth. While the wifi driver is still a work-in-progress, there are reports of Linux users having their bluetooth “just work” without any modifications.

E.g. the Arch wiki lists bluetooth working via btusb, and users have mentioned bluetooth working on Ubuntu without any extra configuration required. My aim is to get this working on NixOS and complete my nixos-hardware PR.

Kernel Config

One Ubuntu user kindly shared their dmesg output here, demonstrating loading of the btqca and bluetooth modules, neither of which I see when running lsmod locally. This lead me to suspect a difference in kernel configuration. After doing some digging into the Arch and Debian default kernel configs and comparing them, I added the following patch and extra modules:

          # Extra config required for Bluetooth.
          {
            name = "enable-qca6390-bluetooth";
            patch = null;
            extraConfig = ''
              BT_QCA m
              BT_HCIBTUSB m
              BT_HCIBTUSB_AUTOSUSPEND y
              BT_HCIUART m
              BT_HCIUART_QCA y
            '';
          }
        ];
        # Enable some extra kernel modules for QCA6390 bluetooth.
        kernelModules = [ "btqca" "btusb" "hci_qca" "hci_uart" ];

I can now see the Bluetooth modules being loaded in my dmesg:

[mindtree@mindtree:~]$ dmesg | grep -i bluetooth
[    3.599287] Bluetooth: Core ver 2.22
[    3.599301] Bluetooth: HCI device and connection manager initialized
[    3.599304] Bluetooth: HCI socket layer initialized
[    3.599306] Bluetooth: L2CAP socket layer initialized
[    3.599309] Bluetooth: SCO socket layer initialized
[    3.619715] Bluetooth: HCI UART driver ver 2.3
[    3.619717] Bluetooth: HCI UART protocol H4 registered
[    3.619717] Bluetooth: HCI UART protocol BCSP registered
[    3.619725] Bluetooth: HCI UART protocol LL registered
[    3.619729] Bluetooth: HCI UART protocol QCA registered
[   48.990643] Bluetooth: BNEP (Ethernet Emulation) ver 1.3
[   48.990649] Bluetooth: BNEP socket layer initialized

However, at this point I was still missing the firmware that was showing up in the other user’s log:

[ 2.416321] Bluetooth: hci0: setting up ROME/QCA6390
[ 2.420348] Bluetooth: hci0: Frame reassembly failed (-84)
...
[ 2.756645] Bluetooth: hci0: QCA Product ID :0x00000010
[ 2.756647] Bluetooth: hci0: QCA SOC Version :0x400a0200
[ 2.756647] Bluetooth: hci0: QCA ROM Version :0x00000200
[ 2.756648] Bluetooth: hci0: QCA Patch Version:0x00000d2b
[ 2.756650] Bluetooth: hci0: QCA controller version 0x02000200
[ 2.756651] Bluetooth: hci0: QCA Downloading qca/htbtfw20.tlv
[ 3.584055] Bluetooth: hci0: QCA Downloading qca/htnv20.bin
[ 3.777754] Bluetooth: hci0: QCA setup on UART is completed

The Firmware

I located the QCA6390 firmware in the linux-firmware repo here. Judging by the user’s output, it seems I specifically needed htbtfw20.tlv and htnv20.bin.

The problem is that I’m unsure exactly how to install this firmware. I’ve made an attempt to create a nix derivation for this firmware here, mostly based on copying how the wifi firmware is installed (see here), but for the most part this has been a wild guess.

Since including this in my nix configuration with the following, I haven’t noticed any difference in my dmesg output, and Bluetooth hasn’t magically started working.

  nixpkgs.overlays = [(final: previous: {
    qca6390-bt-firmware = final.callPackage ./qca6390-bt-firmware.nix {};
  })];

  hardware.firmware = lib.mkBefore [
    # Firmware for the AX500 bluetooth.
    pkgs.qca6390-bt-firmware
  ];

Questions

  1. Am I missing a step required in order to get this Bluetooth firmware to load?
  2. Is there a short-hand in nix for installing firmware from the kernel.org’s linux-firmware repository?

Any advice or ideas would be greatly appreciated!

Extra Info

I’m using the 5.10-rc4 version of the kernel in order to pull in some patches required to get wifi working. I’m using the unstable nixos channel for nixpkgs.

It looks like this user on an older version of Ubuntu was having a related error where their firmware failed to load.

However, my error is a little different in that it does not seem to attempt to load the firmware in the first place. Specifically, this step is never triggered on my system for some reason:

[   37.969110] Bluetooth: hci0: setting up ROME/QCA6390

It seems this firmwareLinuxNonfree package already includes all of the firmware from the same linux-firmware repository that I am cloning to install the bluetooth firmware from!

I would have thought that including this package in hardware.firmware = [ ... ] should do the trick, however doing so does not appear to make a difference :thinking: It looks like enableAllFirmware provides a superset, yet enabling this does not appear to make a difference either.

I’ve noticed a couple more kernel configuration options in the Arch kernel config that might help? I’m going to rebuild the kernel with these to see if they make a difference.

              BT_INTEL m
              BT_HCIUART_INTEL y
              BT_HCIUART_SERDEV y