[SOLVED] Qualcomm QCNFA765 (ath11k/wcn6855) WiFi working on Thinkpad P14s Gen4 AMD

System Details

  • Lenovo ThinkPad P14s Gen4 AMD
  • Qualcomm QCNFA765 (wcn6855 hw2.1)
  • Qubes OS 4.3.0

Problem

The ath11k driver fails with PCI passthrough. The driver reads MSI configuration directly from hardware, but in sys-net those values are remapped, causing firmware crashes.

Last symptoms in dmesg in sys-net (after giving it enough RAM):

[    4.613162] ath11k_pci 0000:00:06.0: BAR 0 [mem 0xf2000000-0xf21fffff 64bit]: assigned
[    4.619723] ath11k_pci 0000:00:06.0: MSI vectors: 1
[    4.620195] ath11k_pci 0000:00:06.0: wcn6855 hw2.1
[    5.600245] ath11k_pci 0000:00:06.0: chip_id 0x12 chip_family 0xb board_id 0xff soc_id 0x400c1211
[    5.600658] ath11k_pci 0000:00:06.0: fw_version 0x11088c35 fw_build_timestamp 2024-04-17 08:34 fw_build_id WLAN.HSP.1.1-03125-QCAHSPSWPL_V1_V2_SILICONZ_LITE-3.6510.41
[    5.724905] ath11k_pci 0000:00:06.0: leaving PCI ASPM disabled to avoid MHI M2 problems
[    6.783575] ath11k_pci 0000:00:06.0: failed to receive control response completion, polling..
[    7.807559] ath11k_pci 0000:00:06.0: Service connect timeout
[    7.807595] ath11k_pci 0000:00:06.0: failed to connect to HTT: -110
[    7.807927] ath11k_pci 0000:00:06.0: failed to start core: -110
[    8.016769] ath11k_pci 0000:00:06.0: firmware crashed: MHI_CB_EE_RDDM
[    8.016834] ath11k_pci 0000:00:06.0: ignore reset dev flags 0x4000
[    8.124355] ath11k_pci 0000:00:06.0: firmware crashed: MHI_CB_EE_RDDM
[   18.112565] ath11k_pci 0000:00:06.0: failed to wait wlan mode request (mode 4): -110
[   18.112606] ath11k_pci 0000:00:06.0: qmi failed to send wlan mode off: -110

Solution

Applying an (unmerged) kernel patch to sys-net that adds module parameters to manually pass MSI values to the driver.

Please read the notes at the bottom for more information before following this.

1. Get MSI Values for the Network Card

In dom0:

sudo lspci -s 02:00.0 -vv | grep -A1 "MSI:"

Replace 02:00.0 with the ID of your network card shown in lspci | grep -i network. Note the values for ā€œAddressā€ and ā€œDataā€. Mine were fee0a000 and 4000.

2. Create a Template for sys-net

If you don’t already have a dedicated template, clone the fedora-42-xfce template (I called it fedora-net-firmware) and update it with dnf update. Power off the template, make it a HVM, and set the kernel to (provided by qube). If you run uname -r in the template, you should see something like 6.17.13-200.fc42.x86_64 instead of 6.12.59-1.qubes.fc41.x86_64 (may be newer for you).

3. Create a StandaloneVM for Building the Patch

Create a StandaloneVM based on the new template, make it an HVM and set the kernel to (provided by qube) (I called it fedora-net-build). Give it 4 CPUs and 4GB of RAM (or however much RAM/CPUs are available) and extra storage (64 GB is probably enough).

In the new StandaloneVM:

dnf install -y gcc make flex bison openssl openssl-devel elfutils-libelf-devel \
    perl ncurses-devel bc dwarves rsync git patch kernel-devel-$(uname -r)

4. Setup Build Environment

The 20GB cap on the root directory (which includes /usr/src/linux) will not be enough to build the kernel. To take advantage of the allocated storage, we create a symlink:

mkdir -p /rw/usr_src_linux
ln -s /rw/usr_src_linux /usr/src/linux

Now clone the kernel repository:

cd /usr/src
git clone --depth 1 --branch linux-6.17.y \
    https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
cd linux

Replace 6.17 with the series returned in uname -r.

Create ath11k-msi-workaround.patch:

--- a/drivers/net/wireless/ath/ath11k/pci.c
+++ b/drivers/net/wireless/ath/ath11k/pci.c
@@ -31,6 +31,14 @@

 #define TCSR_SOC_HW_SUB_VER	0x1910010

+static unsigned long host_msi_vector_addr = 0;
+module_param(host_msi_vector_addr, ulong, 0644);
+MODULE_PARM_DESC(host_msi_vector_addr, "Host MSI vector address for VM passthrough");
+
+static unsigned long host_msi_vector_data = 0;
+module_param(host_msi_vector_data, ulong, 0644);
+MODULE_PARM_DESC(host_msi_vector_data, "Host MSI vector data for VM passthrough");
+
 static const struct pci_device_id ath11k_pci_id_table[] = {
 	{ PCI_VDEVICE(QCOM, QCA6390_DEVICE_ID) },
 	{ PCI_VDEVICE(QCOM, WCN6855_DEVICE_ID) },
@@ -443,6 +451,18 @@ static int ath11k_pci_alloc_msi(struct ath11k_pci *ab_pci)

 	ath11k_pci_msi_disable(ab_pci);

+	if (host_msi_vector_addr && host_msi_vector_data) {
+		ab_pci->ab->pci.msi.ep_base_data = (u32)host_msi_vector_data;
+		ab->pci.msi.addr_hi = (u32)(host_msi_vector_addr >> 32);
+		ab->pci.msi.addr_lo = (u32)(host_msi_vector_addr & 0xffffffff);
+
+		ath11k_dbg(ab, ATH11K_DBG_PCI, "msi workaround: addr hi 0x%x lo 0x%x data %d\n",
+			   ab->pci.msi.addr_hi,
+			   ab->pci.msi.addr_lo,
+			   ab->pci.msi.ep_base_data);
+		return 0;
+	}
+
 	msi_desc = irq_get_msi_desc(ab_pci->pdev->irq);
 	if (!msi_desc) {
 		ath11k_err(ab, "msi_desc is NULL!\n");
@@ -482,6 +502,9 @@ static int ath11k_pci_config_msi_data(struct ath11k_pci *ab_pci)
 {
 	struct msi_desc *msi_desc;

+	if (host_msi_vector_addr && host_msi_vector_data)
+		return 0;
+
 	msi_desc = irq_get_msi_desc(ab_pci->pdev->irq);
 	if (!msi_desc) {
 		ath11k_err(ab_pci->ab, "msi_desc is NULL!\n");

This patch (originally tested on bhyve) was made by Jose Ignacio Tornos Martinez from Red Hat with the help of Baochen Qiang, an engineer at Qualcomm.

Try applying it:

patch -p1 --dry-run < ath11k-msi-workaround.patch

If that returns success, run it without --dry-run:

patch -p1 < ath11k-msi-workaround.patch

Prepare the source tree to build the kernel:

cp /boot/config-$(uname -r) .config
make olddefconfig

You can now build the kernel with the patch applied. This will take a while.

make -j$(nproc)

5. Build the Patched Modules

Now it’s time to build a new set of patched modules. You may notice that ath11k.ko and ath11k_pci.ko are already compiled, but these need to match your kernel version (from uname -r) exactly.

cd drivers/net/wireless/ath/ath11k
make -C /lib/modules/$(uname -r)/build M=$(pwd) modules

To check the new modules, run:

modinfo ath11k_pci.ko | grep -iE "vermagic|parm"

You should see your precise kernel version under vermagic (not ending in -dirty) and parameters for host_msi_vector_addr and host_msi_vector_data.

6. Install in Template

Copy your new modules to the template for sys-net:

qvm-copy ath11k.ko ath11k_pci.ko

Now to install the new modules:

cp /home/user/QubesIncoming/fedora-net-build/ath11k*.ko /lib/modules/$(uname -r)/updates/
depmod -a

And shut down the template once they are installed:

poweroff

7. Configure sys-net

Boot sys-net with this template. Unload the existing ath11k drivers:

rmmod ath11k_pci
rmmod ath11k

Re-load them with your new MSI parameters:

modprobe ath11k
modprobe ath11k_pci host_msi_vector_addr=0xfee0a000 host_msi_vector_data=0x4000

Replace fee0a000 and 4000 with the values you recorded earlier.

Your Qualcomm WiFi card should now load, and you should be able to see to nearby networks in the NetworkManager applet.

To make this happen every time, add these lines to the end of /rw/config/rc.local in sys-net (must not be disposable):

# Wait for automatic (failed) load to initialize device
sleep 10
# Reload with MSI workaround
rmmod ath11k_pci
rmmod ath11k
sleep 1
modprobe ath11k
modprobe ath11k_pci host_msi_vector_addr=0xfee0a000 host_msi_vector_data=0x4000

Replace fee0a000 and 4000 with the values you recorded earlier. If rc.local did not exist previously, set a #!/bin/sh shebang as the first line and make it executable with chmod +x /rw/config/rc.local.

Notes

  • You will probably need to re-build these modules every time your template’s kernel updates.

  • In my case, letting the driver fail once (as demonstrated in step 7) before reloading with parameters was necessary. Setting the values with a .conf file at /etc/modprobe.d/ did not work, and blacklisting the drivers before loading them fresh caused the initialization to fail.

  • This process was a result of ongoing troubleshooting, so some steps may be unnecessary. Specifically, I’m still not sure if compiling the whole kernel is necessary before building the modules (I had already tried compiling the modules with the upstream kernel before discovering I needed to match the build more precisely). Additionally, much of this report is coming from memory, so I may have missed some details.

  • Troubleshooting steps are available in the comments that may help.

If you have any suggestions to correct/improve this guide (especially if you have a better alternative to the hack on step 7) or report how it goes on your hardware on Qubes 4.3, I would be grateful.

3 Likes

This works! Thank you!

I can offer the following feedback on the process:

Step 2 - Builder Template - I had to allocate additional RAM for the HVM Template to be able to boot. I’m not sure why this is set as HVM - if my understanding is correct (very large if), this should be fine continuing to run in PVH mode, as it’s never going to actually have PCIe passthrough devices attached to it. (nope. That’s not how any of that works)

Step 3 - Builder Qube setup - Setting both disk storage values to 64GB should be fine. I found the VM getting stuck on qubes-rootfs-resize.service during boot and timing out on qrexec - I had to workaround this by running qvm-prefs [builder-qube-name] qrexec_timeout 1500 in dom0. I’m not certain why this is set for HVM either? The same logic as above says it should work fine in PVH mode.

Step 4 - Build setup and execution - The Standalone VM rootfs appears to be hard-limited to 20GB, which is inadequate - symlinking /usr/src/linux to /rw is required.

After applying the patch, but before calling make, you’ll need to run make menuconfig, select Exit, and select Yes to write out the kernel configuration file.

Is there any path towards integrating this fix into Qubes and automating it? It would be wonderful if it ā€œjust workedā€, especially if that meant I could keep sys-net as Disposable.

1 Like

Thank you, I remember I did skip something before calling make, though it was cp /boot/config-$(uname -r) .config; make olddefconfig that I ran originally. I’m not sure if there is a difference. I will update my post.

Regarding the HVM modes, once I set the template and build VMs to (provided by qube) for the kernel, I was unable to boot them in PVH mode until I changed it to HVM. Maybe it isn’t completely necessary but I couldn’t boot otherwise in my case.

There may be a way to run the script automatically on a disposable sys-net by modifying the template, although I’m not aware of any rc script file for VMs inherited from the template. The ultimate solution would be to actually use the patched drivers on startup in itnitramfs and applying the MSI values via a configuration file, but I could not figure out how to get that to work.

You might also be able to first make sys-net an AppVM, edit the /rw/config/rc.local and then make it a disposable template. That way you would have a dvm template that includes rc.local but future changes don’t survive reboot.

I do hope the devs find some way to integrate this fix, though it does rely on an unofficial kernel patch, so I’m not sure if that’s something they’d want to integrate into the default template.

I’m having trouble reproducing this again - specifically, I’m having a bitch of time getting the damned kernel to build. I know Step 3 is missing the dkms package requirement, but there’s still something else failing. I’m currently troubleshooting.

Seems the stumbling block is something called SBAT, which I’m guessing my initial run using the kernel config defaults (using make menuconfig) didn’t bother with.

dkms is not needed, if you disable the SBAT values in the config, which otherwise crash the build regardless. The least painful way to do this is to import the distro config as documented in the previous post, run the make olddefconfig, then run make menuconfig and drill down into Device Drivers → Firmware Drivers → EFI Support → Embedded SBAT section file path and erase that value. Then Exit all the way to the top of the tree, and Exit again and select Yes to save the new configuration.

I’ll second that statement! – how nice!! :smiley:

I did it the long way (a full kernel build!) … but watching as the WiFi started working was worth the wait! :slight_smile:

With the new 4.3 and a working WiFi it’s time to write an update to my old My adventures with Qubes 4.1.1 on a Lenovo T14 Gen 3 …

:slight_smile:

1 Like

I believe the changes to sys-net can be done in the template, by adding the following to step 6:

sudo -i
cd /boot/
mv initramfs-$(uname -r).img initramfs-$(uname -r).img-old
dracut --regenerate-all
echo 'GRUB_CMDLINE_LINUX="$GRUB_CMDLINE_LINUX ath11k_pci.host_msi_vector_addr=0xfee0a000 ath11k_pci.host_msi_vector_data=0x4000 "' > /etc/default/grub.qubes.ath11k
echo '. /etc/default/grub.qubes.ath11k' >> /etc/default/grub
grub2-mkconfig -o /boot/grub2/grub.cfg

:slight_smile:

Oh, I did miss something else that might have caused these issues. I initially had to run (just before make olddefconfig):

scripts/config --disable SYSTEM_TRUSTED_KEYS
scripts/config --disable SYSTEM_REVOCATION_KEYS

Maybe this contributed to what happened?

I’m very glad it helped you! If you have tested that change to the template, how did it go?

I’ve tried it and it seems to work … but I would like to do it all over again on a clean R4.3 install – I’ve made many attempts to get WiFi running on that machine … :-/

:slight_smile:

1 Like

You know about any solution for ath12k cards?

A thought regarding long-term management of this solution:

Might this be less burden to administer if we built a patched Qubes kernel package using the standard Qubes kernel build tool? Other than pulling that package into dom0 for installation, which is technically a violation of the security model, that would make all this other faffing about unneeded - We could just assign the patched kernel to sys-net and be done with it.

1 Like

I couldn’t find any patches for that driver, but if that card is failing for the same reason, it might be possible for someone to port the patch to ath12k.

That sounds like a good idea. Where might it be distributed?

I wouldn’t trust it to be distributed, unless the Qubes team wants to create and sign a distribution and distribute it within the Qubes updates framework.

What I’m hoping for is a sequence of instructions that is both simpler and faster than the existing workflow, and involves creating less ā€˜special’ qubes to make the solution work.

1 Like

Okay, I understand. If you do find an easier method, then feel free to edit my original post. I believe it works like a wiki entry.

It’s just an idea, but if someone did maintain just the driver files, which don’t have to be fully trusted, the entire process could be greatly simplified as well for those that don’t want to go DIY about it. It would be pretty easy to just put them on my GitHub each update.

WISHLIST - some kind of startup script that can live and run in dom0, before sys-net starts, which extracts the required values for host_msi_vector_addr and host_msi_vector_data from the current lspci output and executes qvm-prefs -s sys-net kernelopts "ath11k_pci.host_msi_vector_addr=0x[address] ath11k_pci.host_msi_vector_data=0x[data]"

I’ve found my machine sometimes varies the address slightly between power cycles.

Building and installing a patched kernel/modules to fix the Lenovo Qualcomm ath11k wifi driver crashing on startup

https://forum.qubes-os.org/t/38192

References
qubes-builderv2 readme
Kicksecure’s build notes
Verifying the Qubes Master Signing Key

These instructions have been tested and verified to work on a brand-new, completely uncustomized, up-to-date Qubes R4.3.0 installation on my ThinkPad P14s laptop.

Anything I put in [square brackets] requires you to think. That’s data values possibly specific to your machine, or your choice of [qube-names] if you don’t like mine. If you’re trying to copy/paste commandlines that have []s in them, you’re probably going to have a bad time.

**** Step 1 - Get MSI vector data for the wifi chip and prepare module options

in Dom0 terminal
  sudo lspci
    Locate bus address for Wifi interface - [probably 02:00.0]
  sudo lspci -s [bus_address] -vv | grep -A1 "MSI:"
    Record MSI address [probably 0xfee04000] and data [probably 0x4000]
  qvm-prefs -s sys-net kernelopts "ath11k_pci.host_msi_vector_addr=0x[address] ath11k_pci.host_msi_vector_data=0x[data]"

in Qube Manager
  Adjust settings for sys-net
    Increase Initial memory to at least 450MB - I recommend 600MB


**** Step 2 - Setting up the environment and host for qubes-builderv2

in Qube Manager
  Clone your fedora-42-xfce template as [sys-builder-template]
  Clone your default-dvm to [sys-builder-dvm]
  Adjust settings for [sys-builder-dvm]
    Basic tab
      Change the template to [sys-builder-template]
      Set private volume storage to 48GiB  <-- double check this. There's a UI quirk in Qube Manager that means it might not take
    Advanced tab
      Increase VCPUs as per your hardware - I set 6
  Create a new AppVM Qube, call it [sys-builder]
  Adjust settings for [sys-builder]
    On the Basic tab
      Set the Template to [sys-builder-template]
      Increase Private storage max size to 48GiB  <-- double check this.
    On the Advanced tab
      Set the Default disposable template to [sys-builder-dvm]

in [sys-builder] terminal
  git clone https://github.com/QubesOS/qubes-secpack.git
  gpg --import qubes-secpack/keys/*/*
  gpg --edit-key "Master Signing"
    fpr
      Verify the Primary key fingerprint reads exactly "427F 11FD 0FAA 4B08 0123  F01C DDFA 1A3E 3687 9494"
    trust
    5
    y
    q
  git clone https://github.com/QubesOS/qubes-builderv2/
  cd qubes-builderv2
  git tag -v $(git describe)
    Verify there are no errors and the last line of output starts with "gpg: Good signature from" and ends with "[full]"
  qvm-copy dependencies-fedora.txt dependencies-fedora-qubes-executor.txt
    Select [sys-builder-template] from the popup
  git submodule update --init
  sed -e 's/work-qubesos/[sys-builder]/g' -e 's/qubes-builder-dvm/[sys-builder-dvm]/g' rpc/policy/50-qubesbuilder.policy > /home/user/policy

in [sys-builder-template] terminal
  sudo dnf install $(cat QubesIncoming/[sys-builder]/dependencies-fedora.txt)
  sudo dnf install $(cat QubesIncoming/[sys-builder]/dependencies-fedora-qubes-executor.txt)

in Dom0 terminal
  sudo qvm-run --pass-io [sys-builder] -- 'cat /home/user/policy' > /etc/qubes/policy.d/50-qubesbuilder.policy

in [sys-builder-dvm] terminal
  sudo su
  mkdir -p /rw/bind-dirs/builder /rw/config/qubes-bind-dirs.d
  echo "binds+=('/builder')" > /rw/config/qubes-bind-dirs.d/builder.conf
  echo "mount /builder -o dev,suid,remount" >> /rw/config/rc.local

Shutdown [sys-builder-template], [sys-builder-dvm], [sys-builder]


**** Step 3 - Building prerequisites and kernel packages with qubes-builder

in [sys-builder] terminal
  cd qubes-builderv2
  cp example-configs/qubes-os-r4.3.yml builder.yml
  ./qb -c linux-utils package fetch prep build
  ./qb -c core-admin-linux package fetch prep build
  ./qb -c linux-kernel package fetch
  nano artifacts/sources/linux-kernel/kernel.spec.in
    After the line starting 'Patch63:', add a new line. Add a few newlines around here if you want to keep things readable. Feel free to add a comment if you like.
    Patch1000: 1000-ath11k-msi-workaround.patch
    <Ctrl-x>, y, <enter>
  nano artifacts/sources/linux-kernel/1000-ath11k-msi-workaround.patch
    Paste in the MSI patch
    <Ctrl-x>, y, <enter>
  echo [20251231] > artifacts/sources/linux-kernel/rel
    [feel free to use any arbitrary number]
  ./qb -c linux-kernel package prep
  ./qb -c linux-kernel package build
  ls artifacts/components/linux-kernel
    Record the exact name/version of the kernel you just built - mine was [6.12.63-20251231]


**** Step 4 - Pulling the binary packages into dom0 and installing them

in Dom0 terminal
  qvm-run --pass-io [sys-builder] -- 'cat /home/user/qubes-builderv2/artifacts/components/linux-kernel/[version]/host-fc41/build/rpm/kernel-devel-[version].qubes.fc41.x86_64.rpm' > kernel-devel-[version].qubes.fc41.x86_64.rpm
  qvm-run --pass-io [sys-builder] -- 'cat /home/user/qubes-builderv2/artifacts/components/linux-kernel/[version]/host-fc41/build/rpm/kernel-modules-[version].qubes.fc41.x86_64.rpm' > kernel-modules-[version].qubes.fc41.x86_64.rpm
  qvm-run --pass-io [sys-builder] -- 'cat /home/user/qubes-builderv2/artifacts/components/linux-kernel/[version]/host-fc41/build/rpm/kernel-qubes-vm-[version].qubes.fc41.x86_64.rpm' > kernel-qubes-vm-[version].qubes.fc41.x86_64.rpm
  sudo rpm -i ./kernel-devel-[version].qubes.fc41.x86_64.rpm
  sudo rpm -i ./kernel-modules-[version].qubes.fc41.x86_64.rpm
  sudo rpm -i ./kernel-qubes-vm-[version].qubes.fc41.x86_64.rpm
  qvm-prefs -s sys-net kernel [version].fc41

Restart sys-net - your Wifi should be working now!

You can delete [sys-builder], [sys-builder-dvm], and [sys-builder-template] if you are so inclined
You can also clear out the .rpm files out of dom0's home directory
2 Likes

Tested and working on:

Since the kernel is build with qubes-builderv2, is there then a chance the patch can be considered for inclusion in a future Qubes kernel?

@skyhawk: Thanks for the work – I did try to get Qubes Builder running 1-2 years ago (and failed!!) … so now I have both a working WiFi and can play around with Qubes Builder – what a great start to 2026!! :fireworks: :sparkler:

:slight_smile:

Github issue

1 Like

While the problem is real, the ā€œsolutionā€ is gross layering violation… VM shouldn’t mess with MSI/MSI-X values programmed in the hardware, it should stick to those visible inside VM. And those values definitely shouldn’t (need to) be provided by the user manually. Worse, they may change over time (for example across suspend/resume), which makes this patch unreliable in some cases. But then, the IMS (which I assume is what this is really about - see Add support for a new IMS interrupt mechanism [LWN.net] for some description) does require device-specific support from the hypervisor to work with PCI passthrough.

I’ll discuss what should be the proper solution (or at least less ugly workaround) with Xen folks…

4 Likes