lunar lake bluetooth on linux
NEW: The firmware was released by Intel in linux-firmware revision fe16af92ca833cf1dcb1020fd58a5bf5513f8ff7 which is part of release 20241110. Bluetooth should now work out of box.
The Problem
I’ve upgraded to a new intel laptop, and of course I installed NixOS on it. Once I got a very new kernel running (6.12.0rc2), it was already a very good experience. Everything worked expect the fingerprint sensor, which I expected, and bluetooth.
Intel has integrated WiFi and bluetooth into the Lunar Lake platform, and they usually do a good job of keeping their drivers working well in the kernel. Especially considering that the WiFi side was working great, it was a little surprising that Bluetooth wasn’t ready.
The logs showed that there was a driver trying to control the Bluetooth controller, but it was just missing firmware to upload:
[ 3.396972] usbcore: registered new interface driver btusb
[ 3.398503] Bluetooth: hci0: Device revision is 0
[ 3.398511] Bluetooth: hci0: Secure boot is enabled
[ 3.398513] Bluetooth: hci0: OTP lock is disabled
[ 3.398515] Bluetooth: hci0: API lock is enabled
[ 3.398516] Bluetooth: hci0: Debug lock is disabled
[ 3.398517] Bluetooth: hci0: Minimum firmware build 1 week 10 2014
[ 3.398520] Bluetooth: hci0: Bootloader timestamp 2023.33 buildtype 1 build 45995
[ 3.398845] Bluetooth: hci0: DSM reset method type: 0x00
[ 3.399030] Bluetooth: hci0: Failed to load Intel firmware file intel/ibt-0190-0291-iml.sfi (-2)
Off to look for ibt-0190-0291-iml.sfi
then!
Unfortunately, it wasn’t present in linux-firmware
like it should be, and the reason was pretty frustrating:
Include Bluetooth FWs
ibt-0190-0291-iml.sfi
,ibt-0190-0291.ddc
,ibt-0190-0291.sfi
in/lib/firmware/intel/
(These FWs are not public yet. We’re going to do SRU in the future once the FWs are landed on the upstream linux-firmrware)
- https://bugs.launchpad.net/ubuntu/+source/linux-oem-6.11/+bug/2076881
So Intel was letting canonical employees have this firmware, but not me?
Extraction
Well, the windows driver for this Bluetooth controller
worked fine, so the firmware must be stored somewhere in
it as well. I downloaded the latest driver installer from
Intel’s website and extracted it in a clean wine prefix,
which left me with a bunch of individual drivers in
Program Files (x86)
.
It’s easy enough to figure out which one to use thanks
to the driver inf
files, which held the usb vendor
and product IDs assigned to each one in plain text.
I found 8087:0037
in FMP
, and so I got to work
on ibtusb.sys
.
Binwalk didn’t show anything nice at first glance,
so I decided to look at what a known-good firmware
file looked like. I picked ibt-0180-0041.sfi
from linux-firmware
because it had the closest numbers,
and binwalk found a copyright string, SHA256 constant,
and AES S-Box.
These didn’t seem useful alone, except that in the driver file, binwalk found these three signatures in the same order, many times over. Looks like I was right about it holding the firmware, but unfortunately it has a lot and I have to find the right one.
Looking at the start and end of a few other firmware files, I found a common signature for the start and end, and went from there. I found 38 regions in the windows driver that matched these start/end sigs, and wrote a very messy tool to extract them.
Deciding
Now, I had 38 sfi files and no clue which one to use.
I tried reading through the btintel.c
code to
see if the firmware contents had any clues.
I learned some important info,
like that the card has 3 stages
and that I had to supply the last two.
The card has an onboard bootloader,
which boots an immediate loader (iml
) which
the driver provides, which then boots
the full firmware again from the driver.
However, I didn’t spot a way of identifying
the different firmware files.
Well, the windows driver obviously had to pick the right one somehow, so into IDA it went.
Thankfully, Intel decided to make my job easy: each region had a xref into some sort of table/dict, which then matched it to a string identifying what the firmware was for, using codenames.
I could narrow down the list pretty easily,
as the first entries of the list were all
bseq
images, while I needed sfi
images.
Of those series of images, only two had iml
variants: sfi_BLAZARI_A0_IML
and BLAZARI_B0_IML
.
Trial and Error
I tried each in my local firmware folder
as ibt-0190-0291-iml.sfi
, and happily got
a successful boot from the bluetooth controller.
However, it complained that ibt-0090-0291-usb.sfi
or
ibt-0190-0291-usb.sfi
was missing.
This wasn’t unexpected as the canonical reporters
had mentioned 3 unreleased files, although their
second firmware file didn’t have the usb
suffix,
which gave me some uncertainty towards these iml
firmwares.
Since the A0
iml
firmware
resulted in an invalid opcode error and
wanted the usb
firmware with a stranger
filename, I decided to work with the B0
firmwares only. I first tried BLAZARI_B0_USB
as ibt-0190-0291-usb.sfi
, which just hung
the controller during its second boot.
This was a little discouraging, as BLAZARI_B0_USB
seemed like the closest match to BLAZARI_B0_IML
.
The next closest firmware was BLAZARI_B0_FMP_C0_USB
,
so I loaded it in and rebooted.
This time, the driver complained that it was
missing ibt-0190-0291-usb.ddc
, but I remembered
from btintel.c
that these files were optional
as they just set a few extra configuration options
which the card had defaults for.
I opened Plasma’s Bluetooth panel, and was a little surprised to see that it worked normally; devices paired properly, and then I could play music or send files just fine.
And now I have a fully working laptop, it seems.
Do It Yourself
Download the Windows driver version 23.80.0
(sha256: e993a7dd88d868e8f8231618617c397cb31bf874bb5753a09fed5a6dffa5d0c4
)
and run the installer using wine.
Build the extractor tool: https://github.com/melvyn2/intel-bt-fw-imgextract.
Run it on drive_c/Program Files (x86)/Intel/Bluetooth/drivers/ibtusb/FMP/Win10_UWDRelease/x64/ibtusb.sys
.
Install image 17 as intel/ibt-0190-0291-iml.sfi
and
image 16 as intel/ibt-0190-0291-usb.sfi
into your
firmware folder.
If you use NixOS, this is easy to do as an overlay:
{ config, ... }:
{
nixpkgs.overlays = [
(final: prev: {
linux-firmware = prev.linux-firmware.overrideAttrs (old: {
postInstall = ''
cp ${./17.sfi} $out/lib/firmware/intel/ibt-0190-0291-iml.sfi
cp ${./16.sfi} $out/lib/firmware/intel/ibt-0190-0291-usb.sfi
'';
});
})
];
}
(place 17.sfi
and 16.sfi
in the same directory
as the nix file)