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

Hi there, did you manage to solve your problem?

Yes, these days there is a nixos-hardware module that is all you need to get going with the Dell XPS 9310 on NixOS.

1 Like

Unfortunately the nixos-hadrware doesn’t have my laptop model (asus ga403). I would like to fix the audio quality issue. And from the asus linux discord the solution is to first update the kernel to 6.9 and apply the following firmware:

1) git clone https://gitlab.com/kernel-firmware/linux-firmware.git
2) cd linux-firmware
3) make install DESTDIR=installdir
4) sudo cp -r installdir/lib/firmware/cirrus /lib/firmware
5) reboot

And the required files are from this commit a few weeks ago:

So the following command

  hardware.enableAllFirmware = true;
  hardware.firmware = [
    pkgs.firmwareLinuxNonfree
  ];

probably won’t have the firmware I’m looking for (I haven’t checked and I’m not sure how to check this…).
Is there any workarounds to achieve the same effect of copying all the cirrus firmware into /lib/firmware? It seems that nixos doesn’t have a /lib/ directory.

edit:
I found it: on the nixos-unstable, the firmware is still a month ago which doesn’t contain the stuff I wanted.

But with the master branch it contains the most up to date firmware which includes what I wanted. So I probably just need to update my flake.nix to nixpkgs.url = "nixpkgs/master";. I’ll try it later when I get home, but is there any concerns to swapping it to master? i.e. there may be alot of broken stuff?
2nd edit:
It also merged into release-23.11 and the newest kernel in this is 6.9! Which should fix the problem. I’ll try it later when I get home and update this.

3rd edit:
I managed to get everything working!!! The audio is working great just like under windows.
Additionally I checked my power consumption with the newest kernel 6.9 I am able to get around 6-7w at idel. Meaning around 12 hrs of battery life!