* [PATCH v2 0/7] Enable Qualcomm WiFi PCIe passthrough to VMs
@ 2026-10-11 7:45 Jose Ignacio Tornos Martinez
2026-10-11 7:45 ` [PATCH v2 1/7] PCI: Add pci_find_free_ext_cap_offset() helper Jose Ignacio Tornos Martinez
` (7 more replies)
0 siblings, 8 replies; 9+ messages in thread
From: Jose Ignacio Tornos Martinez @ 2026-10-11 7:45 UTC (permalink / raw)
To: jgg, alex, bhelgaas, jjohnson
Cc: johannes, mani, yishaih, skolothumtho, kevin.tian, linux-pci,
kvm, linux-wireless, ath11k, ath12k, linux-arm-msm, linux-kernel,
Jose Ignacio Tornos Martinez
This series enables Qualcomm ath11k/ath12k WiFi devices to work in VM
passthrough scenarios, addressing a long-standing issue where these
devices fail to initialize when passed through to VMs via VFIO.
Qualcomm ath11k/ath12k WiFi devices have been broken in VM passthrough
since they were introduced. The Qualcomm firmware has its own interrupt
controller with private registers that need to be programmed with MSI
addr/data. The driver extracts the addr/data from the MSI descriptor
and programs the firmware's private registers. In a VM, the driver
only sees virtual (IOVA) addr/data, but the firmware's private writes
bypass the IOMMU, so they need the physical host values. This causes
firmware initialization to fail with errors like "BHI offset:
0xffffffff is out of range".
Multiple attempts have been made to solve this over the past 2 years:
March 2024: Workaround using module parameters [1]
- Required manually copying MSI values from host to VM
- Rejected by Johannes Berg as too manual, suggested VMM solution
August 2024: QEMU + kernel solution by Alex Williamson [2]
- Kernel disables MSI virtualization, QEMU intercepts config writes
- Uses brute force pattern matching to infer MSI data writes
- Stuck as RFC - no progress in 8+ months
No action from Qualcomm: Despite multiple reports, no vendor solution
The solution in this series provides a clean, kernel-only approach that
addresses the previous concerns. The PCI reset support for these devices
has already landed in linux-next (commit f8522a518e59 "PCI: Add device-
specific reset for Qualcomm WCN6855/WCN7850 WLAN") [3], solving the device
reset path for VFIO passthrough. This MSI series is the remaining
piece, with both in place, these Qualcomm WiFi devices would be fully
operational in VMs for the first time.
In practice, this specific behavior (firmware requiring physical host
MSI addresses in private registers, bypassing the IOMMU) has only
been observed in Qualcomm ath11k/ath12k devices. A device-specific
approach seems appropriate, and the VFIO variant driver pattern fits
well for this, following the same established pattern as mlx5-vfio-pci
and nvgrace-gpu for their own device-specific needs.
Architecture:
1. qcom-vfio-pci variant driver caches physical host MSI values and
exposes them via extended config space with magic signature "QMSI".
Device-specific hardware quirks belong only in kernel drivers, not
userspace, independent of the tools used.
2. VM drivers (ath11k/ath12k) automatically discover and use cached values
3. PCIe link recovery handles VM timing variations
Thoroughly tested: 3 VMs across 2 WiFi generations, all devices
successfully initialized and are ready for use.
- VM1: ath11k (WCN6855)
- VM2: ath11k (WCN6855)
- VM3: ath12k (WCN7850)
Series structure:
Patch 1/7: PCI helper to find free space in extended config space
for device-specific data exchange between host and VM drivers
Patch 2/7: Shared header defining the MSI cache protocol between
the host variant driver and VM drivers
Patch 3/7: qcom-vfio-pci variant driver that caches host physical
MSI address/data and exposes them to VMs through extended config
space
Patch 4/7: ath11k PCIe link recovery retry to handle initialization
timing variations in VM environments
Patch 5/7: ath11k support to read cached host MSI values and
configure firmware with correct physical addresses, making the
device operational in VMs
Patch 6/7: ath12k PCIe link recovery retry to handle initialization
timing variations in VM environments
Patch 7/7: ath12k support to read cached host MSI values and
configure firmware with correct physical addresses, making the
device operational in VMs
[1] https://lore.kernel.org/all/20240322104912.94811-1-jtornosm@redhat.com/
[2] https://lore.kernel.org/kvm/20240812170014.1583783-1-alex.williamson@redhat.com/
[3] https://lore.kernel.org/all/20261005105436.83962-1-jtornosm@redhat.com/
[4] Original problem report:
https://lore.kernel.org/all/fc6bd06f-d52b-4dee-ab1b-4bb845cc0b95@quicinc.com/
---
v2: Complete cover letter with more information
Address Sashiko comments
v1: https://lore.kernel.org/all/20260930140833.576941-1-jtornosm@redhat.com/
Jose Ignacio Tornos Martinez (7):
PCI: Add pci_find_free_ext_cap_offset() helper
vfio: Add qcom_vfio.h header for MSI cache protocol
vfio/pci: Add qcom-vfio-pci variant driver
ath11k: Add PCIe link recovery retry for VMs
ath11k: Use VFIO MSI cache when available
ath12k: Add PCIe link recovery retry for VMs
ath12k: Use VFIO MSI cache when available
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH v2 1/7] PCI: Add pci_find_free_ext_cap_offset() helper
2026-10-11 7:45 [PATCH v2 0/7] Enable Qualcomm WiFi PCIe passthrough to VMs Jose Ignacio Tornos Martinez
@ 2026-10-11 7:45 ` Jose Ignacio Tornos Martinez
2026-10-11 7:45 ` [PATCH v2 2/7] vfio: Add qcom_vfio.h header for MSI cache protocol Jose Ignacio Tornos Martinez
` (6 subsequent siblings)
7 siblings, 0 replies; 9+ messages in thread
From: Jose Ignacio Tornos Martinez @ 2026-10-11 7:45 UTC (permalink / raw)
To: jgg, alex, bhelgaas, jjohnson
Cc: johannes, mani, yishaih, skolothumtho, kevin.tian, linux-pci,
kvm, linux-wireless, ath11k, ath12k, linux-arm-msm, linux-kernel,
Jose Ignacio Tornos Martinez
Add helper function to find free space in PCI extended configuration
space. This is needed by VFIO variant drivers that need to use unused
extended config space for device-specific purposes, such as passing
metadata to VMs.
The function walks the extended capability chain (with TTL to prevent
infinite loops on malformed chains), then scans for a contiguous region
of zeroed space of at least the requested size. This avoids hardcoding
capability sizes and correctly handles capabilities of any length.
Use case: The qcom-vfio-pci variant driver uses this to find space for
caching host MSI addresses that need to be passed to Qualcomm device
firmware in VMs.
Signed-off-by: Jose Ignacio Tornos Martinez <jtornosm@redhat.com>
---
v2: Address Sashiko comments:
- Add TTL to capability chain walk to prevent infinite loops on
malformed capability chains (matching pci_find_next_ext_capability)
- Replace hardcoded 64-byte offset with zero-scan approach to
correctly handle capabilities of any size
- Fix potential integer underflow in size comparison
- Add cfg_size and size parameter validation
v1: https://lore.kernel.org/all/20260930140833.576941-2-jtornosm@redhat.com/
drivers/pci/pci.c | 72 +++++++++++++++++++++++++++++++++++++++++++++++++++++
include/linux/pci.h | 3 +++
2 files changed, 75 insertions(+)
diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c
index 8f7cfcc00090..7fa261140457 100644
--- a/drivers/pci/pci.c
+++ b/drivers/pci/pci.c
@@ -552,6 +552,78 @@ u16 pci_find_ext_capability(struct pci_dev *dev, int cap)
}
EXPORT_SYMBOL_GPL(pci_find_ext_capability);
+/**
+ * pci_find_free_ext_cap_offset - Find free space in extended config
+ * @dev: PCI device to query
+ * @size: Minimum size needed (in bytes)
+ *
+ * Scans the extended capability chain and then searches for a contiguous
+ * region of zeroed space of at least @size bytes in the extended
+ * configuration space (0x100-0xFFF). This can be used by drivers that
+ * need to use unused extended config space for device-specific purposes
+ * (e.g., VFIO quirks for VM passthrough).
+ *
+ * Returns the offset to free space, or 0 if no suitable gap found.
+ */
+int pci_find_free_ext_cap_offset(struct pci_dev *dev, size_t size)
+{
+ int ttl = (PCI_CFG_SPACE_EXP_SIZE - PCI_CFG_SPACE_SIZE) / 8;
+ int pos = PCI_CFG_SPACE_SIZE;
+ int start = 0, run = 0;
+ int highest_pos = 0;
+ u32 header;
+ int offset;
+
+ if (!pci_is_pcie(dev) || dev->cfg_size <= PCI_CFG_SPACE_SIZE)
+ return 0;
+
+ if (size == 0 || size > PCI_CFG_SPACE_EXP_SIZE - PCI_CFG_SPACE_SIZE)
+ return 0;
+
+ if (pci_read_config_dword(dev, pos, &header))
+ return 0;
+ if (header == 0 || header == 0xffffffff)
+ return 0;
+
+ while (ttl-- > 0) {
+ if (pos > highest_pos)
+ highest_pos = pos;
+
+ pos = PCI_EXT_CAP_NEXT(header);
+ if (!pos || pos < PCI_CFG_SPACE_SIZE)
+ break;
+
+ if (pci_read_config_dword(dev, pos, &header))
+ break;
+ if (header == 0 || header == 0xffffffff)
+ break;
+ }
+
+ if (!highest_pos)
+ return 0;
+
+ for (offset = highest_pos; offset <= (int)PCI_CFG_SPACE_EXP_SIZE - 4;
+ offset += 4) {
+ u32 val;
+
+ if (pci_read_config_dword(dev, offset, &val))
+ break;
+
+ if (val == 0) {
+ if (run == 0)
+ start = offset;
+ run += 4;
+ if (run >= (int)size)
+ return start;
+ } else {
+ run = 0;
+ }
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(pci_find_free_ext_cap_offset);
+
/**
* pci_get_dsn - Read and return the 8-byte Device Serial Number
* @dev: PCI device to query
diff --git a/include/linux/pci.h b/include/linux/pci.h
index 2c4454583c11..5d1ccbb2ed29 100644
--- a/include/linux/pci.h
+++ b/include/linux/pci.h
@@ -1287,6 +1287,7 @@ u8 pci_find_ht_capability(struct pci_dev *dev, int ht_cap);
u8 pci_find_next_ht_capability(struct pci_dev *dev, u8 pos, int ht_cap);
u16 pci_find_ext_capability(struct pci_dev *dev, int cap);
u16 pci_find_next_ext_capability(struct pci_dev *dev, u16 pos, int cap);
+int pci_find_free_ext_cap_offset(struct pci_dev *dev, size_t size);
struct pci_bus *pci_find_next_bus(const struct pci_bus *from);
u16 pci_find_vsec_capability(struct pci_dev *dev, u16 vendor, int cap);
u16 pci_find_dvsec_capability(struct pci_dev *dev, u16 vendor, u16 dvsec);
@@ -2160,6 +2161,8 @@ static inline u8 pci_find_next_capability(struct pci_dev *dev, u8 post, int cap)
{ return 0; }
static inline u16 pci_find_ext_capability(struct pci_dev *dev, int cap)
{ return 0; }
+static inline int pci_find_free_ext_cap_offset(struct pci_dev *dev, size_t size)
+{ return 0; }
static inline u64 pci_get_dsn(struct pci_dev *dev)
{ return 0; }
--
2.53.0
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH v2 2/7] vfio: Add qcom_vfio.h header for MSI cache protocol
2026-10-11 7:45 [PATCH v2 0/7] Enable Qualcomm WiFi PCIe passthrough to VMs Jose Ignacio Tornos Martinez
2026-10-11 7:45 ` [PATCH v2 1/7] PCI: Add pci_find_free_ext_cap_offset() helper Jose Ignacio Tornos Martinez
@ 2026-10-11 7:45 ` Jose Ignacio Tornos Martinez
2026-10-11 7:45 ` [PATCH v2 3/7] vfio/pci: Add qcom-vfio-pci variant driver Jose Ignacio Tornos Martinez
` (5 subsequent siblings)
7 siblings, 0 replies; 9+ messages in thread
From: Jose Ignacio Tornos Martinez @ 2026-10-11 7:45 UTC (permalink / raw)
To: jgg, alex, bhelgaas, jjohnson
Cc: johannes, mani, yishaih, skolothumtho, kevin.tian, linux-pci,
kvm, linux-wireless, ath11k, ath12k, linux-arm-msm, linux-kernel,
Jose Ignacio Tornos Martinez
Add shared header defining the communication protocol between
qcom-vfio-pci variant driver and Qualcomm device drivers (ath11k,
ath12k) for passing physical host MSI addresses to VMs.
Qualcomm device firmware requires physical host MSI addresses rather
than guest IOVA addresses. The qcom-vfio-pci driver caches host MSI
values in extended PCI config space with a magic signature "QMSI" for
VM discovery.
Protocol:
1. Host: qcom-vfio-pci finds free space in extended config (0x100-0xFFF)
2. Host: Writes magic signature "QMSI" (0x49534D51) followed by MSI data
3. VM: Device driver searches for "QMSI" signature to locate cached MSI
4. VM: Reads MSI values and programs device firmware
This avoids offset mismatch when QEMU filters certain capabilities,
causing host and VM to see different extended capability chains.
Signed-off-by: Jose Ignacio Tornos Martinez <jtornosm@redhat.com>
---
v2: Address Sashiko comments:
- Fix search upper bound from 0xf00 to
PCI_CFG_SPACE_EXP_SIZE - QCOM_VFIO_MSI_SPACE_SIZE to cover
the full extended config space range
- Use PCI_CFG_SPACE_SIZE constant instead of hardcoded 0x100
- Add comment clarifying VM-side context for config space scan
v1: https://lore.kernel.org/all/20260930140833.576941-3-jtornosm@redhat.com/
include/linux/qcom_vfio.h | 90 +++++++++++++++++++++++++++++++++++++++
1 file changed, 90 insertions(+)
create mode 100644 include/linux/qcom_vfio.h
diff --git a/include/linux/qcom_vfio.h b/include/linux/qcom_vfio.h
new file mode 100644
index 000000000000..c59c81936e84
--- /dev/null
+++ b/include/linux/qcom_vfio.h
@@ -0,0 +1,90 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/* SPDX-FileCopyrightText: Copyright Red Hat */
+
+#ifndef _QCOM_VFIO_H_
+#define _QCOM_VFIO_H_
+
+#include <linux/pci.h>
+
+/*
+ * MSI Address Passthrough Quirk
+ *
+ * Qualcomm device firmware requires physical host MSI addresses rather than
+ * guest IOVA addresses. The qcom-vfio-pci variant driver caches host MSI
+ * values in extended PCI config space with a magic signature for VM
+ * discovery.
+ *
+ * Protocol:
+ * 1. Host: qcom-vfio-pci finds free space in extended config (0x100-0xFFF)
+ * 2. Host: Writes magic signature "QMSI" (0x49534D51) followed by MSI data
+ * 3. VM: Device driver searches for "QMSI" signature to locate cached MSI
+ * 4. VM: Reads MSI values and programs device firmware
+ *
+ * This avoids offset mismatch when QEMU filters certain capabilities, causing
+ * host and VM to see different extended capability chains.
+ *
+ * Layout in extended config space:
+ * +0x00: Magic signature "QMSI" (0x49534D51)
+ * +0x04: MSI address low 32 bits
+ * +0x08: MSI address high 32 bits
+ * +0x0C: MSI data (16 bits)
+ *
+ * Used by Qualcomm ath11k/ath12k WiFi devices. Supported PCI IDs
+ * are listed in the qcom-vfio-pci variant driver.
+ */
+
+/* Magic signature "QMSI" to identify cached MSI data */
+#define QCOM_VFIO_MSI_MAGIC 0x49534D51
+
+/* Space needed for magic + MSI passthrough data */
+#define QCOM_VFIO_MSI_SPACE_SIZE 16
+
+/* Relative offsets from base discovered by qcom_vfio_find_msi_offset() */
+#define QCOM_VFIO_MSI_MAGIC_OFFSET 0x00 /* Magic signature "QMSI" */
+#define QCOM_VFIO_MSI_ADDR_LO_OFFSET 0x04 /* Host MSI address low 32 bits */
+#define QCOM_VFIO_MSI_ADDR_HI_OFFSET 0x08 /* Host MSI address high 32 bits */
+#define QCOM_VFIO_MSI_DATA_OFFSET 0x0C /* Host MSI data (16 bits) */
+
+/**
+ * qcom_vfio_find_msi_offset - Find free space for host to cache MSI
+ * @pdev: PCI device
+ *
+ * Host-side function: Finds free space in extended config to write MSI cache.
+ * Uses PCI core utility to scan capability chain.
+ *
+ * Returns: Offset in extended config space (>= 0x100), or 0 if no space found
+ */
+static inline int qcom_vfio_find_msi_offset(struct pci_dev *pdev)
+{
+ return pci_find_free_ext_cap_offset(pdev, QCOM_VFIO_MSI_SPACE_SIZE);
+}
+
+/**
+ * qcom_vfio_find_msi_cache - Locate cached MSI data by magic signature
+ * @pdev: PCI device
+ *
+ * VM-side function: Searches extended config space for "QMSI" magic signature
+ * to locate host-cached MSI values. This avoids offset mismatch when QEMU
+ * filters certain capabilities. Config reads go through QEMU's VFIO
+ * virtualization, so no hardware side-effects or URs will occur.
+ *
+ * Returns: Offset of MSI data (after magic), or 0 if not found
+ */
+static inline int qcom_vfio_find_msi_cache(struct pci_dev *pdev)
+{
+ u32 magic;
+ int offset;
+
+ for (offset = PCI_CFG_SPACE_SIZE;
+ offset <= PCI_CFG_SPACE_EXP_SIZE - QCOM_VFIO_MSI_SPACE_SIZE;
+ offset += 4) {
+ if (pci_read_config_dword(pdev, offset, &magic))
+ continue;
+ if (magic == QCOM_VFIO_MSI_MAGIC)
+ return offset;
+ }
+
+ return 0;
+}
+
+#endif /* _QCOM_VFIO_H_ */
--
2.53.0
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH v2 3/7] vfio/pci: Add qcom-vfio-pci variant driver
2026-10-11 7:45 [PATCH v2 0/7] Enable Qualcomm WiFi PCIe passthrough to VMs Jose Ignacio Tornos Martinez
2026-10-11 7:45 ` [PATCH v2 1/7] PCI: Add pci_find_free_ext_cap_offset() helper Jose Ignacio Tornos Martinez
2026-10-11 7:45 ` [PATCH v2 2/7] vfio: Add qcom_vfio.h header for MSI cache protocol Jose Ignacio Tornos Martinez
@ 2026-10-11 7:45 ` Jose Ignacio Tornos Martinez
2026-10-11 7:45 ` [PATCH v2 4/7] ath11k: add PCIe link recovery retry Jose Ignacio Tornos Martinez
` (4 subsequent siblings)
7 siblings, 0 replies; 9+ messages in thread
From: Jose Ignacio Tornos Martinez @ 2026-10-11 7:45 UTC (permalink / raw)
To: jgg, alex, bhelgaas, jjohnson
Cc: johannes, mani, yishaih, skolothumtho, kevin.tian, linux-pci,
kvm, linux-wireless, ath11k, ath12k, linux-arm-msm, linux-kernel,
Jose Ignacio Tornos Martinez
Add VFIO variant driver for Qualcomm PCIe devices that require
MSI address passthrough for VM operation.
Qualcomm ath11k and ath12k WiFi devices have embedded interrupt
controllers that require physical host MSI addresses programmed to
device registers. In VMs, the driver only sees virtualized guest
addresses, causing firmware initialization to fail.
This variant driver:
1. Caches physical host MSI values after allocation
2. Writes them to extended config space with magic signature "QMSI"
3. VM drivers discover and use these values automatically
The driver follows the VFIO variant driver pattern used by mlx5-vfio-pci
and nvgrace-gpu, providing device-specific quirks within the kernel
without requiring QEMU modifications.
Note: the VM must have IOMMU interrupt remapping and caching mode
enabled so that MSI writes from the device using cached host physical
addresses are properly routed through the IOMMU interrupt remapping
table to the VM.
Signed-off-by: Jose Ignacio Tornos Martinez <jtornosm@redhat.com>
---
v2: Address Sashiko comments:
- Fix TOCTOU: pre-fetch SET_IRQS header before core ioctl to avoid
double-fetch race from re-reading userspace after ioctl completes
- Fix stale MSI cache on VM reboot: add close_device wrapper to reset
cached MSI state when device is closed, ensuring fresh MSI values
are captured on next VM attachment
v1: https://lore.kernel.org/all/20260930140833.576941-4-jtornosm@redhat.com/
drivers/vfio/pci/Kconfig | 2 +
drivers/vfio/pci/Makefile | 2 +
drivers/vfio/pci/qcom/Kconfig | 22 ++
drivers/vfio/pci/qcom/Makefile | 3 +
drivers/vfio/pci/qcom/main.c | 285 ++++++++++++++++++++++++++
drivers/vfio/pci/qcom/msi_quirk.c | 163 ++++++++++++++++
drivers/vfio/pci/qcom/qcom_vfio_pci.h | 36 ++++
7 files changed, 513 insertions(+)
create mode 100644 drivers/vfio/pci/qcom/Kconfig
create mode 100644 drivers/vfio/pci/qcom/Makefile
create mode 100644 drivers/vfio/pci/qcom/main.c
create mode 100644 drivers/vfio/pci/qcom/msi_quirk.c
create mode 100644 drivers/vfio/pci/qcom/qcom_vfio_pci.h
diff --git a/drivers/vfio/pci/Kconfig b/drivers/vfio/pci/Kconfig
index 296bf01e185e..237a29443bf5 100644
--- a/drivers/vfio/pci/Kconfig
+++ b/drivers/vfio/pci/Kconfig
@@ -72,6 +72,8 @@ source "drivers/vfio/pci/nvgrace-gpu/Kconfig"
source "drivers/vfio/pci/qat/Kconfig"
+source "drivers/vfio/pci/qcom/Kconfig"
+
source "drivers/vfio/pci/xe/Kconfig"
endmenu
diff --git a/drivers/vfio/pci/Makefile b/drivers/vfio/pci/Makefile
index 6138f1bf241d..63db19102850 100644
--- a/drivers/vfio/pci/Makefile
+++ b/drivers/vfio/pci/Makefile
@@ -23,4 +23,6 @@ obj-$(CONFIG_NVGRACE_GPU_VFIO_PCI) += nvgrace-gpu/
obj-$(CONFIG_QAT_VFIO_PCI) += qat/
+obj-$(CONFIG_QCOM_VFIO_PCI) += qcom/
+
obj-$(CONFIG_XE_VFIO_PCI) += xe/
diff --git a/drivers/vfio/pci/qcom/Kconfig b/drivers/vfio/pci/qcom/Kconfig
new file mode 100644
index 000000000000..8e4c170d01a0
--- /dev/null
+++ b/drivers/vfio/pci/qcom/Kconfig
@@ -0,0 +1,22 @@
+# SPDX-License-Identifier: GPL-2.0-only
+config QCOM_VFIO_PCI
+ tristate "VFIO support for Qualcomm PCI devices"
+ depends on PCI
+ select VFIO_PCI_CORE
+ help
+ This provides VFIO support for Qualcomm PCI devices with device-specific
+ quirks. Currently supports:
+ - Qualcomm ath11k WiFi (Device ID 0x1103)
+ - Qualcomm ath12k WiFi (Device ID 0x1107)
+
+ These devices require special handling for MSI address/data passthrough
+ when used with VFIO. The firmware expects physical host MSI addresses
+ rather than guest IOVA addresses.
+
+ The VM must have IOMMU interrupt remapping and caching mode enabled.
+ For example, with libvirt/QEMU on Intel, add to the domain XML:
+ <iommu model="intel">
+ <driver intremap="on" caching_mode="on"/>
+ </iommu>
+
+ If you don't know what to do here, say N.
diff --git a/drivers/vfio/pci/qcom/Makefile b/drivers/vfio/pci/qcom/Makefile
new file mode 100644
index 000000000000..dc48d87d1ef6
--- /dev/null
+++ b/drivers/vfio/pci/qcom/Makefile
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: GPL-2.0-only
+obj-$(CONFIG_QCOM_VFIO_PCI) += qcom-vfio-pci.o
+qcom-vfio-pci-y := main.o msi_quirk.o
diff --git a/drivers/vfio/pci/qcom/main.c b/drivers/vfio/pci/qcom/main.c
new file mode 100644
index 000000000000..0abfdd5faf80
--- /dev/null
+++ b/drivers/vfio/pci/qcom/main.c
@@ -0,0 +1,285 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// SPDX-FileCopyrightText: Copyright Red Hat
+
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/stddef.h>
+#include <linux/string.h>
+#include <linux/types.h>
+#include <linux/uaccess.h>
+#include <linux/vfio.h>
+#include <linux/vfio_pci_core.h>
+#include <uapi/linux/vfio.h>
+
+#include "qcom_vfio_pci.h"
+
+static struct qcom_vfio_pci_device *to_qcom_vdev(struct vfio_pci_core_device *vdev)
+{
+ return container_of(vdev, struct qcom_vfio_pci_device, vdev);
+}
+
+/*
+ * Intercept config space read operations to return cached host MSI values.
+ * Call core VFIO if not intercepted.
+ */
+static ssize_t qcom_vfio_pci_read(struct vfio_device *core_vdev, char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ struct vfio_pci_core_device *vdev =
+ container_of(core_vdev, struct vfio_pci_core_device, vdev);
+ struct qcom_vfio_pci_device *qdev = to_qcom_vdev(vdev);
+ unsigned int index = VFIO_PCI_OFFSET_TO_INDEX(*ppos);
+ loff_t offset = *ppos & VFIO_PCI_OFFSET_MASK;
+ ssize_t ret;
+
+ if (index == VFIO_PCI_CONFIG_REGION_INDEX) {
+ ret = qcom_vfio_pci_config_rw(qdev, buf, count, offset, false);
+ if (ret != -ENOTTY) {
+ if (ret > 0)
+ *ppos += ret;
+ return ret;
+ }
+ }
+
+ return vfio_pci_core_read(core_vdev, buf, count, ppos);
+}
+
+static int qcom_vfio_pci_init_dev(struct vfio_device *core_vdev)
+{
+ struct qcom_vfio_pci_device *qdev =
+ container_of(core_vdev, struct qcom_vfio_pci_device, vdev.vdev);
+ struct pci_dev *pdev = to_pci_dev(core_vdev->dev);
+ int ret;
+
+ ret = vfio_pci_core_init_dev(core_vdev);
+ if (ret)
+ return ret;
+
+ qdev->msi_offset = qcom_vfio_find_msi_offset(pdev);
+ if (!qdev->msi_offset) {
+ pci_err(pdev, "No free space in extended config for MSI quirk\n");
+ pci_err(pdev, "qcom-vfio-pci cannot function without MSI passthrough, failing probe\n");
+ vfio_pci_core_release_dev(core_vdev);
+ return -ENODEV;
+ }
+ pci_info(pdev, "MSI passthrough quirk using offset 0x%x\n", qdev->msi_offset);
+
+ qdev->msi_cached = false;
+ qdev->msi_addr_lo = 0;
+ qdev->msi_addr_hi = 0;
+ qdev->msi_data = 0;
+
+ return 0;
+}
+
+static const struct vfio_pci_device_ops qcom_vfio_pci_device_ops = {
+ /* No device-specific operations needed */
+};
+
+static int qcom_vfio_pci_open_device(struct vfio_device *core_vdev)
+{
+ struct qcom_vfio_pci_device *qdev =
+ container_of(core_vdev, struct qcom_vfio_pci_device, vdev.vdev);
+ struct vfio_pci_core_device *vdev = &qdev->vdev;
+ int ret;
+
+ ret = vfio_pci_core_enable(vdev);
+ if (ret)
+ return ret;
+
+ vfio_pci_core_finish_enable(vdev);
+ return 0;
+}
+
+static void qcom_vfio_pci_close_device(struct vfio_device *core_vdev)
+{
+ struct qcom_vfio_pci_device *qdev =
+ container_of(core_vdev, struct qcom_vfio_pci_device, vdev.vdev);
+
+ qdev->msi_cached = false;
+ qdev->msi_addr_lo = 0;
+ qdev->msi_addr_hi = 0;
+ qdev->msi_data = 0;
+
+ vfio_pci_core_close_device(core_vdev);
+}
+
+/*
+ * Wrap ioctl to intercept MSI allocation and cache host MSI values.
+ * This is called after QEMU allocates MSI via VFIO_DEVICE_SET_IRQS.
+ */
+static long qcom_vfio_pci_ioctl(struct vfio_device *core_vdev,
+ unsigned int cmd, unsigned long arg)
+{
+ struct vfio_pci_core_device *vdev =
+ container_of(core_vdev, struct vfio_pci_core_device, vdev);
+ struct qcom_vfio_pci_device *qdev = to_qcom_vdev(vdev);
+ bool is_msi_trigger = false;
+ struct vfio_irq_set hdr;
+ long ret;
+
+ /*
+ * Pre-fetch SET_IRQS header to avoid TOCTOU from
+ * re-reading userspace after the core ioctl completes.
+ */
+ if (cmd == VFIO_DEVICE_SET_IRQS) {
+ unsigned long minsz = offsetofend(struct vfio_irq_set, count);
+
+ if (!copy_from_user(&hdr, (void __user *)arg, minsz)) {
+ is_msi_trigger = (hdr.index == VFIO_PCI_MSI_IRQ_INDEX &&
+ (hdr.flags & VFIO_IRQ_SET_ACTION_TRIGGER));
+ if (is_msi_trigger)
+ pci_dbg(qdev->vdev.pdev,
+ "SET_IRQS: idx=%u flags=0x%x start=%u count=%u\n",
+ hdr.index, hdr.flags, hdr.start, hdr.count);
+ }
+ }
+
+ ret = vfio_pci_core_ioctl(core_vdev, cmd, arg);
+
+ if (ret == 0 && is_msi_trigger && !qdev->msi_cached)
+ qcom_cache_and_write_msi(qdev);
+
+ return ret;
+}
+
+static const struct vfio_device_ops qcom_vfio_pci_ops = {
+ .name = "qcom-vfio-pci",
+ .init = qcom_vfio_pci_init_dev,
+ .release = vfio_pci_core_release_dev,
+ .open_device = qcom_vfio_pci_open_device,
+ .close_device = qcom_vfio_pci_close_device,
+ .ioctl = qcom_vfio_pci_ioctl,
+ .get_region_info_caps = vfio_pci_ioctl_get_region_info,
+ .device_feature = vfio_pci_core_ioctl_feature,
+ .read = qcom_vfio_pci_read,
+ .write = vfio_pci_core_write,
+ .mmap = vfio_pci_core_mmap,
+ .request = vfio_pci_core_request,
+ .match = vfio_pci_core_match,
+ .match_token_uuid = vfio_pci_core_match_token_uuid,
+ .bind_iommufd = vfio_iommufd_physical_bind,
+ .unbind_iommufd = vfio_iommufd_physical_unbind,
+ .attach_ioas = vfio_iommufd_physical_attach_ioas,
+ .detach_ioas = vfio_iommufd_physical_detach_ioas,
+};
+
+static int qcom_vfio_pci_probe(struct pci_dev *pdev,
+ const struct pci_device_id *id)
+{
+ struct qcom_vfio_pci_device *qdev;
+ int ret;
+
+ qdev = vfio_alloc_device(qcom_vfio_pci_device, vdev.vdev,
+ &pdev->dev, &qcom_vfio_pci_ops);
+ if (IS_ERR(qdev))
+ return PTR_ERR(qdev);
+
+ dev_set_drvdata(&pdev->dev, &qdev->vdev);
+ qdev->vdev.pci_ops = &qcom_vfio_pci_device_ops;
+ ret = vfio_pci_core_register_device(&qdev->vdev);
+ if (ret)
+ goto out_put_vdev;
+
+ return 0;
+
+out_put_vdev:
+ vfio_put_device(&qdev->vdev.vdev);
+ return ret;
+}
+
+static void qcom_vfio_pci_remove(struct pci_dev *pdev)
+{
+ struct vfio_pci_core_device *vdev = dev_get_drvdata(&pdev->dev);
+ struct qcom_vfio_pci_device *qdev = to_qcom_vdev(vdev);
+
+ vfio_pci_core_unregister_device(&qdev->vdev);
+ vfio_put_device(&qdev->vdev.vdev);
+}
+
+static const struct pci_device_id qcom_vfio_pci_table[] = {
+ /* Qualcomm ath11k WiFi */
+ { PCI_DRIVER_OVERRIDE_DEVICE_VFIO(PCI_VENDOR_ID_QCOM, 0x1103) },
+ /* Qualcomm ath12k WiFi */
+ { PCI_DRIVER_OVERRIDE_DEVICE_VFIO(PCI_VENDOR_ID_QCOM, 0x1107) },
+ { }
+};
+
+MODULE_DEVICE_TABLE(pci, qcom_vfio_pci_table);
+
+static struct pci_driver qcom_vfio_pci_driver = {
+ .name = "qcom-vfio-pci",
+ .id_table = qcom_vfio_pci_table,
+ .probe = qcom_vfio_pci_probe,
+ .remove = qcom_vfio_pci_remove,
+ .driver_managed_dma = true,
+};
+
+static char ids[1024] __initdata;
+module_param_string(ids, ids, sizeof(ids), 0);
+MODULE_PARM_DESC(ids, "Initial PCI IDs to add to the driver, format is \"vendor:device[:subvendor[:subdevice[:class[:class_mask]]]]\" and multiple comma separated entries can be specified");
+
+static void __init qcom_vfio_pci_fill_ids(void)
+{
+ char *p, *id;
+ int rc;
+
+ if (ids[0] == '\0')
+ return;
+
+ p = ids;
+ while ((id = strsep(&p, ","))) {
+ unsigned int vendor, device, subvendor = PCI_ANY_ID,
+ subdevice = PCI_ANY_ID, class = 0, class_mask = 0;
+ int fields;
+
+ if (!strlen(id))
+ continue;
+
+ fields = sscanf(id, "%x:%x:%x:%x:%x:%x",
+ &vendor, &device, &subvendor, &subdevice,
+ &class, &class_mask);
+
+ if (fields < 2) {
+ pr_warn("qcom-vfio-pci: invalid id string \"%s\"\n", id);
+ continue;
+ }
+
+ rc = pci_add_dynid(&qcom_vfio_pci_driver, vendor, device,
+ subvendor, subdevice, class, class_mask, 0);
+ if (rc)
+ pr_warn("qcom-vfio-pci: failed to add dynamic id [%04x:%04x[%04x:%04x]] class %#08x/%08x (%d)\n",
+ vendor, device, subvendor, subdevice,
+ class, class_mask, rc);
+ else
+ pr_info("qcom-vfio-pci: add [%04x:%04x[%04x:%04x]] class %#08x/%08x\n",
+ vendor, device, subvendor, subdevice,
+ class, class_mask);
+ }
+}
+
+static int __init qcom_vfio_pci_init(void)
+{
+ int ret;
+
+ ret = pci_register_driver(&qcom_vfio_pci_driver);
+ if (ret)
+ return ret;
+
+ qcom_vfio_pci_fill_ids();
+
+ return 0;
+}
+
+static void __exit qcom_vfio_pci_exit(void)
+{
+ pci_unregister_driver(&qcom_vfio_pci_driver);
+}
+
+module_init(qcom_vfio_pci_init);
+module_exit(qcom_vfio_pci_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Jose Ignacio Tornos Martinez <jtornosm@redhat.com>");
+MODULE_DESCRIPTION("Qualcomm VFIO PCI - Device-Specific VFIO PCI driver for Qualcomm devices");
diff --git a/drivers/vfio/pci/qcom/msi_quirk.c b/drivers/vfio/pci/qcom/msi_quirk.c
new file mode 100644
index 000000000000..583673d699d5
--- /dev/null
+++ b/drivers/vfio/pci/qcom/msi_quirk.c
@@ -0,0 +1,163 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// SPDX-FileCopyrightText: Copyright Red Hat
+
+#include <linux/kernel.h>
+#include <linux/msi.h>
+#include <linux/pci.h>
+#include <linux/uaccess.h>
+#include <linux/vfio_pci_core.h>
+
+#include "qcom_vfio_pci.h"
+
+/**
+ * qcom_cache_and_write_msi - Cache host MSI address and data
+ * @qdev: Qualcomm VFIO device
+ *
+ * Called after QEMU allocates MSI via SET_IRQS ioctl to cache the physical
+ * host MSI addresses that will be returned to the VM driver.
+ */
+void qcom_cache_and_write_msi(struct qcom_vfio_pci_device *qdev)
+{
+ struct pci_dev *pdev = qdev->vdev.pdev;
+ struct device *dev = &pdev->dev;
+ struct msi_desc *desc;
+ u32 addr_lo, addr_hi;
+ u16 data;
+
+ if (qdev->msi_cached)
+ return;
+
+ if (!qdev->msi_offset)
+ return;
+
+ __msi_lock_descs(dev);
+ desc = msi_first_desc(dev, MSI_DESC_ASSOCIATED);
+ if (!desc) {
+ __msi_unlock_descs(dev);
+ pci_warn(pdev, "MSI descriptor not found\n");
+ return;
+ }
+
+ addr_lo = desc->msg.address_lo;
+ addr_hi = desc->msg.address_hi;
+ data = desc->msg.data;
+ __msi_unlock_descs(dev);
+
+ pci_dbg(pdev, "MSI descriptor - addr_lo=0x%x addr_hi=0x%x data=0x%x\n",
+ addr_lo, addr_hi, data);
+
+ /*
+ * Validate: address must be valid. Data can be 0 with interrupt remapping.
+ * When Intel VT-d interrupt remapping is enabled, the MSI data field
+ * contains an index into the interrupt remapping table, which can be 0.
+ * We cache it anyway since that's what the VM driver will program.
+ */
+ if (!addr_lo) {
+ pci_warn(pdev, "MSI address invalid, skipping cache\n");
+ return;
+ }
+ qdev->msi_addr_lo = addr_lo;
+ qdev->msi_addr_hi = addr_hi;
+ qdev->msi_data = data;
+ qdev->msi_cached = true;
+
+ pci_write_config_dword(pdev, qdev->msi_offset + QCOM_VFIO_MSI_MAGIC_OFFSET,
+ QCOM_VFIO_MSI_MAGIC);
+ pci_write_config_dword(pdev, qdev->msi_offset + QCOM_VFIO_MSI_ADDR_LO_OFFSET,
+ qdev->msi_addr_lo);
+ pci_write_config_dword(pdev, qdev->msi_offset + QCOM_VFIO_MSI_ADDR_HI_OFFSET,
+ qdev->msi_addr_hi);
+ pci_write_config_word(pdev, qdev->msi_offset + QCOM_VFIO_MSI_DATA_OFFSET,
+ qdev->msi_data);
+
+ pci_info(pdev, "Qualcomm WiFi MSI cached at offset 0x%x - addr_lo=0x%x addr_hi=0x%x data=0x%x (magic=QMSI)\n",
+ qdev->msi_offset, qdev->msi_addr_lo, qdev->msi_addr_hi, qdev->msi_data);
+}
+
+/**
+ * qcom_intercept_msi_read - Intercept MSI config reads and return cached values
+ * @qdev: Qualcomm VFIO device
+ * @pos: Config space offset
+ * @buf: User buffer
+ * @count: Read size
+ *
+ * Returns: Number of bytes read, or negative error
+ */
+static ssize_t qcom_intercept_msi_read(struct qcom_vfio_pci_device *qdev,
+ loff_t pos, char __user *buf, size_t count)
+{
+ u32 tmp_val = 0;
+ int msi_magic, msi_addr_lo, msi_addr_hi, msi_data;
+
+ if (!qdev->msi_offset)
+ return -ENOTTY;
+
+ msi_magic = qdev->msi_offset + QCOM_VFIO_MSI_MAGIC_OFFSET;
+ msi_addr_lo = qdev->msi_offset + QCOM_VFIO_MSI_ADDR_LO_OFFSET;
+ msi_addr_hi = qdev->msi_offset + QCOM_VFIO_MSI_ADDR_HI_OFFSET;
+ msi_data = qdev->msi_offset + QCOM_VFIO_MSI_DATA_OFFSET;
+
+ if (pos == msi_magic && count == sizeof(u32)) {
+ if (qdev->msi_cached) {
+ tmp_val = QCOM_VFIO_MSI_MAGIC;
+ pci_dbg(qdev->vdev.pdev, "Intercepting MAGIC read at 0x%llx, returning 0x%x (QMSI)\n",
+ pos, tmp_val);
+ } else {
+ return -ENOTTY;
+ }
+ } else if (qdev->msi_cached) {
+ if (pos == msi_addr_lo && count == sizeof(u32)) {
+ tmp_val = qdev->msi_addr_lo;
+ pci_dbg(qdev->vdev.pdev, "Intercepting ADDR_LO read at 0x%llx, returning 0x%x\n",
+ pos, tmp_val);
+ } else if (pos == msi_addr_hi && count == sizeof(u32)) {
+ tmp_val = qdev->msi_addr_hi;
+ pci_dbg(qdev->vdev.pdev, "Intercepting ADDR_HI read at 0x%llx, returning 0x%x\n",
+ pos, tmp_val);
+ } else if (pos == msi_data && count == sizeof(u16)) {
+ tmp_val = qdev->msi_data;
+ pci_dbg(qdev->vdev.pdev, "Intercepting MSI_DATA read at 0x%llx, returning 0x%x\n",
+ pos, tmp_val);
+ } else {
+ return -ENOTTY;
+ }
+ } else {
+ return -ENOTTY;
+ }
+
+ if (copy_to_user(buf, &tmp_val, count))
+ return -EFAULT;
+
+ return count;
+}
+
+/**
+ * qcom_vfio_pci_config_rw - Config space read/write with MSI interception
+ * @qdev: Qualcomm VFIO device
+ * @buf: User buffer
+ * @count: Size
+ * @offset: Config space offset (0-0xFFF)
+ * @iswrite: true for write, false for read
+ *
+ * Returns: Number of bytes transferred, or -ENOTTY if not intercepted
+ */
+ssize_t qcom_vfio_pci_config_rw(struct qcom_vfio_pci_device *qdev,
+ char __user *buf, size_t count,
+ loff_t offset, bool iswrite)
+{
+ int msi_range_start, msi_range_end;
+
+ if (iswrite)
+ return -ENOTTY;
+
+ if (!qdev->msi_offset)
+ return -ENOTTY;
+
+ msi_range_start = qdev->msi_offset;
+ msi_range_end = qdev->msi_offset + QCOM_VFIO_MSI_SPACE_SIZE;
+
+ if (offset >= msi_range_start && offset < msi_range_end)
+ return qcom_intercept_msi_read(qdev, offset, buf, count);
+
+ return -ENOTTY;
+}
diff --git a/drivers/vfio/pci/qcom/qcom_vfio_pci.h b/drivers/vfio/pci/qcom/qcom_vfio_pci.h
new file mode 100644
index 000000000000..de37fd94b74b
--- /dev/null
+++ b/drivers/vfio/pci/qcom/qcom_vfio_pci.h
@@ -0,0 +1,36 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/* SPDX-FileCopyrightText: Copyright Red Hat */
+
+#ifndef _QCOM_VFIO_PCI_H_
+#define _QCOM_VFIO_PCI_H_
+
+#include <linux/vfio_pci_core.h>
+#include <linux/qcom_vfio.h>
+
+/**
+ * struct qcom_vfio_pci_device - Qualcomm VFIO PCI device with quirks
+ * @vdev: Core VFIO PCI device
+ * @msi_offset: Base offset in extended config for MSI passthrough (0 if disabled)
+ * @msi_cached: Whether host MSI address/data has been cached
+ * @msi_addr_lo: Cached host MSI address (low 32 bits)
+ * @msi_addr_hi: Cached host MSI address (high 32 bits)
+ * @msi_data: Cached host MSI data value
+ */
+struct qcom_vfio_pci_device {
+ struct vfio_pci_core_device vdev;
+
+ /* MSI quirk state */
+ int msi_offset;
+ bool msi_cached;
+ u32 msi_addr_lo;
+ u32 msi_addr_hi;
+ u16 msi_data;
+};
+
+/* MSI quirk functions */
+ssize_t qcom_vfio_pci_config_rw(struct qcom_vfio_pci_device *qdev,
+ char __user *buf, size_t count,
+ loff_t offset, bool iswrite);
+void qcom_cache_and_write_msi(struct qcom_vfio_pci_device *qdev);
+
+#endif /* _QCOM_VFIO_PCI_H_ */
--
2.54.0
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH v2 4/7] ath11k: add PCIe link recovery retry
2026-10-11 7:45 [PATCH v2 0/7] Enable Qualcomm WiFi PCIe passthrough to VMs Jose Ignacio Tornos Martinez
` (2 preceding siblings ...)
2026-10-11 7:45 ` [PATCH v2 3/7] vfio/pci: Add qcom-vfio-pci variant driver Jose Ignacio Tornos Martinez
@ 2026-10-11 7:45 ` Jose Ignacio Tornos Martinez
2026-10-11 7:45 ` [PATCH v2 5/7] ath11k: Use VFIO MSI cache when available Jose Ignacio Tornos Martinez
` (3 subsequent siblings)
7 siblings, 0 replies; 9+ messages in thread
From: Jose Ignacio Tornos Martinez @ 2026-10-11 7:45 UTC (permalink / raw)
To: jgg, alex, bhelgaas, jjohnson
Cc: johannes, mani, yishaih, skolothumtho, kevin.tian, linux-pci,
kvm, linux-wireless, ath11k, ath12k, linux-arm-msm, linux-kernel,
Jose Ignacio Tornos Martinez
When ath11k devices are passed through to VMs via PCIe passthrough,
the PCIe link may not stabilize immediately after global reset due to
virtualization layer timing variations. This causes MHI initialization
to fail with "BHI offset: 0xffffffff is out of range" errors because
the device is read before it's ready.
Currently, ath11k_pci_soc_global_reset() just warns if the link is
down but continues anyway, leading to inevitable MHI failures.
This patch adds:
1. A retry loop (up to 5000ms) to wait for the PCIe link to recover
after global reset (similar to the existing ath12k LTSSM retry logic)
2. Proper error propagation to prevent continuing with a dead link
This is especially important for VM environments where timing can vary
significantly from bare metal.
Signed-off-by: Jose Ignacio Tornos Martinez <jtornosm@redhat.com>
---
v2: Address Sashiko comments:
- Replace jiffies-based timeout with iteration counter to work
correctly in atomic/panic contexts where jiffies does not advance
(consistency with ath12k patch 6/7)
v1: https://lore.kernel.org/all/20260930140833.576941-5-jtornosm@redhat.com/
drivers/net/wireless/ath/ath11k/pci.c | 38 ++++++++++++++++++++++-----
1 file changed, 31 insertions(+), 7 deletions(-)
diff --git a/drivers/net/wireless/ath/ath11k/pci.c b/drivers/net/wireless/ath/ath11k/pci.c
index 7114eca8810d..4fc6aaf036c1 100644
--- a/drivers/net/wireless/ath/ath11k/pci.c
+++ b/drivers/net/wireless/ath/ath11k/pci.c
@@ -190,8 +190,10 @@ static void ath11k_pci_restore_window(struct ath11k_base *ab)
spin_unlock_bh(&ab_pci->window_lock);
}
-static void ath11k_pci_soc_global_reset(struct ath11k_base *ab)
+static int ath11k_pci_soc_global_reset(struct ath11k_base *ab)
{
+ bool link_recovered = false;
+ int retries = 250;
u32 val, delay;
val = ath11k_pcic_read32(ab, PCIE_SOC_GLOBAL_RESET);
@@ -211,14 +213,28 @@ static void ath11k_pci_soc_global_reset(struct ath11k_base *ab)
mdelay(delay);
- val = ath11k_pcic_read32(ab, PCIE_SOC_GLOBAL_RESET);
- if (val == 0xffffffff)
- ath11k_warn(ab, "link down error during global reset\n");
+ /* Wait for PCIe link to recover, especially important in VM environments
+ * where timing can vary significantly from bare metal.
+ */
+ while (retries-- > 0) {
+ val = ath11k_pcic_read32(ab, PCIE_SOC_GLOBAL_RESET);
+ if (val != 0xffffffff) {
+ link_recovered = true;
+ break;
+ }
+ mdelay(20);
+ }
+ if (!link_recovered) {
+ ath11k_err(ab, "PCIe link failed to recover - device may need power cycle\n");
+ return -ETIMEDOUT;
+ }
/* Restore window register as its content is cleared during
* hardware global reset, such that it aligns with host cache.
*/
ath11k_pci_restore_window(ab);
+
+ return 0;
}
static void ath11k_pci_clear_dbg_registers(struct ath11k_base *ab)
@@ -373,8 +389,10 @@ static void ath11k_pci_force_wake(struct ath11k_base *ab)
mdelay(5);
}
-static void ath11k_pci_sw_reset(struct ath11k_base *ab, bool power_on)
+static int ath11k_pci_sw_reset(struct ath11k_base *ab, bool power_on)
{
+ int ret;
+
mdelay(100);
if (power_on) {
@@ -387,8 +405,12 @@ static void ath11k_pci_sw_reset(struct ath11k_base *ab, bool power_on)
ath11k_mhi_clear_vector(ab);
ath11k_pci_clear_dbg_registers(ab);
- ath11k_pci_soc_global_reset(ab);
+ ret = ath11k_pci_soc_global_reset(ab);
+ if (ret)
+ return ret;
ath11k_mhi_set_mhictrl_reset(ab);
+
+ return 0;
}
static void ath11k_pci_init_qmi_ce_config(struct ath11k_base *ab)
@@ -818,7 +840,9 @@ static int ath11k_pci_power_up(struct ath11k_base *ab)
ab_pci->register_window = 0;
clear_bit(ATH11K_FLAG_DEVICE_INIT_DONE, &ab->dev_flags);
- ath11k_pci_sw_reset(ab_pci->ab, true);
+ ret = ath11k_pci_sw_reset(ab_pci->ab, true);
+ if (ret)
+ return ret;
/* Disable ASPM during firmware download due to problems switching
* to AMSS state.
--
2.53.0
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH v2 5/7] ath11k: Use VFIO MSI cache when available
2026-10-11 7:45 [PATCH v2 0/7] Enable Qualcomm WiFi PCIe passthrough to VMs Jose Ignacio Tornos Martinez
` (3 preceding siblings ...)
2026-10-11 7:45 ` [PATCH v2 4/7] ath11k: add PCIe link recovery retry Jose Ignacio Tornos Martinez
@ 2026-10-11 7:45 ` Jose Ignacio Tornos Martinez
2026-10-11 7:45 ` [PATCH v2 6/7] ath12k: add PCIe link recovery retry Jose Ignacio Tornos Martinez
` (2 subsequent siblings)
7 siblings, 0 replies; 9+ messages in thread
From: Jose Ignacio Tornos Martinez @ 2026-10-11 7:45 UTC (permalink / raw)
To: jgg, alex, bhelgaas, jjohnson
Cc: johannes, mani, yishaih, skolothumtho, kevin.tian, linux-pci,
kvm, linux-wireless, ath11k, ath12k, linux-arm-msm, linux-kernel,
Jose Ignacio Tornos Martinez
The Qualcomm ath11k firmware requires physical host MSI addresses for
interrupt configuration but cannot handle IOVA-based MSI addressing in
virtualized environments.
When the device is passed through to a VM via VFIO:
- The guest kernel allocates MSI vectors and sees IOVA addresses
- The ath11k firmware expects physical host MSI addresses
- Providing IOVA to the firmware causes interrupt delivery to fail
- Result: device is non-functional in VMs
The qcom-vfio-pci variant driver (introduced in earlier patches) solves
this by caching the physical host MSI values and exposing them through
PCI extended config space with a "QMSI" magic signature for discovery.
Search for the QMSI magic signature in extended config space. If found,
the device is running on a VM and we can read the cached host MSI values
from that location. If the VFIO cache is not present (i.e. on baremetal),
fall back to standard PCI MSI configuration.
This change is transparent, arch independent and requires no user
configuration when used with the qcom-vfio-pci driver.
Signed-off-by: Jose Ignacio Tornos Martinez <jtornosm@redhat.com>
---
v2: Address Sashiko comments:
- Add vfio_msi_cached flag to ath11k_pci struct to track when VFIO
cached values are in use
- Guard config_msi_data() to skip overwrite when VFIO cached values
are already set, preventing guest virtual MSI data from overwriting
host physical values
v1: https://lore.kernel.org/all/20260930140833.576941-6-jtornosm@redhat.com/
drivers/net/wireless/ath/ath11k/pci.c | 37 +++++++++++++++++++++++++++
drivers/net/wireless/ath/ath11k/pci.h | 2 ++
2 files changed, 39 insertions(+)
diff --git a/drivers/net/wireless/ath/ath11k/pci.c b/drivers/net/wireless/ath/ath11k/pci.c
index c372676b097f..b5e4a3f21d89 100644
--- a/drivers/net/wireless/ath/ath11k/pci.c
+++ b/drivers/net/wireless/ath/ath11k/pci.c
@@ -10,6 +10,7 @@
#include <linux/of.h>
#include <linux/time.h>
#include <linux/vmalloc.h>
+#include <linux/qcom_vfio.h>
#include "pci.h"
#include "core.h"
@@ -463,8 +464,11 @@ static int ath11k_pci_alloc_msi(struct ath11k_pci *ab_pci)
struct ath11k_base *ab = ab_pci->ab;
const struct ath11k_msi_config *msi_config = ab->pci.msi.config;
struct pci_dev *pci_dev = ab_pci->pdev;
+ u32 msi_addr_lo = 0, msi_addr_hi = 0;
struct msi_desc *msi_desc;
+ u16 msi_data = 0;
int num_vectors;
+ int msi_offset;
int ret;
num_vectors = pci_alloc_irq_vectors(pci_dev,
@@ -488,6 +492,36 @@ static int ath11k_pci_alloc_msi(struct ath11k_pci *ab_pci)
}
ath11k_info(ab, "MSI vectors: %d\n", num_vectors);
+ /*
+ * Try to get host MSI address from VFIO cache.
+ * When running on a VM, the ath11k firmware requires physical host
+ * MSI addresses and cannot handle guest IOVA addresses.
+ * qcom-vfio-pci writes host MSI values to extended config space with
+ * a magic signature "QMSI" for discovery. Search for it.
+ */
+ msi_offset = qcom_vfio_find_msi_cache(pci_dev);
+ if (msi_offset) {
+ pci_read_config_dword(pci_dev,
+ msi_offset + QCOM_VFIO_MSI_ADDR_LO_OFFSET,
+ &msi_addr_lo);
+ pci_read_config_dword(pci_dev,
+ msi_offset + QCOM_VFIO_MSI_ADDR_HI_OFFSET,
+ &msi_addr_hi);
+ pci_read_config_word(pci_dev,
+ msi_offset + QCOM_VFIO_MSI_DATA_OFFSET,
+ &msi_data);
+
+ if (msi_addr_lo && msi_addr_lo != 0xFFFFFFFF) {
+ ab->pci.msi.addr_lo = msi_addr_lo;
+ ab->pci.msi.addr_hi = msi_addr_hi;
+ ab->pci.msi.ep_base_data = msi_data;
+ ab_pci->vfio_msi_cached = true;
+ ath11k_info(ab, "VFIO MSI cache at offset 0x%x: addr_lo=0x%x addr_hi=0x%x data=%d\n",
+ msi_offset, msi_addr_lo, msi_addr_hi, msi_data);
+ return 0;
+ }
+ }
+
ath11k_pci_msi_disable(ab_pci);
msi_desc = irq_get_msi_desc(ab_pci->pdev->irq);
@@ -527,7 +561,10 @@ static void ath11k_pci_free_msi(struct ath11k_pci *ab_pci)
static int ath11k_pci_config_msi_data(struct ath11k_pci *ab_pci)
{
struct msi_desc *msi_desc;
+ if (ab_pci->vfio_msi_cached)
+ 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");
diff --git a/drivers/net/wireless/ath/ath11k/pci.h b/drivers/net/wireless/ath/ath11k/pci.h
index 9a17fbc1e5d2..4c8b6d3f7a21 100644
--- a/drivers/net/wireless/ath/ath11k/pci.h
+++ b/drivers/net/wireless/ath/ath11k/pci.h
@@ -74,6 +74,8 @@ struct ath11k_pci {
unsigned long flags;
u16 link_ctl;
u64 dma_mask;
+
+ bool vfio_msi_cached;
};
static inline struct ath11k_pci *ath11k_pci_priv(struct ath11k_base *ab)
--
2.55.0
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH v2 6/7] ath12k: add PCIe link recovery retry
2026-10-11 7:45 [PATCH v2 0/7] Enable Qualcomm WiFi PCIe passthrough to VMs Jose Ignacio Tornos Martinez
` (4 preceding siblings ...)
2026-10-11 7:45 ` [PATCH v2 5/7] ath11k: Use VFIO MSI cache when available Jose Ignacio Tornos Martinez
@ 2026-10-11 7:45 ` Jose Ignacio Tornos Martinez
2026-10-11 7:45 ` [PATCH v2 7/7] ath12k: Use VFIO MSI cache when available Jose Ignacio Tornos Martinez
2026-10-11 18:22 ` [PATCH v2 0/7] Enable Qualcomm WiFi PCIe passthrough to VMs Jeff Johnson
7 siblings, 0 replies; 9+ messages in thread
From: Jose Ignacio Tornos Martinez @ 2026-10-11 7:45 UTC (permalink / raw)
To: jgg, alex, bhelgaas, jjohnson
Cc: johannes, mani, yishaih, skolothumtho, kevin.tian, linux-pci,
kvm, linux-wireless, ath11k, ath12k, linux-arm-msm, linux-kernel,
Jose Ignacio Tornos Martinez
When ath12k devices are passed through to VMs via PCIe passthrough,
the PCIe link may not stabilize immediately after global reset due to
virtualization layer timing variations. This causes MHI initialization
to fail with "BHI offset: 0xffffffff is out of range" errors because
the device is read before it's ready.
Currently, ath12k_pci_soc_global_reset() just warns if the link is
down but continues anyway, leading to inevitable MHI failures.
This patch adds:
1. A retry loop (up to 5000ms) to wait for the PCIe link to recover
after global reset (similar to the existing LTSSM retry logic)
2. Proper error propagation to prevent continuing with a dead link
This is especially important for VM environments where timing can vary
significantly from bare metal.
Signed-off-by: Jose Ignacio Tornos Martinez <jtornosm@redhat.com>
---
v2: Address Sashiko comments:
- Replace jiffies-based timeout with iteration counter to work
correctly when called from ath12k_pci_panic_handler() where
interrupts are disabled and jiffies does not advance
v1: https://lore.kernel.org/all/20260930140833.576941-7-jtornosm@redhat.com/
drivers/net/wireless/ath/ath12k/pci.c | 38 ++++++++++++++++++++++-----
1 file changed, 31 insertions(+), 7 deletions(-)
diff --git a/drivers/net/wireless/ath/ath12k/pci.c b/drivers/net/wireless/ath/ath12k/pci.c
index af0e882fd0b5..65712486d312 100644
--- a/drivers/net/wireless/ath/ath12k/pci.c
+++ b/drivers/net/wireless/ath/ath12k/pci.c
@@ -181,8 +181,10 @@ static void ath12k_pci_restore_window(struct ath12k_base *ab)
spin_unlock_bh(&ab_pci->window_lock);
}
-static void ath12k_pci_soc_global_reset(struct ath12k_base *ab)
+static int ath12k_pci_soc_global_reset(struct ath12k_base *ab)
{
+ bool link_recovered = false;
+ int retries = 250;
u32 val, delay;
val = ath12k_pci_read32(ab, PCIE_SOC_GLOBAL_RESET);
@@ -206,14 +208,28 @@ static void ath12k_pci_soc_global_reset(struct ath12k_base *ab)
mdelay(delay);
- val = ath12k_pci_read32(ab, PCIE_SOC_GLOBAL_RESET);
- if (val == 0xffffffff)
- ath12k_warn(ab, "link down error during global reset\n");
+ /* Wait for PCIe link to recover, especially important in VM environments
+ * where timing can vary significantly from bare metal.
+ */
+ while (retries-- > 0) {
+ val = ath12k_pci_read32(ab, PCIE_SOC_GLOBAL_RESET);
+ if (val != 0xffffffff) {
+ link_recovered = true;
+ break;
+ }
+ mdelay(20);
+ }
+ if (!link_recovered) {
+ ath12k_err(ab, "PCIe link failed to recover - device may need power cycle\n");
+ return -ETIMEDOUT;
+ }
/* Restore window register as its content is cleared during
* hardware global reset, such that it aligns with host cache.
*/
ath12k_pci_restore_window(ab);
+
+ return 0;
}
static void ath12k_pci_clear_dbg_registers(struct ath12k_base *ab)
@@ -299,8 +315,10 @@ static void ath12k_pci_force_wake(struct ath12k_base *ab)
mdelay(5);
}
-static void ath12k_pci_sw_reset(struct ath12k_base *ab, bool power_on)
+static int ath12k_pci_sw_reset(struct ath12k_base *ab, bool power_on)
{
+ int ret;
+
if (power_on) {
ath12k_pci_enable_ltssm(ab);
ath12k_pci_clear_all_intrs(ab);
@@ -309,8 +327,12 @@ static void ath12k_pci_sw_reset(struct ath12k_base *ab, bool power_on)
ath12k_mhi_clear_vector(ab);
ath12k_pci_clear_dbg_registers(ab);
- ath12k_pci_soc_global_reset(ab);
+ ret = ath12k_pci_soc_global_reset(ab);
+ if (ret)
+ return ret;
ath12k_mhi_set_mhictrl_reset(ab);
+
+ return 0;
}
static void ath12k_pci_free_ce_irq(struct ath12k_base *ab, int num_ce)
@@ -1452,7 +1474,9 @@ int ath12k_pci_power_up(struct ath12k_base *ab)
ab_pci->register_window = 0;
clear_bit(ATH12K_PCI_FLAG_INIT_DONE, &ab_pci->flags);
- ath12k_pci_sw_reset(ab_pci->ab, true);
+ ret = ath12k_pci_sw_reset(ab_pci->ab, true);
+ if (ret)
+ return ret;
/* Disable ASPM during firmware download due to problems switching
* to AMSS state.
--
2.55.0
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH v2 7/7] ath12k: Use VFIO MSI cache when available
2026-10-11 7:45 [PATCH v2 0/7] Enable Qualcomm WiFi PCIe passthrough to VMs Jose Ignacio Tornos Martinez
` (5 preceding siblings ...)
2026-10-11 7:45 ` [PATCH v2 6/7] ath12k: add PCIe link recovery retry Jose Ignacio Tornos Martinez
@ 2026-10-11 7:45 ` Jose Ignacio Tornos Martinez
2026-10-11 18:22 ` [PATCH v2 0/7] Enable Qualcomm WiFi PCIe passthrough to VMs Jeff Johnson
7 siblings, 0 replies; 9+ messages in thread
From: Jose Ignacio Tornos Martinez @ 2026-10-11 7:45 UTC (permalink / raw)
To: jgg, alex, bhelgaas, jjohnson
Cc: johannes, mani, yishaih, skolothumtho, kevin.tian, linux-pci,
kvm, linux-wireless, ath11k, ath12k, linux-arm-msm, linux-kernel,
Jose Ignacio Tornos Martinez
The Qualcomm ath12k firmware requires physical host MSI addresses for
interrupt configuration but cannot handle IOVA-based MSI addressing in
virtualized environments.
When the device is passed through to a VM via VFIO:
- The guest kernel allocates MSI vectors and sees IOVA addresses
- The ath12k firmware expects physical host MSI addresses
- Providing IOVA to the firmware causes interrupt delivery to fail
- Result: device is non-functional in VMs
The qcom-vfio-pci variant driver (introduced in earlier patches) solves
this by caching the physical host MSI values and exposing them through
PCI extended config space with a "QMSI" magic signature for discovery.
Search for the QMSI magic signature in extended config space. If found,
the device is running on a VM and we can read the cached host MSI values
from that location. If the VFIO cache is not present (i.e. on baremetal),
fall back to standard PCI MSI configuration.
This change is transparent, arch independent and requires no user
configuration when used with the qcom-vfio-pci driver.
Signed-off-by: Jose Ignacio Tornos Martinez <jtornosm@redhat.com>
---
v2: Address Sashiko comments:
- Guard config_msi_data() to skip overwrite when VFIO cached values
are already set, preventing guest virtual MSI data from overwriting
host physical values
v1: https://lore.kernel.org/all/20260930140833.576941-8-jtornosm@redhat.com/
drivers/net/wireless/ath/ath12k/pci.c | 46 +++++++++++++++++++++++++++
drivers/net/wireless/ath/ath12k/pci.h | 6 ++++
2 files changed, 52 insertions(+)
diff --git a/drivers/net/wireless/ath/ath12k/pci.c b/drivers/net/wireless/ath/ath12k/pci.c
index 65712486d312..a9f3b2e18c45 100644
--- a/drivers/net/wireless/ath/ath12k/pci.c
+++ b/drivers/net/wireless/ath/ath12k/pci.c
@@ -8,6 +8,7 @@
#include <linux/interrupt.h>
#include <linux/msi.h>
#include <linux/pci.h>
+#include <linux/qcom_vfio.h>
#include <linux/time.h>
#include <linux/vmalloc.h>
@@ -783,8 +784,11 @@ static int ath12k_pci_msi_alloc(struct ath12k_pci *ab_pci)
{
struct ath12k_base *ab = ab_pci->ab;
const struct ath12k_msi_config *msi_config = ab_pci->msi_config;
+ u32 msi_addr_lo = 0, msi_addr_hi = 0;
struct msi_desc *msi_desc;
+ u16 msi_data = 0;
int num_vectors;
+ int msi_offset;
int ret;
num_vectors = pci_alloc_irq_vectors(ab_pci->pdev,
@@ -812,6 +816,39 @@ static int ath12k_pci_msi_alloc(struct ath12k_pci *ab_pci)
ath12k_info(ab, "MSI vectors: %d\n", num_vectors);
+ /*
+ * Try to get host MSI address from VFIO cache.
+ * When running on a VM, the ath12k firmware requires physical host
+ * MSI addresses and cannot handle guest IOVA addresses.
+ * qcom-vfio-pci writes host MSI values to extended config space with
+ * a magic signature "QMSI" for discovery. Search for it.
+ */
+ msi_offset = qcom_vfio_find_msi_cache(ab_pci->pdev);
+ if (msi_offset) {
+ pci_read_config_dword(ab_pci->pdev,
+ msi_offset + QCOM_VFIO_MSI_ADDR_LO_OFFSET,
+ &msi_addr_lo);
+ pci_read_config_dword(ab_pci->pdev,
+ msi_offset + QCOM_VFIO_MSI_ADDR_HI_OFFSET,
+ &msi_addr_hi);
+ pci_read_config_word(ab_pci->pdev,
+ msi_offset + QCOM_VFIO_MSI_DATA_OFFSET,
+ &msi_data);
+
+ if (msi_addr_lo && msi_addr_lo != 0xFFFFFFFF) {
+ ab_pci->msi_addr_hi = msi_addr_hi;
+ ab_pci->msi_addr_lo = msi_addr_lo;
+ ab_pci->msi_ep_base_data = msi_data;
+ ab_pci->msi_addr_cached = true;
+
+ ath12k_info(ab,
+ "using host MSI from VFIO at offset 0x%x: addr_lo=0x%x addr_hi=0x%x data=%d\n",
+ msi_offset, msi_addr_lo, msi_addr_hi, msi_data);
+ return 0;
+ }
+ }
+ ab_pci->msi_addr_cached = false;
+
ath12k_pci_msi_disable(ab_pci);
msi_desc = irq_get_msi_desc(ab_pci->pdev->irq);
@@ -843,7 +880,10 @@ static void ath12k_pci_msi_free(struct ath12k_pci *ab_pci)
static int ath12k_pci_config_msi_data(struct ath12k_pci *ab_pci)
{
struct msi_desc *msi_desc;
+ if (ab_pci->msi_addr_cached)
+ return 0;
+
msi_desc = irq_get_msi_desc(ab_pci->pdev->irq);
if (!msi_desc) {
ath12k_err(ab_pci->ab, "msi_desc is NULL!\n");
@@ -1083,6 +1123,12 @@ void ath12k_pci_get_msi_address(struct ath12k_base *ab, u32 *msi_addr_lo,
struct ath12k_pci *ab_pci = ath12k_pci_priv(ab);
struct pci_dev *pci_dev = to_pci_dev(ab->dev);
+ if (ab_pci->msi_addr_cached) {
+ *msi_addr_hi = ab_pci->msi_addr_hi;
+ *msi_addr_lo = ab_pci->msi_addr_lo;
+ return;
+ }
+
pci_read_config_dword(pci_dev, pci_dev->msi_cap + PCI_MSI_ADDRESS_LO,
msi_addr_lo);
diff --git a/drivers/net/wireless/ath/ath12k/pci.h b/drivers/net/wireless/ath/ath12k/pci.h
index 0e0e2020c6ae..ee3696d40dfb 100644
--- a/drivers/net/wireless/ath/ath12k/pci.h
+++ b/drivers/net/wireless/ath/ath12k/pci.h
@@ -129,6 +129,12 @@ struct ath12k_pci {
/* enum ath12k_pci_flags */
unsigned long flags;
u16 link_ctl;
+
+ /* Cached host MSI address for firmware configuration in VMs */
+ u32 msi_addr_hi;
+ u32 msi_addr_lo;
+ bool msi_addr_cached;
+
unsigned long irq_flags;
const struct ath12k_pci_ops *pci_ops;
u32 qmi_instance;
--
2.55.0
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH v2 0/7] Enable Qualcomm WiFi PCIe passthrough to VMs
2026-10-11 7:45 [PATCH v2 0/7] Enable Qualcomm WiFi PCIe passthrough to VMs Jose Ignacio Tornos Martinez
` (6 preceding siblings ...)
2026-10-11 7:45 ` [PATCH v2 7/7] ath12k: Use VFIO MSI cache when available Jose Ignacio Tornos Martinez
@ 2026-10-11 18:22 ` Jeff Johnson
7 siblings, 0 replies; 9+ messages in thread
From: Jeff Johnson @ 2026-10-11 18:22 UTC (permalink / raw)
To: Jose Ignacio Tornos Martinez, jgg, alex, bhelgaas, jjohnson
Cc: johannes, mani, yishaih, skolothumtho, kevin.tian, linux-pci,
kvm, linux-wireless, ath11k, ath12k, linux-arm-msm, linux-kernel,
Baochen Qiang, Rameshkumar Sundaram
On 10/11/2026 12:45 AM, Jose Ignacio Tornos Martinez wrote:
> This series enables Qualcomm ath11k/ath12k WiFi devices to work in VM
> passthrough scenarios, addressing a long-standing issue where these
> devices fail to initialize when passed through to VMs via VFIO.
>
> Qualcomm ath11k/ath12k WiFi devices have been broken in VM passthrough
> since they were introduced. The Qualcomm firmware has its own interrupt
> controller with private registers that need to be programmed with MSI
> addr/data. The driver extracts the addr/data from the MSI descriptor
> and programs the firmware's private registers. In a VM, the driver
> only sees virtual (IOVA) addr/data, but the firmware's private writes
> bypass the IOMMU, so they need the physical host values. This causes
> firmware initialization to fail with errors like "BHI offset:
> 0xffffffff is out of range".
>
> Multiple attempts have been made to solve this over the past 2 years:
>
> March 2024: Workaround using module parameters [1]
> - Required manually copying MSI values from host to VM
> - Rejected by Johannes Berg as too manual, suggested VMM solution
>
> August 2024: QEMU + kernel solution by Alex Williamson [2]
> - Kernel disables MSI virtualization, QEMU intercepts config writes
> - Uses brute force pattern matching to infer MSI data writes
> - Stuck as RFC - no progress in 8+ months
>
> No action from Qualcomm: Despite multiple reports, no vendor solution
>
> The solution in this series provides a clean, kernel-only approach that
> addresses the previous concerns. The PCI reset support for these devices
> has already landed in linux-next (commit f8522a518e59 "PCI: Add device-
> specific reset for Qualcomm WCN6855/WCN7850 WLAN") [3], solving the device
> reset path for VFIO passthrough. This MSI series is the remaining
> piece, with both in place, these Qualcomm WiFi devices would be fully
> operational in VMs for the first time.
>
> In practice, this specific behavior (firmware requiring physical host
> MSI addresses in private registers, bypassing the IOMMU) has only
> been observed in Qualcomm ath11k/ath12k devices. A device-specific
> approach seems appropriate, and the VFIO variant driver pattern fits
> well for this, following the same established pattern as mlx5-vfio-pci
> and nvgrace-gpu for their own device-specific needs.
>
> Architecture:
> 1. qcom-vfio-pci variant driver caches physical host MSI values and
> exposes them via extended config space with magic signature "QMSI".
> Device-specific hardware quirks belong only in kernel drivers, not
> userspace, independent of the tools used.
> 2. VM drivers (ath11k/ath12k) automatically discover and use cached values
> 3. PCIe link recovery handles VM timing variations
>
> Thoroughly tested: 3 VMs across 2 WiFi generations, all devices
> successfully initialized and are ready for use.
> - VM1: ath11k (WCN6855)
> - VM2: ath11k (WCN6855)
> - VM3: ath12k (WCN7850)
>
> Series structure:
> Patch 1/7: PCI helper to find free space in extended config space
> for device-specific data exchange between host and VM drivers
> Patch 2/7: Shared header defining the MSI cache protocol between
> the host variant driver and VM drivers
> Patch 3/7: qcom-vfio-pci variant driver that caches host physical
> MSI address/data and exposes them to VMs through extended config
> space
> Patch 4/7: ath11k PCIe link recovery retry to handle initialization
> timing variations in VM environments
> Patch 5/7: ath11k support to read cached host MSI values and
> configure firmware with correct physical addresses, making the
> device operational in VMs
> Patch 6/7: ath12k PCIe link recovery retry to handle initialization
> timing variations in VM environments
> Patch 7/7: ath12k support to read cached host MSI values and
> configure firmware with correct physical addresses, making the
> device operational in VMs
>
> [1] https://lore.kernel.org/all/20240322104912.94811-1-jtornosm@redhat.com/
> [2] https://lore.kernel.org/kvm/20240812170014.1583783-1-alex.williamson@redhat.com/
> [3] https://lore.kernel.org/all/20261005105436.83962-1-jtornosm@redhat.com/
> [4] Original problem report:
> https://lore.kernel.org/all/fc6bd06f-d52b-4dee-ab1b-4bb845cc0b95@quicinc.com/
>
> ---
> v2: Complete cover letter with more information
Thank you for providing this level of detail in your cover letter. I've raised
this issue with the engineering team in the past, but I've once again raised
the issue with your in-depth analysis.
/jeff
^ permalink raw reply [flat|nested] 9+ messages in thread
end of thread, other threads:[~2026-10-11 18:22 UTC | newest]
Thread overview: 9+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-11 7:45 [PATCH v2 0/7] Enable Qualcomm WiFi PCIe passthrough to VMs Jose Ignacio Tornos Martinez
2026-10-11 7:45 ` [PATCH v2 1/7] PCI: Add pci_find_free_ext_cap_offset() helper Jose Ignacio Tornos Martinez
2026-10-11 7:45 ` [PATCH v2 2/7] vfio: Add qcom_vfio.h header for MSI cache protocol Jose Ignacio Tornos Martinez
2026-10-11 7:45 ` [PATCH v2 3/7] vfio/pci: Add qcom-vfio-pci variant driver Jose Ignacio Tornos Martinez
2026-10-11 7:45 ` [PATCH v2 4/7] ath11k: add PCIe link recovery retry Jose Ignacio Tornos Martinez
2026-10-11 7:45 ` [PATCH v2 5/7] ath11k: Use VFIO MSI cache when available Jose Ignacio Tornos Martinez
2026-10-11 7:45 ` [PATCH v2 6/7] ath12k: add PCIe link recovery retry Jose Ignacio Tornos Martinez
2026-10-11 7:45 ` [PATCH v2 7/7] ath12k: Use VFIO MSI cache when available Jose Ignacio Tornos Martinez
2026-10-11 18:22 ` [PATCH v2 0/7] Enable Qualcomm WiFi PCIe passthrough to VMs Jeff Johnson
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®