All of lore.kernel.org
 help / color / mirror / Atom feed
* [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; 17+ 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] 17+ 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:57   ` sashiko-bot
  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, 1 reply; 17+ 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 related	[flat|nested] 17+ 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:59   ` sashiko-bot
  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, 1 reply; 17+ 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 related	[flat|nested] 17+ 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  8:07   ` sashiko-bot
  2026-10-11  7:45 ` [PATCH v2 4/7] ath11k: add PCIe link recovery retry Jose Ignacio Tornos Martinez
                   ` (4 subsequent siblings)
  7 siblings, 1 reply; 17+ 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 related	[flat|nested] 17+ 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:59   ` sashiko-bot
  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, 1 reply; 17+ 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 related	[flat|nested] 17+ 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  8:04   ` sashiko-bot
  2026-10-11  7:45 ` [PATCH v2 6/7] ath12k: add PCIe link recovery retry Jose Ignacio Tornos Martinez
                   ` (2 subsequent siblings)
  7 siblings, 1 reply; 17+ 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 related	[flat|nested] 17+ 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  8:05   ` sashiko-bot
  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, 1 reply; 17+ 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 related	[flat|nested] 17+ 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  8:02   ` sashiko-bot
  2026-10-11 18:22 ` [PATCH v2 0/7] Enable Qualcomm WiFi PCIe passthrough to VMs Jeff Johnson
  7 siblings, 1 reply; 17+ 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 related	[flat|nested] 17+ messages in thread

* Re: [PATCH v2 1/7] PCI: Add pci_find_free_ext_cap_offset() helper
  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:57   ` sashiko-bot
  0 siblings, 0 replies; 17+ messages in thread
From: sashiko-bot @ 2026-10-11  7:57 UTC (permalink / raw)
  To: Jose Ignacio Tornos Martinez; +Cc: linux-pci, kvm

Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [High] pci: zero-scan identifies zeroed payload of highest capability as free space
- [High] pci: out-of-bounds config space access due to hardcoded PCI_CFG_SPACE_EXP_SIZE
- [High] pci: config space corruption due to zero-scanning capability payloads
- [Medium] pci: function fails to identify completely empty extended config space
--

commit c1630045212cdfa197e241362843099e2b8fa29f
Author: Jose Ignacio Tornos Martinez <jtornosm@redhat.com>

PCI: Add pci_find_free_ext_cap_offset() helper

This commit adds a helper function to find free space in the PCI extended
configuration space for VFIO variant drivers to use. It walks the extended
capability chain and scans for contiguous zeroed space.

> diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c
> index 8cc6a89130b53..ae234c9a2a29b 100644
> --- a/drivers/pci/pci.c
> +++ b/drivers/pci/pci.c
[ ... ]
> +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;

[Severity: Medium]
Will this early exit prevent the function from utilizing completely empty
extended configuration space?

If the header at PCI_CFG_SPACE_SIZE is 0 or 0xffffffff, it indicates
the device has no extended capabilities whatsoever. In this case, the entire
extended configuration space up to dev->cfg_size is unused and free.

Returning 0 here prevents drivers from utilizing this completely empty
configuration space.

> +
> +	while (ttl-- > 0) {
[ ... ]
> +	if (!highest_pos)
> +		return 0;
> +
> +	for (offset = highest_pos; offset <= (int)PCI_CFG_SPACE_EXP_SIZE - 4;
> +	     offset += 4) {

[Severity: High]
Does this loop bound cause out-of-bounds configuration space accesses?

The loop condition is hardcoded to PCI_CFG_SPACE_EXP_SIZE - 4 (4092 bytes),
completely ignoring dev->cfg_size. Many PCIe devices and host bridges have
restricted configuration sizes (e.g., 512, 1024, or 2048 bytes) due to hardware
limits or quirks.

For these devices, calling pci_read_config_dword() outside their physical
configuration space can trigger unsupported request errors, leading to machine
check exceptions or system errors.

If the bridge returns 0 for these out-of-bounds reads, the function could
falsely identify non-existent space as free, leading to out-of-bounds writes.

[Severity: High]
Could starting the scan exactly at highest_pos cause the function to scan
an active capability's payload?

The scan begins exactly at the header of the highest-offset capability
(highest_pos), instead of skipping past its actual size.

> +		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 {

[Severity: High]
Will this zero-scanning approach corrupt active capabilities?

Because the scan doesn't skip the actual size of the highest capability, it
reads the payload registers (such as status, mask, or log registers) which
often naturally default to zero.

If the scanner finds a contiguous block of zeroes inside the active payload, it
will falsely return this internal offset as 'free space'.

Callers writing to this offset will then silently overwrite and corrupt the
active capability's state.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261011074514.515894-1-jtornosm@redhat.com?part=1

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [PATCH v2 4/7] ath11k: add PCIe link recovery retry
  2026-10-11  7:45 ` [PATCH v2 4/7] ath11k: add PCIe link recovery retry Jose Ignacio Tornos Martinez
@ 2026-10-11  7:59   ` sashiko-bot
  0 siblings, 0 replies; 17+ messages in thread
From: sashiko-bot @ 2026-10-11  7:59 UTC (permalink / raw)
  To: Jose Ignacio Tornos Martinez; +Cc: kvm, linux-pci

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] ath11k: 5000ms mdelay() causes severe CPU stall in normal contexts
--

commit 457eae9bbd9159c5c25a5626326a881f1232316d
Author: Jose Ignacio Tornos Martinez <jtornosm@redhat.com>

ath11k: add PCIe link recovery retry

When ath11k devices are passed through to VMs via PCIe passthrough, the
PCIe link may not stabilize immediately after global reset. This patch
adds a retry loop to wait for the PCIe link to recover after global
reset and prevents continuing with a dead link.

Link: https://lore.kernel.org/all/20260930140833.576941-5-jtornosm@redhat.com/

> diff --git a/drivers/net/wireless/ath/ath11k/pci.c b/drivers/net/wireless/ath/ath11k/pci.c
> index a163168f36176..7e29ebaf97731 100644
> --- a/drivers/net/wireless/ath/ath11k/pci.c
> +++ b/drivers/net/wireless/ath/ath11k/pci.c

[ ... ]

> @@ -215,14 +217,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);
> +	}

[Severity: High]
Does this mdelay() loop cause a severe CPU stall?

The commit notes indicate this loop was changed to an iteration counter to
work in atomic/panic contexts. However, looking at the callers of
ath11k_pci_soc_global_reset() (such as probe, core_reset, core_restart,
and resume_early), these execute in contexts where it seems safe to sleep.
Even in resume_early, interrupts are enabled and jiffies advance normally.

Furthermore, immediately after this loop, the code calls
ath11k_pci_restore_window(), which unconditionally takes a spinlock with
spin_lock_bh():

ath11k_pci_restore_window(struct ath11k_base *ab) {
    ...
    spin_lock_bh(&ab_pci->window_lock);
    ...
}

Since taking a bottom-half disabling spinlock would be invalid in a hardirq
or panic context, doesn't this mean the code runs in normal process context?
If so, spinning the CPU without yielding for up to 5000ms (250 * 20ms) might
degrade responsiveness and risk triggering the soft lockup watchdog.

Could msleep() or fsleep() be used here instead?

> +	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)

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261011074514.515894-1-jtornosm@redhat.com?part=4

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [PATCH v2 2/7] vfio: Add qcom_vfio.h header for MSI cache protocol
  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:59   ` sashiko-bot
  0 siblings, 0 replies; 17+ messages in thread
From: sashiko-bot @ 2026-10-11  7:59 UTC (permalink / raw)
  To: Jose Ignacio Tornos Martinez; +Cc: kvm, linux-pci

Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] pci: hardware state overwrite via pci_find_free_ext_cap_offset()
- [High] pci: out-of-bounds config space read in qcom_vfio_find_msi_cache
- [High] pci/vfio: false positive in linear MSI cache scan risks MSI misconfiguration
--

commit a4701d60f47321f6e79355d9a8e74d4e1c2789b7
Author: Jose Ignacio Tornos Martinez <jtornosm@redhat.com>

vfio: Add qcom_vfio.h header for MSI cache protocol

Add a shared header for the qcom-vfio-pci driver and Qualcomm device drivers.
It defines a communication protocol for caching host MSI values in extended
PCI config space with a "QMSI" magic signature for VM discovery. This avoids
offset mismatches when QEMU filters certain capabilities.

> diff --git a/include/linux/qcom_vfio.h b/include/linux/qcom_vfio.h
> new file mode 100644
> index 0000000000000..7553bc382c8e4
> --- /dev/null
> +++ b/include/linux/qcom_vfio.h
> @@ -0,0 +1,90 @@

[ ... ]

> +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);
> +}

[Severity: High]
Does relying on pci_find_free_ext_cap_offset() risk hardware state overwrite?

The implementation of pci_find_free_ext_cap_offset() does not skip the size
of the last extended capability. It begins scanning for zeroed registers
immediately at the start of the last active capability. Consequently, it can
misidentify zeroed registers (such as reserved or masked fields) inside
active capabilities as free space.

Using this offset to cache MSI values could overwrite live hardware
registers, causing a regression due to hardware state corruption.
Furthermore, if the extended config space is completely empty, it
incorrectly returns 0 instead of 0x100.

[ ... ]

> +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) {

[Severity: High]
Could this loop lead to out-of-bounds config space reads on bare-metal
systems?

The qcom_vfio_find_msi_cache() helper is called directly by the ath11k and
ath12k drivers during normal probe, meaning it executes on bare-metal
systems, not just inside VMs. The loop unconditionally reads up to
PCI_CFG_SPACE_EXP_SIZE (4096 bytes), completely bypassing the dynamic
pdev->cfg_size limit.

The PCI core generic pci_read_config_dword() does not perform bounds
checking against pdev->cfg_size and relies entirely on callers to respect
the boundaries. If the host platform restricts the config space size (for
example, due to a broken ECAM falling back to legacy accesses or a
PCIe-to-PCI bridge), this loop will force reads beyond safe boundaries.

Accessing undefined extended configuration space on bare-metal can cause
regressions like system lockups or fatal PCIe Unsupported Request panics.

> +		if (pci_read_config_dword(pdev, offset, &magic))
> +			continue;
> +		if (magic == QCOM_VFIO_MSI_MAGIC)
> +			return offset;
> +	}

[Severity: High]
Is it possible for this linear scan to falsely identify unrelated data as
the MSI cache, leading to misconfiguration?

This linear scan bypasses the PCIe standard capability chain traversal and
blindly reads all extended config registers, including dynamic logs like AER.

If a hardware error occurs and the AER Header Log captures a TLP containing
the value 0x49534D51, the scan will falsely identify it as the VFIO MSI
cache. It would then read the adjacent AER log values as msi_addr_lo,
msi_addr_hi, and msi_data, and program the physical device to use a garbage
MSI address.

This could lead to regressions including data corruption, IOMMU faults, and
loss of network interrupts.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261011074514.515894-1-jtornosm@redhat.com?part=2

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [PATCH v2 7/7] ath12k: Use VFIO MSI cache when available
  2026-10-11  7:45 ` [PATCH v2 7/7] ath12k: Use VFIO MSI cache when available Jose Ignacio Tornos Martinez
@ 2026-10-11  8:02   ` sashiko-bot
  0 siblings, 0 replies; 17+ messages in thread
From: sashiko-bot @ 2026-10-11  8:02 UTC (permalink / raw)
  To: Jose Ignacio Tornos Martinez; +Cc: kvm, linux-pci

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] pci: unsafe linear scan of extended config space on baremetal
--

commit 7935697effd72af8780a5758936f11256bc2c6d5
Author: Jose Ignacio Tornos Martinez <jtornosm@redhat.com>

ath12k: Use VFIO MSI cache when available

This commit modifies the ath12k driver to search for a VFIO MSI cache in the
PCIe extended config space. It uses these cached host MSI physical addresses
for firmware configuration instead of guest IOVA addresses, resolving issues
when the device is passed through to a VM via VFIO.

> diff --git a/drivers/net/wireless/ath/ath12k/pci.c b/drivers/net/wireless/ath/ath12k/pci.c
> index f7879fb48d32e..8e92213fd3fce 100644
> --- a/drivers/net/wireless/ath/ath12k/pci.c
> +++ b/drivers/net/wireless/ath/ath12k/pci.c
[ ... ]
> @@ -811,6 +815,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);

[Severity: Medium]
Is it safe to unconditionally call qcom_vfio_find_msi_cache() on all setups,
including baremetal hardware?

This function performs a linear scan of the extended config space:

include/linux/qcom_vfio.h:qcom_vfio_find_msi_cache() {
    ...
    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;
    ...
}

Reading arbitrary PCIe configuration registers on baremetal hardware can
trigger read-to-clear side effects, unsupported request (UR) errors, or
Advanced Error Reporting (AER) faults.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261011074514.515894-1-jtornosm@redhat.com?part=7

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [PATCH v2 5/7] ath11k: Use VFIO MSI cache when available
  2026-10-11  7:45 ` [PATCH v2 5/7] ath11k: Use VFIO MSI cache when available Jose Ignacio Tornos Martinez
@ 2026-10-11  8:04   ` sashiko-bot
  0 siblings, 0 replies; 17+ messages in thread
From: sashiko-bot @ 2026-10-11  8:04 UTC (permalink / raw)
  To: Jose Ignacio Tornos Martinez; +Cc: linux-pci, kvm

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] ath11k: early return in ath11k_pci_alloc_msi() skips MSI disable
- [Medium] pci: unsafe linear scan of extended config space in qcom_vfio_find_msi_cache()
--

commit f45d065c25ccffe5ad5bba4c9805876b528061e6
Author: Jose Ignacio Tornos Martinez <jtornosm@redhat.com>
Subject: ath11k: Use VFIO MSI cache when available

This commit introduces a mechanism to detect and read physical host MSI
addresses from a cached location in the PCI extended config space. This
allows the device to function correctly when passed through to a VM via
VFIO, avoiding interrupt delivery failures caused by guest IOVA addresses.

> diff --git a/drivers/net/wireless/ath/ath11k/pci.c b/drivers/net/wireless/ath/ath11k/pci.c
> --- a/drivers/net/wireless/ath/ath11k/pci.c
> +++ b/drivers/net/wireless/ath/ath11k/pci.c
[ ... ]
> @@ -487,6 +491,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);

[Severity: Medium]
Is it safe to unconditionally execute this config space scan during every
bare metal probe?

The qcom_vfio_find_msi_cache() function performs a blind sequential read of
the 4KB extended config space:

        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;
        }

Reading arbitrary or reserved extended config registers without checking
capability headers violates PCIe specifications. Might this trigger
unintended side-effects on physical hardware, such as clearing
clear-on-read registers or triggering Unsupported Requests?

> +	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;

[Severity: High]
Could this early return leave MSI enabled during the remainder of the probe
phase?

Normally, the function calls ath11k_pci_msi_disable(ab_pci) shortly after
this block. By returning early, we skip disabling MSI. As documented in
ath11k_pci_clear_all_intrs():

        /* This is a WAR for PCIE Hotreset.
         * When target receive Hotreset, but will set the interrupt.
         * So when download SBL again, SBL will open Interrupt and
         * receive it, and crash immediately.
         */

If MSI remains enabled here, could the device send a spurious MSI over the
PCIe bus before the IRQ handlers are properly installed, potentially causing
host interrupts or device crashes in a virtualized environment?

> +		}
> +	}
> +
>  	ath11k_pci_msi_disable(ab_pci);
>  
>  	msi_desc = irq_get_msi_desc(ab_pci->pdev->irq);

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261011074514.515894-1-jtornosm@redhat.com?part=5

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [PATCH v2 6/7] ath12k: add PCIe link recovery retry
  2026-10-11  7:45 ` [PATCH v2 6/7] ath12k: add PCIe link recovery retry Jose Ignacio Tornos Martinez
@ 2026-10-11  8:05   ` sashiko-bot
  0 siblings, 0 replies; 17+ messages in thread
From: sashiko-bot @ 2026-10-11  8:05 UTC (permalink / raw)
  To: Jose Ignacio Tornos Martinez; +Cc: kvm, linux-pci

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] ath12k: 5-second CPU stall during surprise removal due to mdelay()
--

commit d00e14e9dd0d255c530deddfbc808bf7524a6d82
Author: Jose Ignacio Tornos Martinez <jtornosm@redhat.com>

ath12k: add PCIe link recovery retry

This commit adds a retry loop to ath12k_pci_soc_global_reset() that waits up
to 5000ms for the PCIe link to recover after a global reset. This addresses
MHI initialization failures caused by virtualization layer timing variations
in PCIe passthrough environments.

Link: https://lore.kernel.org/all/20260930140833.576941-7-jtornosm@redhat.com/

> diff --git a/drivers/net/wireless/ath/ath12k/pci.c b/drivers/net/wireless/ath/ath12k/pci.c
> --- a/drivers/net/wireless/ath/ath12k/pci.c
> +++ b/drivers/net/wireless/ath/ath12k/pci.c
[ ... ]
> @@ -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);
> +	}

[Severity: High]
Will this loop cause a 5-second CPU stall during surprise removal?

During device teardown or module unload (such as ath12k_pci_remove() ->
ath12k_core_hw_group_cleanup() -> ath12k_core_hw_group_stop() ->
ath12k_core_soc_destroy() -> ath12k_hif_power_down() ->
ath12k_pci_power_down()), if the device has been physically removed, the
memory-mapped read will continuously return 0xffffffff.

This forces the loop to exhaust all 250 retries, resulting in a 5000ms
busy-wait (mdelay) in process context, which severely disrupts system
responsiveness and can trigger soft lockup watchdogs.

The commit notes mention this was changed to use an iteration counter
for ath12k_pci_panic_handler(), but it doesn't appear that such a
handler currently exists in the driver. Even if one were added later,
stalling the CPU for 5 seconds on all teardowns and suspends might be
unacceptable.

Could this code use msleep() instead, or conditionally check
in_atomic() / is_panic before busy-waiting?

> +	if (!link_recovered) {
> +		ath12k_err(ab, "PCIe link failed to recover - device may need power cycle\n");
> +		return -ETIMEDOUT;
> +	}

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261011074514.515894-1-jtornosm@redhat.com?part=6

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [PATCH v2 3/7] vfio/pci: Add qcom-vfio-pci variant driver
  2026-10-11  7:45 ` [PATCH v2 3/7] vfio/pci: Add qcom-vfio-pci variant driver Jose Ignacio Tornos Martinez
@ 2026-10-11  8:07   ` sashiko-bot
  0 siblings, 0 replies; 17+ messages in thread
From: sashiko-bot @ 2026-10-11  8:07 UTC (permalink / raw)
  To: Jose Ignacio Tornos Martinez; +Cc: kvm, linux-pci

Thank you for your contribution! Sashiko AI review found 7 potential issue(s) to consider:
- [High] vfio/pci: stale MSI cache on VM soft-reboot
- [High] vfio/pci: endianness bug exposes corrupted MSI config to big-endian guests
- [High] pci: writing virtual MSI configuration directly to unassigned physical PCIe space triggers AER UR
- [High] vfio/pci: qcom: host kernel panic via TOCTOU in qcom_vfio_pci_ioctl
- [Medium] vfio/pci: unratelimited kernel warning DoS via SET_IRQS
- [Medium] vfio/pci: missing memory barriers for lockless MSI cache accesses
- [Medium] vfio/pci/qcom: MSI config cache bypassed by non-exact reads
--

commit a2f13f43e82ecd07210fb028cbc53419856cb4cb
Author: Jose Ignacio Tornos Martinez <jtornosm@redhat.com>
Subject: vfio/pci: Add qcom-vfio-pci variant driver

Add a VFIO variant driver for Qualcomm PCIe devices (ath11k and ath12k)
that require MSI address passthrough for VM operation. This driver caches
physical host MSI values and writes them to a designated extended config
space offset so the VM can discover them automatically.

Link: https://lore.kernel.org/all/20260930140833.576941-4-jtornosm@redhat.com/

> diff --git a/drivers/vfio/pci/qcom/main.c b/drivers/vfio/pci/qcom/main.c
> new file mode 100644
> index 0000000000000..8773661d29a10
> --- /dev/null
> +++ b/drivers/vfio/pci/qcom/main.c
[ ... ]
> +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;

[Severity: High]
Does this flag also need to be cleared when MSIs are disabled or the device
is reset?

During a VM soft reboot, QEMU typically disables MSIs (SET_IRQS with
DATA_NONE) and issues VFIO_DEVICE_RESET, but keeps the device file descriptor
open. Because qdev->msi_cached is only cleared on full device close, won't
a soft reboot leave this flag as true?

If so, the driver might skip updating the cache and serve stale host MSI
addresses to the new VM instance.

> +	qdev->msi_addr_lo = 0;
> +	qdev->msi_addr_hi = 0;
> +	qdev->msi_data = 0;
> +
> +	vfio_pci_core_close_device(core_vdev);
> +}
[ ... ]
> +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));

[Severity: Medium]
Can this condition evaluate to true for an MSI disable command?

If userspace passes VFIO_IRQ_SET_DATA_NONE and count=0, is_msi_trigger will
still evaluate to true. Later in qcom_cache_and_write_msi(), msi_first_desc()
will return NULL, triggering an unratelimited pci_warn().

Could this allow an unprivileged user to flood the host kernel log by
repeatedly disabling MSIs?

[Severity: High]
Is there a time-of-check to time-of-use (TOCTOU) race condition here despite
the pre-fetch?

The header is read via copy_from_user(), setting is_msi_trigger. Then
vfio_pci_core_ioctl() is called below, which performs its own
copy_from_user() on the same userspace memory pointer.

If userspace concurrently changes hdr.index to VFIO_PCI_INTX_IRQ_INDEX
between the two fetches, the core will configure INTx (which doesn't allocate
MSI data structures). However, qcom_vfio_pci_ioctl() will still see
is_msi_trigger == true and execute qcom_cache_and_write_msi().

Because MSI was never enabled by the core, won't __msi_lock_descs()
dereference a NULL dev->msi.data pointer and cause a host kernel panic?

> +			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;
> +}
[ ... ]
> diff --git a/drivers/vfio/pci/qcom/msi_quirk.c b/drivers/vfio/pci/qcom/msi_quirk.c
> new file mode 100644
> index 0000000000000..b717aff37ffc8
> --- /dev/null
> +++ b/drivers/vfio/pci/qcom/msi_quirk.c
[ ... ]
> +void qcom_cache_and_write_msi(struct qcom_vfio_pci_device *qdev)
> +{
[ ... ]
> +	qdev->msi_addr_lo = addr_lo;
> +	qdev->msi_addr_hi = addr_hi;
> +	qdev->msi_data = data;
> +	qdev->msi_cached = true;

[Severity: Medium]
Are memory barriers required when setting this flag?

Because this flag is read concurrently in qcom_intercept_msi_read() without
locks, can the compiler or CPU reorder the store to msi_cached before the
data stores?

If so, a lockless reader might observe msi_cached == true but read
uninitialized or stale addresses, serving garbage to the guest VM. A
smp_store_release() here and smp_load_acquire() on the read side might be
needed.

> +
> +	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);

[Severity: High]
Do these writes trigger Unsupported Requests on the physical bus?

Since qdev->msi_offset points to unassigned capability space found by
pci_find_free_ext_cap_offset(), writing to it on the physical device can
cause the hardware to return an Unsupported Request (UR) per the PCIe
specification.

With AER enabled on the host, this could trigger error interrupts, PCIe bus
resets, or host system crashes. Also, since VM reads are intercepted in
software, are these physical writes actually necessary?

> +
> +	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);
> +}
[ ... ]
> +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 (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;

[Severity: High]
Will this copy the wrong bytes on big-endian host architectures?

qdev->msi_data is assigned to tmp_val (a native u32). When count=2 is
requested, copy_to_user(buf, &tmp_val, count) will copy the first two bytes
in memory.

On big-endian hosts, won't this copy the most-significant zero-padding bytes
of the u32 rather than the actual data? This would cause the guest VM to
receive corrupted PCI configuration space data. Using cpu_to_le32() and
cpu_to_le16() before copying to userspace might be required.

> +			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;
> +	}

[Severity: Medium]
Is it intentional that non-exact read sizes bypass the cache?

If the guest VM reads this gap using unaligned, bulk, or byte-sized reads
(e.g., count=1 or count=8), the code returns -ENOTTY. This causes the VFIO
core to fall back to a physical hardware read.

Since the physical hardware has no implementation at this offset, won't it
just return 0, breaking the virtualization contract for guests that don't do
exactly 16-bit or 32-bit aligned reads?

> +
> +	if (copy_to_user(buf, &tmp_val, count))
> +		return -EFAULT;
> +
> +	return count;
> +}

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261011074514.515894-1-jtornosm@redhat.com?part=3

^ permalink raw reply	[flat|nested] 17+ 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
  2026-10-12 14:48   ` Jason Gunthorpe
  7 siblings, 1 reply; 17+ 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] 17+ messages in thread

* Re: [PATCH v2 0/7] Enable Qualcomm WiFi PCIe passthrough to VMs
  2026-10-11 18:22 ` [PATCH v2 0/7] Enable Qualcomm WiFi PCIe passthrough to VMs Jeff Johnson
@ 2026-10-12 14:48   ` Jason Gunthorpe
  0 siblings, 0 replies; 17+ messages in thread
From: Jason Gunthorpe @ 2026-10-12 14:48 UTC (permalink / raw)
  To: Jeff Johnson
  Cc: Jose Ignacio Tornos Martinez, alex, bhelgaas, jjohnson, johannes,
	mani, yishaih, skolothumtho, kevin.tian, linux-pci, kvm,
	linux-wireless, ath11k, ath12k, linux-arm-msm, linux-kernel,
	Baochen Qiang, Rameshkumar Sundaram

On Sun, Oct 11, 2026 at 11:22:26AM -0700, Jeff Johnson wrote:

> 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.

The main issue here is the driver can't use a normal MSI-X in the
normal MSI-X table.

"private" MSI registers don't work for virtualization.

Jason


^ permalink raw reply	[flat|nested] 17+ messages in thread

end of thread, other threads:[~2026-10-12 14:48 UTC | newest]

Thread overview: 17+ 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:57   ` sashiko-bot
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:59   ` sashiko-bot
2026-10-11  7:45 ` [PATCH v2 3/7] vfio/pci: Add qcom-vfio-pci variant driver Jose Ignacio Tornos Martinez
2026-10-11  8:07   ` sashiko-bot
2026-10-11  7:45 ` [PATCH v2 4/7] ath11k: add PCIe link recovery retry Jose Ignacio Tornos Martinez
2026-10-11  7:59   ` sashiko-bot
2026-10-11  7:45 ` [PATCH v2 5/7] ath11k: Use VFIO MSI cache when available Jose Ignacio Tornos Martinez
2026-10-11  8:04   ` sashiko-bot
2026-10-11  7:45 ` [PATCH v2 6/7] ath12k: add PCIe link recovery retry Jose Ignacio Tornos Martinez
2026-10-11  8:05   ` sashiko-bot
2026-10-11  7:45 ` [PATCH v2 7/7] ath12k: Use VFIO MSI cache when available Jose Ignacio Tornos Martinez
2026-10-11  8:02   ` sashiko-bot
2026-10-11 18:22 ` [PATCH v2 0/7] Enable Qualcomm WiFi PCIe passthrough to VMs Jeff Johnson
2026-10-12 14:48   ` Jason Gunthorpe

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.