mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH v3 0/8] PCI/KVM/VFIO: MSI address passthrough for Qualcomm WiFi devices
@ 2026-10-29 16:37 Jose Ignacio Tornos Martinez
  2026-10-29 16:37 ` [PATCH v3 1/8] vfio: add helper to find PCI device by vendor/device ID Jose Ignacio Tornos Martinez
                   ` (7 more replies)
  0 siblings, 8 replies; 11+ messages in thread
From: Jose Ignacio Tornos Martinez @ 2026-10-29 16:37 UTC (permalink / raw)
  To: jgg, alex, pbonzini, seanjc, bhelgaas, jjohnson
  Cc: johannes, mani, lpieralisi, kwilczynski, robh, vkuznets,
	kevin.tian, linux-pci, kvm, linux-wireless, ath11k, ath12k,
	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. These devices have embedded firmware that
programs MSI target addresses directly into device BAR registers,
bypassing the standard PCI config MSI path. The driver extracts the
addr/data from the MSI descriptor and programs the firmware's private
registers. In a VM, the driver only sees the guest's virtual APIC
address instead of the host's physical IRTE address. The firmware
programs the guest address into the BAR registers, and the resulting
MSI writes hit a stale or invalid IRTE in the host's interrupt
remapping table, causing DMAR faults:

  DMAR: DRHD: ... INTR-REMAP
  DMAR: [INTR-REMAP] ... Present field in the IRTE entry is clear

Standard VFIO MSI virtualization handles PCI config space MSI
correctly (MHI interrupts work fine), but has no visibility into
firmware-managed BAR-level MSI programming.

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 14+ months

No action from Qualcomm: Despite multiple reports [4], no vendor
solution.

The PCI reset support for these devices has already landed in mainline
(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.

v1/v2 explored passing the host MSI address through VFIO extended
config space. Jason Gunthorpe pointed out [5] that a guest-initiated
approach via KVM paravirtualization would be cleaner, avoid VFIO
internal coupling, and generalize better to other devices with
similar firmware behavior. The proposal here is a new guest module
that tries to address this by creating a custom MSI domain that
provides the physical host values instead.

v3 implements this as a 3-piece kernel-only solution:

  1. Host side (KVM + VFIO): A new KVM hypercall
     (KVM_HC_PCI_MSI_ALLOC) dispatches to a VFIO handler that reads
     the host's physical MSI address/data for the passthrough device.
     Connected via function pointer in struct kvm to avoid hard
     subsystem dependencies.

  2. Guest side (PCI controller): A new MSI IRQ domain module
     (pci-kvm-msi, taking pci-hyperv.c as a reference as Jason
     suggested) that intercepts MSI composition for matched devices.
     In order to sync host initialization with the guest driver, it
     provides kvm_pci_msi_wait_ready() to poll the hypercall until the
     host MSI is configured, and kvm_pci_msi_get_address() to read
     cached values.

  3. Driver changes (ath11k, ath12k): Guarded with
     IS_ENABLED(CONFIG_PCI_KVM_MSI_DOMAIN). Call wait_ready after
     msi_enable in power_up, then use get_address where needed.
     Zero overhead on baremetal (kvm_para_available() returns false).

The solution requires no QEMU changes. Tested with WCN6855 (ath11k),
WCN7850 (ath12k), and QCN9274 (ath12k) on host with KVM/VFIO
passthrough, all three create fully working WiFi interfaces in
the guest VM.

Patches:
  1-3: Core infrastructure (VFIO helpers, KVM handler, guest module)
  4-5: ath11k support (link retry + KVM MSI integration)
  6-7: ath12k support (link retry + KVM MSI integration)
    8: QCN9274 device support (reset quirks + KVM MSI ID)

[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] https://lore.kernel.org/all/fc6bd06f-d52b-4dee-ab1b-4bb845cc0b95@quicinc.com/
[5] https://lore.kernel.org/all/20261014150047.GO14213@ziepe.ca/

---
v3: KVM paravirtualization approach based on Jason Gunthorpe feedback.
    New hypercall + guest MSI domain module replaces VFIO variant
    driver.
    Added QCN9274 support.
v2: https://lore.kernel.org/all/20261014142606.418972-1-jtornosm@redhat.com/
v1: https://lore.kernel.org/all/20260930140833.576941-1-jtornosm@redhat.com/

Jose Ignacio Tornos Martinez (8):
  vfio: add helper to find PCI device by vendor/device ID
  KVM: add PCI MSI hypercall for VFIO passthrough
  PCI: add KVM MSI domain guest module
  ath11k: add PCIe link recovery retry
  ath11k: use KVM MSI domain for VM passthrough
  ath12k: add PCIe link recovery retry
  ath12k: use KVM MSI domain for VM passthrough
  PCI: add QCN9274 to reset quirks and KVM MSI domain


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

* [PATCH v3 1/8] vfio: add helper to find PCI device by vendor/device ID
  2026-10-29 16:37 [PATCH v3 0/8] PCI/KVM/VFIO: MSI address passthrough for Qualcomm WiFi devices Jose Ignacio Tornos Martinez
@ 2026-10-29 16:37 ` Jose Ignacio Tornos Martinez
  2026-10-29 16:37 ` [PATCH v3 2/8] KVM: add PCI MSI hypercall for VFIO passthrough Jose Ignacio Tornos Martinez
                   ` (6 subsequent siblings)
  7 siblings, 0 replies; 11+ messages in thread
From: Jose Ignacio Tornos Martinez @ 2026-10-29 16:37 UTC (permalink / raw)
  To: jgg, alex, pbonzini, seanjc, bhelgaas, jjohnson
  Cc: johannes, mani, lpieralisi, kwilczynski, robh, vkuznets,
	kevin.tian, linux-pci, kvm, linux-wireless, ath11k, ath12k,
	linux-kernel, Jose Ignacio Tornos Martinez

Add vfio_file_find_pci_device() that searches for a PCI device with
the specified vendor/device ID within a VFIO group or device file. The
function walks the group device list to find a matching PCI device, or
checks the single device for a cdev-based file.

This helper enables other subsystems (e.g. KVM PCI MSI domain) to
locate VFIO-owned PCI devices by their IDs without depending on VFIO
internals. The companion vfio_file_device() provides access to the
underlying vfio_device from a device file descriptor.

Signed-off-by: Jose Ignacio Tornos Martinez <jtornosm@redhat.com>
---
v3: new
v2: https://lore.kernel.org/all/20261011074514.515894-1-jtornosm@redhat.com/

 drivers/vfio/group.c     | 41 ++++++++++++++++++++++++++++++++++++++++
 drivers/vfio/vfio_main.c |  6 ++++++
 include/linux/vfio.h     | 10 ++++++++++
 3 files changed, 57 insertions(+)

diff --git a/drivers/vfio/group.c b/drivers/vfio/group.c
index 185f359f75d3..0b6cf90edda3 100644
--- a/drivers/vfio/group.c
+++ b/drivers/vfio/group.c
@@ -10,6 +10,7 @@
  * Author: Tom Lyon, pugs@cisco.com
  */
 
+#include <linux/pci.h>
 #include <linux/vfio.h>
 #include <linux/iommufd.h>
 #include <linux/anon_inodes.h>
@@ -894,6 +895,46 @@ bool vfio_file_has_dev(struct file *file, struct vfio_device *device)
 }
 EXPORT_SYMBOL_GPL(vfio_file_has_dev);
 
+struct vfio_device *vfio_file_find_pci_device(struct file *file,
+					      u16 vendor, u16 device_id)
+{
+	struct vfio_group *group;
+	struct vfio_device *vdev;
+	struct vfio_device *ret = NULL;
+
+	group = vfio_group_from_file(file);
+	if (group) {
+		mutex_lock(&group->device_lock);
+		list_for_each_entry(vdev, &group->device_list, group_next) {
+			struct pci_dev *pdev;
+
+			if (!dev_is_pci(vdev->dev))
+				continue;
+
+			pdev = to_pci_dev(vdev->dev);
+			if (pdev->vendor == vendor &&
+			    pdev->device == device_id) {
+				ret = vdev;
+				break;
+			}
+		}
+		mutex_unlock(&group->device_lock);
+		return ret;
+	}
+
+	vdev = vfio_file_device(file);
+	if (vdev && dev_is_pci(vdev->dev)) {
+		struct pci_dev *pdev = to_pci_dev(vdev->dev);
+
+		if (pdev->vendor == vendor &&
+		    pdev->device == device_id)
+			return vdev;
+	}
+
+	return NULL;
+}
+EXPORT_SYMBOL_GPL(vfio_file_find_pci_device);
+
 static char *vfio_devnode(const struct device *dev, umode_t *mode)
 {
 	return kasprintf(GFP_KERNEL, "vfio/%s", dev_name(dev));
diff --git a/drivers/vfio/vfio_main.c b/drivers/vfio/vfio_main.c
index 4e5d738ccf4b..a83e5d805eac 100644
--- a/drivers/vfio/vfio_main.c
+++ b/drivers/vfio/vfio_main.c
@@ -1551,6 +1551,12 @@ void vfio_file_set_kvm(struct file *vfio_file, struct file *kvm)
 }
 EXPORT_SYMBOL_GPL(vfio_file_set_kvm);
 
+struct vfio_device *vfio_file_device(struct file *file)
+{
+	return vfio_device_from_file(file);
+}
+EXPORT_SYMBOL_GPL(vfio_file_device);
+
 /*
  * Sub-module support
  */
diff --git a/include/linux/vfio.h b/include/linux/vfio.h
index 0cc91c6f96d2..a62fbf42458a 100644
--- a/include/linux/vfio.h
+++ b/include/linux/vfio.h
@@ -364,6 +364,8 @@ struct iommu_group *vfio_file_iommu_group(struct file *file);
 #if IS_ENABLED(CONFIG_VFIO_GROUP)
 bool vfio_file_is_group(struct file *file);
 bool vfio_file_has_dev(struct file *file, struct vfio_device *device);
+struct vfio_device *vfio_file_find_pci_device(struct file *file,
+					      u16 vendor, u16 device_id);
 #else
 static inline bool vfio_file_is_group(struct file *file)
 {
@@ -374,10 +376,18 @@ static inline bool vfio_file_has_dev(struct file *file, struct vfio_device *devi
 {
 	return false;
 }
+
+static inline struct vfio_device *vfio_file_find_pci_device(struct file *file,
+							    u16 vendor,
+							    u16 device_id)
+{
+	return NULL;
+}
 #endif
 bool vfio_file_is_valid(struct file *file);
 bool vfio_file_enforced_coherent(struct file *file);
 void vfio_file_set_kvm(struct file *vfio_file, struct file *kvm);
+struct vfio_device *vfio_file_device(struct file *file);
 
 #define VFIO_PIN_PAGES_MAX_ENTRIES	(PAGE_SIZE/sizeof(unsigned long))
 
-- 
2.56.0


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

* [PATCH v3 2/8] KVM: add PCI MSI hypercall for VFIO passthrough
  2026-10-29 16:37 [PATCH v3 0/8] PCI/KVM/VFIO: MSI address passthrough for Qualcomm WiFi devices Jose Ignacio Tornos Martinez
  2026-10-29 16:37 ` [PATCH v3 1/8] vfio: add helper to find PCI device by vendor/device ID Jose Ignacio Tornos Martinez
@ 2026-10-29 16:37 ` Jose Ignacio Tornos Martinez
  2026-10-29 16:58   ` Jason Gunthorpe
  2026-10-29 16:37 ` [PATCH v3 3/8] PCI: add KVM MSI domain guest module Jose Ignacio Tornos Martinez
                   ` (5 subsequent siblings)
  7 siblings, 1 reply; 11+ messages in thread
From: Jose Ignacio Tornos Martinez @ 2026-10-29 16:37 UTC (permalink / raw)
  To: jgg, alex, pbonzini, seanjc, bhelgaas, jjohnson
  Cc: johannes, mani, lpieralisi, kwilczynski, robh, vkuznets,
	kevin.tian, linux-pci, kvm, linux-wireless, ath11k, ath12k,
	linux-kernel, Jose Ignacio Tornos Martinez

Some PCI devices (e.g. Qualcomm WiFi adapters) have firmware that
programs MSI addresses directly into device BAR registers, bypassing
the standard PCI config MSI path. When these devices are passed
through to VMs via VFIO, the firmware programs the guest virtual MSI
address instead of the host physical IRTE address, causing DMAR faults
and non-functional interrupts.

Add a KVM hypercall (KVM_HC_PCI_MSI_ALLOC) that allows a guest to
request the host's physical MSI address/data for a VFIO-assigned PCI
device. The hypercall is dispatched from the x86 hypercall handler to
a VFIO backend that:

1. Finds the host PCI device matching the guest's vendor:device ID
   using vfio_file_find_pci_device() (introduced in a previous patch)
2. Reads the host MSI address/data from the device's msi_desc or
   by walking the IRQ domain compose chain
3. Returns the result to the guest via a GPA response buffer

The VFIO handler is connected to the KVM instance via a function
pointer (pci_msi_alloc) in struct kvm, set when the VFIO device is
created and cleared on release. This avoids a hard dependency between
KVM and VFIO subsystems.

When MSI is not yet enabled by QEMU, or the IRQ vector is not
configured, the handler returns success with a zeroed response,
allowing the guest to poll until the host MSI is ready.

Signed-off-by: Jose Ignacio Tornos Martinez <jtornosm@redhat.com>
---
v3: new
v2: https://lore.kernel.org/all/20261011074514.515894-1-jtornosm@redhat.com/

 arch/x86/kvm/x86.c            | 39 ++++++++++++++
 include/linux/kvm_host.h      |  3 ++
 include/uapi/linux/kvm_para.h |  1 +
 virt/kvm/vfio.c               | 97 +++++++++++++++++++++++++++++++++++
 virt/kvm/vfio.h               |  8 +++
 5 files changed, 148 insertions(+)

diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index 32563a91fba9..2f0cae07f978 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -86,6 +86,7 @@
 #include <asm/sgx.h>
 #include <asm/virt.h>
 
+#include <linux/msi.h>
 #include <clocksource/hyperv_timer.h>
 
 #define CREATE_TRACE_POINTS
@@ -7490,6 +7491,44 @@ int ____kvm_emulate_hypercall(struct kvm_vcpu *vcpu, int cpl,
 		vcpu->arch.complete_userspace_io = complete_hypercall;
 		return 0;
 	}
+	case KVM_HC_PCI_MSI_ALLOC: {
+		struct msi_msg msg = {};
+		struct {
+			u32 address_lo;
+			u32 address_hi;
+			u32 data;
+			u32 reserved;
+		} resp;
+
+		if (vcpu->kvm->pci_msi_alloc == NULL) {
+			ret = -KVM_ENOSYS;
+			break;
+		}
+
+		/*
+		 * a0 = PCI vendor:device (vendor << 16 | device)
+		 * a1 = MSI vector index
+		 * a2 = GPA of response buffer
+		 */
+		ret = vcpu->kvm->pci_msi_alloc(vcpu->kvm, a0, a1, &msg);
+		if (ret) {
+			ret = -KVM_EINVAL;
+			break;
+		}
+
+		resp.address_lo = msg.address_lo;
+		resp.address_hi = msg.address_hi;
+		resp.data = msg.data;
+		resp.reserved = 0;
+
+		if (kvm_write_guest(vcpu->kvm, a2, &resp, sizeof(resp))) {
+			ret = -KVM_EFAULT;
+			break;
+		}
+
+		ret = 0;
+		break;
+	}
 	default:
 		ret = -KVM_ENOSYS;
 		break;
diff --git a/include/linux/kvm_host.h b/include/linux/kvm_host.h
index e7fc94e2910a..4921810e9f3f 100644
--- a/include/linux/kvm_host.h
+++ b/include/linux/kvm_host.h
@@ -871,6 +871,9 @@ struct kvm {
 	struct xarray mem_attr_array;
 #endif
 	char stats_id[KVM_STATS_NAME_SIZE];
+
+	int (*pci_msi_alloc)(struct kvm *kvm, u32 devid,
+			     unsigned int vector, struct msi_msg *msg);
 };
 
 #define kvm_err(fmt, ...) \
diff --git a/include/uapi/linux/kvm_para.h b/include/uapi/linux/kvm_para.h
index 960c7e93d1a9..3c7ca359faa6 100644
--- a/include/uapi/linux/kvm_para.h
+++ b/include/uapi/linux/kvm_para.h
@@ -30,6 +30,7 @@
 #define KVM_HC_SEND_IPI		10
 #define KVM_HC_SCHED_YIELD		11
 #define KVM_HC_MAP_GPA_RANGE		12
+#define KVM_HC_PCI_MSI_ALLOC		13
 
 /*
  * hypercalls use architecture specific
diff --git a/virt/kvm/vfio.c b/virt/kvm/vfio.c
index 19548a430942..60356173e69f 100644
--- a/virt/kvm/vfio.c
+++ b/virt/kvm/vfio.c
@@ -11,7 +11,10 @@
 #include <linux/kvm_host.h>
 #include <linux/list.h>
 #include <linux/module.h>
+#include <linux/irq.h>
+#include <linux/msi.h>
 #include <linux/mutex.h>
+#include <linux/pci.h>
 #include <linux/slab.h>
 #include <linux/uaccess.h>
 #include <linux/vfio.h>
@@ -315,6 +318,8 @@ static void kvm_vfio_release(struct kvm_device *dev)
 
 	kvm_vfio_update_coherency(dev);
 
+	dev->kvm->pci_msi_alloc = NULL;
+
 	kfree(kv);
 	kfree(dev); /* alloc by kvm_ioctl_create_device, free by .release */
 }
@@ -349,6 +354,98 @@ static int kvm_vfio_create(struct kvm_device *dev, u32 type)
 	mutex_init(&kv->lock);
 
 	dev->private = kv;
+	dev->kvm->pci_msi_alloc = kvm_vfio_pci_msi_alloc;
+
+	return 0;
+}
+
+/**
+ * kvm_vfio_pci_msi_alloc - Get physical MSI address/data for a VFIO device
+ * @kvm: the VM instance
+ * @devid: PCI vendor:device packed as (vendor << 16 | device)
+ * @vector: MSI vector index
+ * @msg: output MSI message (address + data)
+ *
+ * Walks VFIO files attached to this VM, uses vfio_file_find_pci_device()
+ * to find the PCI device matching the given vendor:device ID, and returns
+ * the host's physical MSI address/data for the requested vector.
+ *
+ * Returns 0 on success, negative errno on failure.
+ */
+int kvm_vfio_pci_msi_alloc(struct kvm *kvm, u32 devid,
+			     unsigned int vector, struct msi_msg *msg)
+{
+	struct kvm_vfio_file *kvf;
+	struct kvm_vfio *kv = NULL;
+	struct vfio_device *vdev;
+	struct kvm_device *dev;
+	struct msi_desc *desc;
+	struct irq_data *irqd;
+	struct pci_dev *pdev;
+	u16 vendor = devid >> 16;
+	u16 device = devid & 0xffff;
+	int irq;
+
+	scoped_guard(mutex, &kvm->lock) {
+		list_for_each_entry(dev, &kvm->devices, vm_node) {
+			if (dev->ops == &kvm_vfio_ops) {
+				kv = dev->private;
+				break;
+			}
+		}
+	}
+
+	if (!kv)
+		return -ENODEV;
+
+	guard(mutex)(&kv->lock);
+
+	vdev = NULL;
+	list_for_each_entry(kvf, &kv->file_list, node) {
+		vdev = vfio_file_find_pci_device(kvf->file, vendor, device);
+		if (vdev)
+			break;
+	}
+
+	if (!vdev)
+		return -ENODEV;
+
+	pdev = to_pci_dev(vdev->dev);
+
+	if (!pdev->msi_enabled && !pdev->msix_enabled) {
+		memset(msg, 0, sizeof(*msg));
+		return 0;
+	}
+
+	irq = pci_irq_vector(pdev, vector);
+	if (irq <= 0) {
+		memset(msg, 0, sizeof(*msg));
+		return 0;
+	}
+
+	irqd = irq_get_irq_data(irq);
+	if (!irqd) {
+		memset(msg, 0, sizeof(*msg));
+		return 0;
+	}
+
+	desc = irq_get_msi_desc(irq);
+	if (desc) {
+		*msg = desc->msg;
+	} else {
+		struct irq_data *pos;
+
+		for (pos = irqd; pos; pos = pos->parent_data) {
+			if (pos->chip && pos->chip->irq_compose_msi_msg) {
+				pos->chip->irq_compose_msi_msg(pos, msg);
+				break;
+			}
+		}
+		if (!pos) {
+			memset(msg, 0, sizeof(*msg));
+			return 0;
+		}
+	}
 
 	return 0;
 }
diff --git a/virt/kvm/vfio.h b/virt/kvm/vfio.h
index e130a4a03530..c2237ca9ff38 100644
--- a/virt/kvm/vfio.h
+++ b/virt/kvm/vfio.h
@@ -5,6 +5,8 @@
 #ifdef CONFIG_KVM_VFIO
 int kvm_vfio_ops_init(void);
 void kvm_vfio_ops_exit(void);
+int kvm_vfio_pci_msi_alloc(struct kvm *kvm, u32 devid,
+			     unsigned int vector, struct msi_msg *msg);
 #else
 static inline int kvm_vfio_ops_init(void)
 {
@@ -13,6 +15,12 @@ static inline int kvm_vfio_ops_init(void)
 static inline void kvm_vfio_ops_exit(void)
 {
 }
+static inline int kvm_vfio_pci_msi_alloc(struct kvm *kvm, u32 devid,
+					   unsigned int vector,
+					   struct msi_msg *msg)
+{
+	return -ENODEV;
+}
 #endif
 
 #endif
-- 
2.56.0


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

* [PATCH v3 3/8] PCI: add KVM MSI domain guest module
  2026-10-29 16:37 [PATCH v3 0/8] PCI/KVM/VFIO: MSI address passthrough for Qualcomm WiFi devices Jose Ignacio Tornos Martinez
  2026-10-29 16:37 ` [PATCH v3 1/8] vfio: add helper to find PCI device by vendor/device ID Jose Ignacio Tornos Martinez
  2026-10-29 16:37 ` [PATCH v3 2/8] KVM: add PCI MSI hypercall for VFIO passthrough Jose Ignacio Tornos Martinez
@ 2026-10-29 16:37 ` Jose Ignacio Tornos Martinez
  2026-10-29 16:37 ` [PATCH v3 4/8] ath11k: add PCIe link recovery retry Jose Ignacio Tornos Martinez
                   ` (4 subsequent siblings)
  7 siblings, 0 replies; 11+ messages in thread
From: Jose Ignacio Tornos Martinez @ 2026-10-29 16:37 UTC (permalink / raw)
  To: jgg, alex, pbonzini, seanjc, bhelgaas, jjohnson
  Cc: johannes, mani, lpieralisi, kwilczynski, robh, vkuznets,
	kevin.tian, linux-pci, kvm, linux-wireless, ath11k, ath12k,
	linux-kernel, Jose Ignacio Tornos Martinez

Add a guest-side PCI MSI IRQ domain driver for KVM virtual machines
that intercepts MSI composition for devices whose firmware programs
MSI addresses directly into BAR registers.

The problem: Qualcomm WiFi devices (ath11k/ath12k) have embedded
firmware that programs MSI target addresses from values cached at
driver init time. In a VM with VFIO passthrough, the driver sees
the guest's virtual APIC address instead of the host's physical
IRTE address. When the firmware writes this guest address to BAR
registers, the resulting MSI goes to a stale or invalid IRTE,
causing DMAR faults.

The solution: This module creates a custom MSI parent IRQ domain
(modeled on pci-hyperv.c) that interposes between the PCI MSI layer
and the platform's existing MSI domain. It provides two exported
functions for use by device drivers:

  - kvm_pci_msi_wait_ready(): Polls a KVM hypercall
    (KVM_HC_PCI_MSI_ALLOC, introduced in a previous patch) until the
    host returns non-zero MSI address/data. Caches the result in
    per-vector chip_data and updates msi_desc->msg for the base
    vector.

  - kvm_pci_msi_get_address(): Returns cached host MSI address/data
    previously obtained by wait_ready. Allows drivers that read MSI
    addresses on-the-fly (like ath12k) to get the host values without
    another hypercall.

On baremetal (no KVM), kvm_para_available() returns false and
wait_ready returns 0 immediately, so drivers guarded with
IS_ENABLED(CONFIG_PCI_KVM_MSI_DOMAIN) have zero overhead.

The compose callback is maintained for completeness: if the MSI
framework recomposes after wait_ready has populated the cache, the
cached host values are returned instead of the guest virtual address.

The module matches devices by PCI ID table, attaches the custom MSI
domain before driver probe via bus notifier, and requires the
pci-kvm-msi module to load before the wireless drivers via
MODULE_SOFTDEP in the driver patches.

Signed-off-by: Jose Ignacio Tornos Martinez <jtornosm@redhat.com>
---
v3: new
v2: https://lore.kernel.org/all/20261011074514.515894-1-jtornosm@redhat.com/

 drivers/pci/controller/Kconfig       |  12 +
 drivers/pci/controller/Makefile      |   1 +
 drivers/pci/controller/pci-kvm-msi.c | 437 +++++++++++++++++++++++++++
 include/linux/pci-kvm-msi.h          |  13 +
 4 files changed, 463 insertions(+)
 create mode 100644 drivers/pci/controller/pci-kvm-msi.c
 create mode 100644 include/linux/pci-kvm-msi.h

diff --git a/drivers/pci/controller/Kconfig b/drivers/pci/controller/Kconfig
index d246bbe37948..cdf65d1c2674 100644
--- a/drivers/pci/controller/Kconfig
+++ b/drivers/pci/controller/Kconfig
@@ -248,6 +248,18 @@ config PCI_HYPERV_INTERFACE
 	  drivers to have a common interface with the Hyper-V PCI frontend
 	  driver.
 
+config PCI_KVM_MSI_DOMAIN
+	tristate "KVM PCI passthrough MSI domain"
+	depends on X86_64 && KVM_GUEST && PCI_MSI
+	select IRQ_MSI_LIB
+	help
+	  Provides physical MSI address/data to PCI devices in KVM guests
+	  that need them for firmware-managed interrupts. Required for
+	  passthrough of devices like Qualcomm WiFi where firmware programs
+	  MSI addresses directly.
+
+	  If unsure, say N.
+
 config PCI_TEGRA
 	tristate "NVIDIA Tegra PCIe controller"
 	depends on ARCH_TEGRA || COMPILE_TEST
diff --git a/drivers/pci/controller/Makefile b/drivers/pci/controller/Makefile
index d478743b5142..4f10edae0f92 100644
--- a/drivers/pci/controller/Makefile
+++ b/drivers/pci/controller/Makefile
@@ -4,6 +4,7 @@ obj-$(CONFIG_PCI_FTPCI100) += pci-ftpci100.o
 obj-$(CONFIG_PCI_IXP4XX) += pci-ixp4xx.o
 obj-$(CONFIG_PCI_HYPERV) += pci-hyperv.o
 obj-$(CONFIG_PCI_HYPERV_INTERFACE) += pci-hyperv-intf.o
+obj-$(CONFIG_PCI_KVM_MSI_DOMAIN) += pci-kvm-msi.o
 obj-$(CONFIG_PCI_MVEBU) += pci-mvebu.o
 obj-$(CONFIG_PCI_AARDVARK) += pci-aardvark.o
 obj-$(CONFIG_PCI_TEGRA) += pci-tegra.o
diff --git a/drivers/pci/controller/pci-kvm-msi.c b/drivers/pci/controller/pci-kvm-msi.c
new file mode 100644
index 000000000000..e2b0a4114aa8
--- /dev/null
+++ b/drivers/pci/controller/pci-kvm-msi.c
@@ -0,0 +1,437 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// SPDX-FileCopyrightText: Copyright Red Hat
+/*
+ * KVM PCI MSI domain driver
+ *
+ * Provides physical MSI address/data to PCI devices in KVM guests that
+ * need them for firmware-managed interrupts (e.g., Qualcomm WiFi devices
+ * where firmware programs MSI addresses directly).
+ *
+ * Modeled on pci-hyperv.c MSI domain. Instead of VMbus messages, uses
+ * KVM hypercalls to request physical MSI addresses from the host.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/pci.h>
+#include <linux/pci-kvm-msi.h>
+#include <linux/pci_ids.h>
+#include <linux/msi.h>
+#include <linux/irq.h>
+#include <linux/irqdomain.h>
+#include <linux/kvm_para.h>
+#include <linux/irqchip/irq-msi-lib.h>
+#include <asm/hw_irq.h>
+#include <asm/pgtable_types.h>
+
+struct kvm_msi_response {
+	u32 address_lo;
+	u32 address_hi;
+	u32 data;
+	u32 reserved;
+};
+
+struct kvm_msi_irq_data {
+	struct msi_msg msg;
+	u32 devid;
+	unsigned int vector;
+	bool cached;
+};
+
+#define KVM_MSI_POLL_INTERVAL_MS	10
+
+static struct irq_domain *kvm_msi_domain;
+
+struct kvm_msi_ctx {
+	struct pci_dev *pdev;
+	struct irq_domain *domain;
+	u32 devid;
+	unsigned int first_virq;
+	unsigned int nr_irqs;
+};
+
+static struct kvm_msi_ctx kvm_msi_ctx;
+
+int kvm_pci_msi_wait_ready(struct pci_dev *pdev, unsigned long timeout_ms)
+{
+	struct kvm_msi_response resp = {};
+	struct kvm_msi_irq_data *kvm_data;
+	unsigned int max_retries;
+	struct irq_data *irqd;
+	struct msi_desc *desc;
+	unsigned int retries;
+	long hret;
+	int i;
+
+	if (!kvm_para_available())
+		return 0;
+
+	if (!kvm_msi_domain)
+		return 0;
+
+	if (!pdev || !kvm_msi_ctx.pdev || kvm_msi_ctx.pdev != pdev)
+		return 0;
+
+	max_retries = timeout_ms / KVM_MSI_POLL_INTERVAL_MS;
+	if (!max_retries)
+		max_retries = 1;
+
+	for (retries = 0; retries < max_retries; retries++) {
+		memset(&resp, 0, sizeof(resp));
+		hret = kvm_hypercall3(KVM_HC_PCI_MSI_ALLOC,
+				      kvm_msi_ctx.devid, 0,
+				      slow_virt_to_phys(&resp));
+
+		if (hret == 0 && resp.address_lo != 0)
+			goto ready;
+
+		msleep(KVM_MSI_POLL_INTERVAL_MS);
+	}
+
+	pr_err("kvm-pci-msi: %s wait timed out after %lu ms\n",
+	       pci_name(pdev), timeout_ms);
+	return -ETIMEDOUT;
+
+ready:
+	pr_info("kvm-pci-msi: %s host addr=%08x:%08x data=%08x\n",
+		pci_name(pdev), resp.address_hi, resp.address_lo,
+		resp.data);
+
+	for (i = 0; i < kvm_msi_ctx.nr_irqs; i++) {
+		irqd = irq_domain_get_irq_data(kvm_msi_ctx.domain,
+						kvm_msi_ctx.first_virq + i);
+		if (!irqd)
+			continue;
+		kvm_data = irqd->chip_data;
+		if (!kvm_data)
+			continue;
+
+		kvm_data->msg.address_lo = resp.address_lo;
+		kvm_data->msg.address_hi = resp.address_hi;
+		kvm_data->msg.data = resp.data + i;
+		kvm_data->cached = true;
+	}
+
+	desc = irq_get_msi_desc(pdev->irq);
+	if (desc) {
+		desc->msg.address_lo = resp.address_lo;
+		desc->msg.address_hi = resp.address_hi;
+		desc->msg.data = resp.data;
+	}
+
+	return 1;
+}
+EXPORT_SYMBOL_GPL(kvm_pci_msi_wait_ready);
+
+int kvm_pci_msi_get_address(struct pci_dev *pdev, u32 *addr_lo, u32 *addr_hi,
+			     u32 *data)
+{
+	struct kvm_msi_irq_data *kvm_data;
+	struct irq_data *irqd;
+
+	if (!kvm_msi_domain)
+		return 0;
+
+	if (!pdev || !kvm_msi_ctx.pdev || kvm_msi_ctx.pdev != pdev)
+		return 0;
+
+	irqd = irq_domain_get_irq_data(kvm_msi_ctx.domain,
+					kvm_msi_ctx.first_virq);
+	if (!irqd)
+		return 0;
+
+	kvm_data = irqd->chip_data;
+	if (!kvm_data || !kvm_data->cached)
+		return 0;
+
+	*addr_lo = kvm_data->msg.address_lo;
+	*addr_hi = kvm_data->msg.address_hi;
+	if (data)
+		*data = kvm_data->msg.data;
+
+	return 1;
+}
+EXPORT_SYMBOL_GPL(kvm_pci_msi_get_address);
+
+static void kvm_parent_compose_msi_msg(struct irq_data *data,
+				       struct msi_msg *msg)
+{
+	struct irq_data *pos;
+
+	for (pos = data->parent_data; pos; pos = pos->parent_data) {
+		if (pos->chip && pos->chip->irq_compose_msi_msg) {
+			pos->chip->irq_compose_msi_msg(pos, msg);
+			return;
+		}
+	}
+	memset(msg, 0, sizeof(*msg));
+}
+
+static void kvm_compose_msi_msg(struct irq_data *data, struct msi_msg *msg)
+{
+	struct kvm_msi_irq_data *kvm_data = data->chip_data;
+
+	if (kvm_data && kvm_data->cached) {
+		*msg = kvm_data->msg;
+		return;
+	}
+
+	kvm_parent_compose_msi_msg(data, msg);
+}
+
+static struct irq_chip kvm_msi_irq_chip = {
+	.name			= "KVM-MSI",
+	.irq_compose_msi_msg	= kvm_compose_msi_msg,
+	.irq_set_affinity	= irq_chip_set_affinity_parent,
+	.irq_ack		= irq_chip_ack_parent,
+};
+
+static int kvm_pcie_domain_alloc(struct irq_domain *d, unsigned int virq,
+				 unsigned int nr_irqs, void *arg)
+{
+	struct irq_alloc_info *info = arg;
+	struct kvm_msi_irq_data *kvm_data;
+	struct msi_desc *desc;
+	struct pci_dev *pdev;
+	u32 devid;
+	int ret;
+	int i;
+
+	ret = irq_domain_alloc_irqs_parent(d, virq, nr_irqs, arg);
+	if (ret < 0)
+		return ret;
+
+	desc = info->desc;
+	if (!desc || !desc->dev || !dev_is_pci(desc->dev)) {
+		ret = -EINVAL;
+		goto err_free_parent;
+	}
+
+	pdev = to_pci_dev(desc->dev);
+	devid = ((u32)pdev->vendor << 16) | pdev->device;
+
+	for (i = 0; i < nr_irqs; i++) {
+		kvm_data = kzalloc_obj(*kvm_data);
+		if (!kvm_data) {
+			ret = -ENOMEM;
+			goto err_free_data;
+		}
+
+		kvm_data->devid = devid;
+		kvm_data->vector = desc->msi_index + i;
+
+		irq_domain_set_info(d, virq + i, virq + i,
+				    &kvm_msi_irq_chip, kvm_data,
+				    handle_edge_irq, NULL, "KVM-MSI");
+	}
+
+	kvm_msi_ctx.pdev = pdev;
+	kvm_msi_ctx.devid = devid;
+	kvm_msi_ctx.first_virq = virq;
+	kvm_msi_ctx.nr_irqs = nr_irqs;
+	kvm_msi_ctx.domain = d;
+
+	return 0;
+
+err_free_data:
+	for (i--; i >= 0; i--) {
+		kvm_data = irq_data_get_irq_chip_data(
+				irq_domain_get_irq_data(d, virq + i));
+		kfree(kvm_data);
+	}
+err_free_parent:
+	irq_domain_free_irqs_parent(d, virq, nr_irqs);
+	return ret;
+}
+
+static void kvm_pcie_domain_free(struct irq_domain *d, unsigned int virq,
+				 unsigned int nr_irqs)
+{
+	struct kvm_msi_irq_data *kvm_data;
+	int i;
+
+	if (kvm_msi_ctx.first_virq == virq) {
+		kvm_msi_ctx.pdev = NULL;
+		kvm_msi_ctx.nr_irqs = 0;
+	}
+
+	for (i = 0; i < nr_irqs; i++) {
+		kvm_data = irq_data_get_irq_chip_data(
+				irq_domain_get_irq_data(d, virq + i));
+		kfree(kvm_data);
+	}
+	irq_domain_free_irqs_top(d, virq, nr_irqs);
+}
+
+static const struct irq_domain_ops kvm_pcie_domain_ops = {
+	.alloc	= kvm_pcie_domain_alloc,
+	.free	= kvm_pcie_domain_free,
+};
+
+static bool kvm_pcie_init_dev_msi_info(struct device *dev,
+				       struct irq_domain *domain,
+				       struct irq_domain *real_parent,
+				       struct msi_domain_info *info)
+{
+	struct irq_chip *chip = info->chip;
+
+	if (!msi_lib_init_dev_msi_info(dev, domain, real_parent, info)) {
+		pr_err("kvm-pci-msi: msi_lib_init_dev_msi_info failed for %s\n",
+		       dev_name(dev));
+		return false;
+	}
+
+	info->ops->msi_prepare = pci_msi_prepare;
+
+	chip->irq_set_affinity = irq_chip_set_affinity_parent;
+	chip->irq_retrigger = irq_chip_retrigger_hierarchy;
+	chip->flags |= IRQCHIP_MOVE_DEFERRED;
+
+	return true;
+}
+
+static const struct msi_parent_ops kvm_pcie_msi_parent_ops = {
+	.required_flags		= MSI_FLAG_USE_DEF_DOM_OPS |
+				  MSI_FLAG_USE_DEF_CHIP_OPS,
+	.supported_flags	= MSI_GENERIC_FLAGS_MASK |
+				  MSI_FLAG_PCI_MSIX,
+	.bus_select_token	= DOMAIN_BUS_PCI_MSI,
+	.chip_flags		= MSI_CHIP_FLAG_SET_ACK,
+	.init_dev_msi_info	= kvm_pcie_init_dev_msi_info,
+};
+
+static int kvm_msi_create_domain(struct pci_dev *ref_dev)
+{
+	struct irq_domain *parent;
+	struct irq_domain_info info = {
+		.ops		= &kvm_pcie_domain_ops,
+	};
+
+	parent = dev_get_msi_domain(&ref_dev->dev);
+	if (!parent)
+		parent = dev_get_msi_domain(&ref_dev->bus->dev);
+	if (!parent) {
+		dev_err(&ref_dev->dev, "no MSI domain found for parent\n");
+		return -ENODEV;
+	}
+
+	info.parent = parent;
+	info.fwnode = irq_domain_alloc_named_fwnode("KVM-PCI-MSI");
+	if (!info.fwnode)
+		return -ENOMEM;
+
+	kvm_msi_domain = msi_create_parent_irq_domain(&info,
+						       &kvm_pcie_msi_parent_ops);
+	if (!kvm_msi_domain) {
+		pr_err("kvm-pci-msi: failed to create MSI domain\n");
+		irq_domain_free_fwnode(info.fwnode);
+		return -ENODEV;
+	}
+
+	return 0;
+}
+
+static const struct pci_device_id kvm_msi_devids[] = {
+	{ PCI_DEVICE(PCI_VENDOR_ID_QCOM, 0x1103) },	/* WCN6855 */
+	{ PCI_DEVICE(PCI_VENDOR_ID_QCOM, 0x1107) },	/* WCN7850 */
+	{ }
+};
+
+static void kvm_msi_attach_device(struct pci_dev *pdev)
+{
+	if (pdev->driver) {
+		dev_warn(&pdev->dev,
+			 "kvm-pci-msi: driver already bound (%s), unbind/rebind needed\n",
+			 pdev->driver->name);
+		return;
+	}
+
+	dev_info(&pdev->dev,
+		 "kvm-pci-msi: attaching KVM MSI domain [%04x:%04x]\n",
+		 pdev->vendor, pdev->device);
+	dev_set_msi_domain(&pdev->dev, kvm_msi_domain);
+}
+
+static int kvm_msi_bus_notify(struct notifier_block *nb, unsigned long action,
+			      void *data)
+{
+	struct pci_dev *pdev;
+
+	if (action != BUS_NOTIFY_ADD_DEVICE)
+		return NOTIFY_DONE;
+
+	pdev = to_pci_dev(data);
+	if (!pci_match_id(kvm_msi_devids, pdev))
+		return NOTIFY_DONE;
+
+	if (!kvm_msi_domain)
+		return NOTIFY_DONE;
+
+	kvm_msi_attach_device(pdev);
+	return NOTIFY_OK;
+}
+
+static struct notifier_block kvm_msi_nb = {
+	.notifier_call = kvm_msi_bus_notify,
+};
+
+static int __init kvm_pci_msi_init(void)
+{
+	struct pci_dev *pdev = NULL;
+	struct pci_dev *first = NULL;
+	int ret;
+
+	if (!kvm_para_available())
+		return -ENODEV;
+
+	for_each_pci_dev(pdev) {
+		if (pci_match_id(kvm_msi_devids, pdev)) {
+			first = pdev;
+			break;
+		}
+	}
+
+	if (!first) {
+		pr_info("kvm-pci-msi: no matching devices found\n");
+		return -ENODEV;
+	}
+
+	ret = kvm_msi_create_domain(first);
+	pci_dev_put(first);
+	if (ret)
+		return ret;
+
+	pdev = NULL;
+	for_each_pci_dev(pdev) {
+		if (pci_match_id(kvm_msi_devids, pdev))
+			kvm_msi_attach_device(pdev);
+	}
+
+	bus_register_notifier(&pci_bus_type, &kvm_msi_nb);
+
+	pr_info("kvm-pci-msi: ready, domain created for %zu device types\n",
+		ARRAY_SIZE(kvm_msi_devids) - 1);
+	return 0;
+}
+
+static void __exit kvm_pci_msi_exit(void)
+{
+	struct fwnode_handle *fwnode;
+
+	bus_unregister_notifier(&pci_bus_type, &kvm_msi_nb);
+
+	if (kvm_msi_domain) {
+		fwnode = kvm_msi_domain->fwnode;
+		irq_domain_remove(kvm_msi_domain);
+		irq_domain_free_fwnode(fwnode);
+		kvm_msi_domain = NULL;
+	}
+}
+
+module_init(kvm_pci_msi_init);
+module_exit(kvm_pci_msi_exit);
+
+MODULE_DESCRIPTION("KVM PCI MSI passthrough domain driver");
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Jose Ignacio Tornos Martinez <jtornosm@redhat.com>");
diff --git a/include/linux/pci-kvm-msi.h b/include/linux/pci-kvm-msi.h
new file mode 100644
index 000000000000..cc615e913a3b
--- /dev/null
+++ b/include/linux/pci-kvm-msi.h
@@ -0,0 +1,13 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/* SPDX-FileCopyrightText: Copyright Red Hat */
+
+#ifndef _LINUX_PCI_KVM_MSI_H
+#define _LINUX_PCI_KVM_MSI_H
+
+#include <linux/pci.h>
+
+int kvm_pci_msi_wait_ready(struct pci_dev *pdev, unsigned long timeout_ms);
+int kvm_pci_msi_get_address(struct pci_dev *pdev, u32 *addr_lo, u32 *addr_hi,
+			     u32 *data);
+
+#endif /* _LINUX_PCI_KVM_MSI_H */
-- 
2.56.0


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

* [PATCH v3 4/8] ath11k: add PCIe link recovery retry
  2026-10-29 16:37 [PATCH v3 0/8] PCI/KVM/VFIO: MSI address passthrough for Qualcomm WiFi devices Jose Ignacio Tornos Martinez
                   ` (2 preceding siblings ...)
  2026-10-29 16:37 ` [PATCH v3 3/8] PCI: add KVM MSI domain guest module Jose Ignacio Tornos Martinez
@ 2026-10-29 16:37 ` Jose Ignacio Tornos Martinez
  2026-10-29 16:37 ` [PATCH v3 5/8] ath11k: use KVM MSI domain for VM passthrough Jose Ignacio Tornos Martinez
                   ` (3 subsequent siblings)
  7 siblings, 0 replies; 11+ messages in thread
From: Jose Ignacio Tornos Martinez @ 2026-10-29 16:37 UTC (permalink / raw)
  To: jgg, alex, pbonzini, seanjc, bhelgaas, jjohnson
  Cc: johannes, mani, lpieralisi, kwilczynski, robh, vkuznets,
	kevin.tian, linux-pci, kvm, linux-wireless, ath11k, ath12k,
	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>
---
v3: no modification
v2: https://lore.kernel.org/all/20261011074514.515894-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 a163168f3617..7e29ebaf9773 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);
@@ -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);
+	}
+	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)
@@ -377,8 +393,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) {
@@ -391,8 +409,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)
@@ -822,7 +844,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.56.0


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

* [PATCH v3 5/8] ath11k: use KVM MSI domain for VM passthrough
  2026-10-29 16:37 [PATCH v3 0/8] PCI/KVM/VFIO: MSI address passthrough for Qualcomm WiFi devices Jose Ignacio Tornos Martinez
                   ` (3 preceding siblings ...)
  2026-10-29 16:37 ` [PATCH v3 4/8] ath11k: add PCIe link recovery retry Jose Ignacio Tornos Martinez
@ 2026-10-29 16:37 ` Jose Ignacio Tornos Martinez
  2026-10-29 17:00   ` Jason Gunthorpe
  2026-10-29 16:37 ` [PATCH v3 6/8] ath12k: add PCIe link recovery retry Jose Ignacio Tornos Martinez
                   ` (2 subsequent siblings)
  7 siblings, 1 reply; 11+ messages in thread
From: Jose Ignacio Tornos Martinez @ 2026-10-29 16:37 UTC (permalink / raw)
  To: jgg, alex, pbonzini, seanjc, bhelgaas, jjohnson
  Cc: johannes, mani, lpieralisi, kwilczynski, robh, vkuznets,
	kevin.tian, linux-pci, kvm, linux-wireless, ath11k, ath12k,
	linux-kernel, Jose Ignacio Tornos Martinez

The Qualcomm ath11k firmware programs MSI addresses directly into
device BAR registers for CE/SRNG ring interrupt delivery, bypassing
the standard PCI config MSI path managed by QEMU/VFIO. In a VM, the
firmware receives the guest virtual APIC address instead of the host
IRTE address, causing DMAR faults and non-functional interrupts.

When CONFIG_PCI_KVM_MSI_DOMAIN is enabled, use the KVM PCI MSI domain
synchronization functions to obtain the correct host physical MSI
address after MSI is enabled in power_up. The kvm_pci_msi_wait_ready()
function performs a KVM hypercall to retrieve the host MSI address,
and kvm_pci_msi_get_address() reads the cached values to update the
driver's internal MSI address/data used for ring configuration.

This placement (after msi_enable in power_up) ensures:
- Host MSI is enabled and stable (QEMU has completed setup)
- No MSI state transitions are in progress
- CE/SRNG rings have not been programmed yet
- The function runs on every power_up, including recovery

On baremetal, if CONFIG_PCI_KVM_MSI_DOMAIN is not enabled the code is
compiled out and the behavior is identical to the original. If
CONFIG_PCI_KVM_MSI_DOMAIN is enabled (e.g. distribution kernels), the
KVM MSI functions detect baremetal at runtime and return immediately,
preserving the original behavior with no side effects.

Signed-off-by: Jose Ignacio Tornos Martinez <jtornosm@redhat.com>
---
v3: new
v2: https://lore.kernel.org/all/20261011074514.515894-1-jtornosm@redhat.com/

 drivers/net/wireless/ath/ath11k/pci.c | 13 +++++++++++++
 1 file changed, 13 insertions(+)

diff --git a/drivers/net/wireless/ath/ath11k/pci.c b/drivers/net/wireless/ath/ath11k/pci.c
index 7e29ebaf9773..31889e5caa8d 100644
--- a/drivers/net/wireless/ath/ath11k/pci.c
+++ b/drivers/net/wireless/ath/ath11k/pci.c
@@ -7,6 +7,9 @@
 #include <linux/module.h>
 #include <linux/msi.h>
 #include <linux/pci.h>
+#if IS_ENABLED(CONFIG_PCI_KVM_MSI_DOMAIN)
+#include <linux/pci-kvm-msi.h>
+#endif
 #include <linux/of.h>
 #include <linux/time.h>
 #include <linux/vmalloc.h>
@@ -855,6 +858,13 @@ static int ath11k_pci_power_up(struct ath11k_base *ab)
 
 	ath11k_pci_msi_enable(ab_pci);
 
+#if IS_ENABLED(CONFIG_PCI_KVM_MSI_DOMAIN)
+	if (kvm_pci_msi_wait_ready(ab_pci->pdev, 500) > 0)
+		kvm_pci_msi_get_address(ab_pci->pdev, &ab->pci.msi.addr_lo,
+					&ab->pci.msi.addr_hi,
+					&ab->pci.msi.ep_base_data);
+#endif
+
 	ret = ath11k_mhi_start(ab_pci);
 	if (ret) {
 		ath11k_err(ab, "failed to start mhi: %d\n", ret);
@@ -1350,6 +1360,9 @@ module_exit(ath11k_pci_exit);
 
 MODULE_DESCRIPTION("Driver support for Qualcomm Technologies PCIe 802.11ax WLAN devices");
 MODULE_LICENSE("Dual BSD/GPL");
+#if IS_ENABLED(CONFIG_PCI_KVM_MSI_DOMAIN)
+MODULE_SOFTDEP("pre: pci-kvm-msi");
+#endif
 
 /* firmware files */
 MODULE_FIRMWARE(ATH11K_FW_DIR "/QCA6390/hw2.0/*");
-- 
2.56.0


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

* [PATCH v3 6/8] ath12k: add PCIe link recovery retry
  2026-10-29 16:37 [PATCH v3 0/8] PCI/KVM/VFIO: MSI address passthrough for Qualcomm WiFi devices Jose Ignacio Tornos Martinez
                   ` (4 preceding siblings ...)
  2026-10-29 16:37 ` [PATCH v3 5/8] ath11k: use KVM MSI domain for VM passthrough Jose Ignacio Tornos Martinez
@ 2026-10-29 16:37 ` Jose Ignacio Tornos Martinez
  2026-10-29 16:37 ` [PATCH v3 7/8] ath12k: use KVM MSI domain for VM passthrough Jose Ignacio Tornos Martinez
  2026-10-29 16:37 ` [PATCH v3 8/8] PCI: add QCN9274 to reset quirks and KVM MSI domain Jose Ignacio Tornos Martinez
  7 siblings, 0 replies; 11+ messages in thread
From: Jose Ignacio Tornos Martinez @ 2026-10-29 16:37 UTC (permalink / raw)
  To: jgg, alex, pbonzini, seanjc, bhelgaas, jjohnson
  Cc: johannes, mani, lpieralisi, kwilczynski, robh, vkuznets,
	kevin.tian, linux-pci, kvm, linux-wireless, ath11k, ath12k,
	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>
---
v3: no modification
v2: https://lore.kernel.org/all/20261011074514.515894-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 12bbb75570f1..f7879fb48d32 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.56.0


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

* [PATCH v3 7/8] ath12k: use KVM MSI domain for VM passthrough
  2026-10-29 16:37 [PATCH v3 0/8] PCI/KVM/VFIO: MSI address passthrough for Qualcomm WiFi devices Jose Ignacio Tornos Martinez
                   ` (5 preceding siblings ...)
  2026-10-29 16:37 ` [PATCH v3 6/8] ath12k: add PCIe link recovery retry Jose Ignacio Tornos Martinez
@ 2026-10-29 16:37 ` Jose Ignacio Tornos Martinez
  2026-10-29 16:37 ` [PATCH v3 8/8] PCI: add QCN9274 to reset quirks and KVM MSI domain Jose Ignacio Tornos Martinez
  7 siblings, 0 replies; 11+ messages in thread
From: Jose Ignacio Tornos Martinez @ 2026-10-29 16:37 UTC (permalink / raw)
  To: jgg, alex, pbonzini, seanjc, bhelgaas, jjohnson
  Cc: johannes, mani, lpieralisi, kwilczynski, robh, vkuznets,
	kevin.tian, linux-pci, kvm, linux-wireless, ath11k, ath12k,
	linux-kernel, Jose Ignacio Tornos Martinez

The Qualcomm ath12k firmware programs MSI addresses directly into
device BAR registers for CE/SRNG ring interrupt delivery, bypassing
the standard PCI config MSI path managed by QEMU/VFIO. In a VM, the
firmware receives the guest virtual APIC address instead of the host
IRTE address, causing DMAR faults and non-functional interrupts.

When CONFIG_PCI_KVM_MSI_DOMAIN is enabled, use the KVM PCI MSI domain
synchronization functions:

1. In power_up, after msi_enable, call kvm_pci_msi_wait_ready() to
   retrieve and cache the host physical MSI address via KVM hypercall.

2. In ath12k_pci_get_msi_address(), call kvm_pci_msi_get_address() to
   return the cached host MSI address instead of reading from PCI
   config (which contains the guest virtual address in a VM).

Unlike ath11k which caches MSI address once during power_up, ath12k
reads MSI address on-the-fly via get_msi_address() each time rings
are configured. The kvm_pci_msi_get_address() function provides the
cached host values from a previous successful wait_ready call.

On baremetal, if CONFIG_PCI_KVM_MSI_DOMAIN is not enabled the code is
compiled out and the behavior is identical to the original. If
CONFIG_PCI_KVM_MSI_DOMAIN is enabled (e.g. distribution kernels), the
KVM MSI functions detect baremetal at runtime and return immediately,
preserving the original behavior with no side effects.

Signed-off-by: Jose Ignacio Tornos Martinez <jtornosm@redhat.com>
---
v3: new
v2: https://lore.kernel.org/all/20261011074514.515894-1-jtornosm@redhat.com/

 drivers/net/wireless/ath/ath12k/pci.c | 15 +++++++++++++++
 1 file changed, 15 insertions(+)

diff --git a/drivers/net/wireless/ath/ath12k/pci.c b/drivers/net/wireless/ath/ath12k/pci.c
index f7879fb48d32..dbd960f88d38 100644
--- a/drivers/net/wireless/ath/ath12k/pci.c
+++ b/drivers/net/wireless/ath/ath12k/pci.c
@@ -8,6 +8,9 @@
 #include <linux/interrupt.h>
 #include <linux/msi.h>
 #include <linux/pci.h>
+#if IS_ENABLED(CONFIG_PCI_KVM_MSI_DOMAIN)
+#include <linux/pci-kvm-msi.h>
+#endif
 #include <linux/time.h>
 #include <linux/vmalloc.h>
 
@@ -1082,6 +1085,11 @@ 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 IS_ENABLED(CONFIG_PCI_KVM_MSI_DOMAIN)
+	if (kvm_pci_msi_get_address(pci_dev, msi_addr_lo, msi_addr_hi, NULL))
+		return;
+#endif
+
 	pci_read_config_dword(pci_dev, pci_dev->msi_cap + PCI_MSI_ADDRESS_LO,
 			      msi_addr_lo);
 
@@ -1485,6 +1493,10 @@ int ath12k_pci_power_up(struct ath12k_base *ab)
 
 	ath12k_pci_msi_enable(ab_pci);
 
+#if IS_ENABLED(CONFIG_PCI_KVM_MSI_DOMAIN)
+	kvm_pci_msi_wait_ready(ab_pci->pdev, 500);
+#endif
+
 	if (ath12k_fw_feature_supported(ab, ATH12K_FW_FEATURE_MULTI_QRTR_ID))
 		ath12k_pci_update_qrtr_node_id(ab);
 
@@ -1886,3 +1898,6 @@ EXPORT_SYMBOL(ath12k_pci_unregister_driver);
 /* firmware files */
 MODULE_FIRMWARE(ATH12K_FW_DIR "/QCN9274/hw2.0/*");
 MODULE_FIRMWARE(ATH12K_FW_DIR "/WCN7850/hw2.0/*");
+#if IS_ENABLED(CONFIG_PCI_KVM_MSI_DOMAIN)
+MODULE_SOFTDEP("pre: pci-kvm-msi");
+#endif
-- 
2.56.0


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

* [PATCH v3 8/8] PCI: add QCN9274 to reset quirks and KVM MSI domain
  2026-10-29 16:37 [PATCH v3 0/8] PCI/KVM/VFIO: MSI address passthrough for Qualcomm WiFi devices Jose Ignacio Tornos Martinez
                   ` (6 preceding siblings ...)
  2026-10-29 16:37 ` [PATCH v3 7/8] ath12k: use KVM MSI domain for VM passthrough Jose Ignacio Tornos Martinez
@ 2026-10-29 16:37 ` Jose Ignacio Tornos Martinez
  7 siblings, 0 replies; 11+ messages in thread
From: Jose Ignacio Tornos Martinez @ 2026-10-29 16:37 UTC (permalink / raw)
  To: jgg, alex, pbonzini, seanjc, bhelgaas, jjohnson
  Cc: johannes, mani, lpieralisi, kwilczynski, robh, vkuznets,
	kevin.tian, linux-pci, kvm, linux-wireless, ath11k, ath12k,
	linux-kernel, Jose Ignacio Tornos Martinez

In order to have Qualcomm QCN9274 (PCI ID 17cb:1109) completely
operational from VM environments, add to:

1. quirk_no_bus_reset: Disable broken bus reset, as with WCN6855 and
   WCN7850.

2. pci_dev_specific_reset_methods: Use the existing
   reset_qualcomm_wlan function. Unlike WCN7850, the QCN9274 is not
   initialized at all without a valid device-specific reset method.

3. kvm_msi_devids in pci-kvm-msi: Enable KVM MSI domain support for
   VFIO passthrough to VMs.

Signed-off-by: Jose Ignacio Tornos Martinez <jtornosm@redhat.com>
---
v3: new
v2: https://lore.kernel.org/all/20261011074514.515894-1-jtornosm@redhat.com/

 drivers/pci/controller/pci-kvm-msi.c | 1 +
 drivers/pci/quirks.c                 | 2 ++
 2 files changed, 3 insertions(+)

diff --git a/drivers/pci/controller/pci-kvm-msi.c b/drivers/pci/controller/pci-kvm-msi.c
index e935e5d9846d..b008e73fedce 100644
--- a/drivers/pci/controller/pci-kvm-msi.c
+++ b/drivers/pci/controller/pci-kvm-msi.c
@@ -336,6 +336,7 @@ static int kvm_msi_create_domain(struct pci_dev *ref_dev)
 static const struct pci_device_id kvm_msi_devids[] = {
 	{ PCI_DEVICE(PCI_VENDOR_ID_QCOM, 0x1103) },	/* WCN6855 */
 	{ PCI_DEVICE(PCI_VENDOR_ID_QCOM, 0x1107) },	/* WCN7850 */
+	{ PCI_DEVICE(PCI_VENDOR_ID_QCOM, 0x1109) },	/* QCN9274 */
 	{ }
 };
 
diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c
index 7f1f47ea2ee4..89d82675a76c 100644
--- a/drivers/pci/quirks.c
+++ b/drivers/pci/quirks.c
@@ -3818,6 +3818,7 @@ DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_ATHEROS, 0x0034, quirk_no_bus_reset);
 DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_ATHEROS, 0x003e, quirk_no_bus_reset);
 DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_QCOM, 0x1103, quirk_no_bus_reset); /* WCN6855 */
 DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_QCOM, 0x1107, quirk_no_bus_reset); /* WCN7850 */
+DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_QCOM, 0x1109, quirk_no_bus_reset); /* QCN9274 */
 DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_QCOM, 0x0308, quirk_no_bus_reset); /* SDX62/SDX65 */
 
 /*
@@ -4398,6 +4399,7 @@ static const struct pci_dev_reset_methods pci_dev_reset_methods[] = {
 	{ PCI_VENDOR_ID_QCOM, 0x0308, reset_qualcomm_modem }, /* SDX62/SDX65 modems */
 	{ PCI_VENDOR_ID_QCOM, 0x1103, reset_qualcomm_wlan },  /* WCN6855 WLAN */
 	{ PCI_VENDOR_ID_QCOM, 0x1107, reset_qualcomm_wlan },  /* WCN7850 WLAN */
+	{ PCI_VENDOR_ID_QCOM, 0x1109, reset_qualcomm_wlan },  /* QCN9274 WLAN */
 	{ 0 }
 };
 
-- 
2.56.0


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

* Re: [PATCH v3 2/8] KVM: add PCI MSI hypercall for VFIO passthrough
  2026-10-29 16:37 ` [PATCH v3 2/8] KVM: add PCI MSI hypercall for VFIO passthrough Jose Ignacio Tornos Martinez
@ 2026-10-29 16:58   ` Jason Gunthorpe
  0 siblings, 0 replies; 11+ messages in thread
From: Jason Gunthorpe @ 2026-10-29 16:58 UTC (permalink / raw)
  To: Jose Ignacio Tornos Martinez
  Cc: alex, pbonzini, seanjc, bhelgaas, jjohnson, johannes, mani,
	lpieralisi, kwilczynski, robh, vkuznets, kevin.tian, linux-pci,
	kvm, linux-wireless, ath11k, ath12k, linux-kernel

On Thu, Oct 29, 2026 at 05:37:07PM +0100, Jose Ignacio Tornos Martinez wrote:
> Some PCI devices (e.g. Qualcomm WiFi adapters) have firmware that
> programs MSI addresses directly into device BAR registers, bypassing
> the standard PCI config MSI path. When these devices are passed
> through to VMs via VFIO, the firmware programs the guest virtual MSI
> address instead of the host physical IRTE address, causing DMAR faults
> and non-functional interrupts.
> 
> Add a KVM hypercall (KVM_HC_PCI_MSI_ALLOC) that allows a guest to
> request the host's physical MSI address/data for a VFIO-assigned PCI
> device. The hypercall is dispatched from the x86 hypercall handler to
> a VFIO backend that:
> 
> 1. Finds the host PCI device matching the guest's vendor:device ID
>    using vfio_file_find_pci_device() (introduced in a previous patch)
> 2. Reads the host MSI address/data from the device's msi_desc or
>    by walking the IRQ domain compose chain
> 3. Returns the result to the guest via a GPA response buffer

This is not quite what I suggested, this still relies on the host
setting up the msi using the trapped MSI emulation, which is the
primary hack..

Was it too hard to make this an 'allocate msi' operation?

> The VFIO handler is connected to the KVM instance via a function
> pointer (pci_msi_alloc) in struct kvm, set when the VFIO device is
> created and cleared on release. This avoids a hard dependency between
> KVM and VFIO subsystems.

I feel like VFIO shouldn't be involved here, for the purpose of MSI
all we need is to inform KVM what struct pci_device * is at a vBDF and
then KVM can do the interrupt bits. Maybe this is already happening?

> +	case KVM_HC_PCI_MSI_ALLOC: {
> +		struct msi_msg msg = {};
> +		struct {
> +			u32 address_lo;
> +			u32 address_hi;
> +			u32 data;
> +			u32 reserved;
> +		} resp;
> +
> +		if (vcpu->kvm->pci_msi_alloc == NULL) {
> +			ret = -KVM_ENOSYS;
> +			break;
> +		}

Yet here it is called alloc?

Nothing should use vendor/device ID either, it should all be drive on
vBDF.

> +	irq = pci_irq_vector(pdev, vector);
> +	if (irq <= 0) {
> +		memset(msg, 0, sizeof(*msg));
> +		return 0;
> +	}
> +
> +	irqd = irq_get_irq_data(irq);
> +	if (!irqd) {
> +		memset(msg, 0, sizeof(*msg));
> +		return 0;
> +	}
> +
> +	desc = irq_get_msi_desc(irq);
> +	if (desc) {
> +		*msg = desc->msg;

And this isn't alloc, it is some query..

I think kvm maintainers would insist on a complete API here if this is
the direction.

I probably would not have selected this, it is why I suggested
virtio-msi or something that could be optional and seperate from the
core KVM guest ABI.

But maybe there is interest in doing it this way?

Jason

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

* Re: [PATCH v3 5/8] ath11k: use KVM MSI domain for VM passthrough
  2026-10-29 16:37 ` [PATCH v3 5/8] ath11k: use KVM MSI domain for VM passthrough Jose Ignacio Tornos Martinez
@ 2026-10-29 17:00   ` Jason Gunthorpe
  0 siblings, 0 replies; 11+ messages in thread
From: Jason Gunthorpe @ 2026-10-29 17:00 UTC (permalink / raw)
  To: Jose Ignacio Tornos Martinez
  Cc: alex, pbonzini, seanjc, bhelgaas, jjohnson, johannes, mani,
	lpieralisi, kwilczynski, robh, vkuznets, kevin.tian, linux-pci,
	kvm, linux-wireless, ath11k, ath12k, linux-kernel

On Thu, Oct 29, 2026 at 05:37:10PM +0100, Jose Ignacio Tornos Martinez wrote:
> @@ -855,6 +858,13 @@ static int ath11k_pci_power_up(struct ath11k_base *ab)
>  
>  	ath11k_pci_msi_enable(ab_pci);
>  
> +#if IS_ENABLED(CONFIG_PCI_KVM_MSI_DOMAIN)
> +	if (kvm_pci_msi_wait_ready(ab_pci->pdev, 500) > 0)
> +		kvm_pci_msi_get_address(ab_pci->pdev, &ab->pci.msi.addr_lo,
> +					&ab->pci.msi.addr_hi,
> +					&ab->pci.msi.ep_base_data);
> +#endif

This connection should be done through ACPI somehow, and I think
Thomas would expect this to be modeled on the platform msi domain,
just ported to PCI instead of this.

Jason

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

end of thread, other threads:[~2026-10-29 17:00 UTC | newest]

Thread overview: 11+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-29 16:37 [PATCH v3 0/8] PCI/KVM/VFIO: MSI address passthrough for Qualcomm WiFi devices Jose Ignacio Tornos Martinez
2026-10-29 16:37 ` [PATCH v3 1/8] vfio: add helper to find PCI device by vendor/device ID Jose Ignacio Tornos Martinez
2026-10-29 16:37 ` [PATCH v3 2/8] KVM: add PCI MSI hypercall for VFIO passthrough Jose Ignacio Tornos Martinez
2026-10-29 16:58   ` Jason Gunthorpe
2026-10-29 16:37 ` [PATCH v3 3/8] PCI: add KVM MSI domain guest module Jose Ignacio Tornos Martinez
2026-10-29 16:37 ` [PATCH v3 4/8] ath11k: add PCIe link recovery retry Jose Ignacio Tornos Martinez
2026-10-29 16:37 ` [PATCH v3 5/8] ath11k: use KVM MSI domain for VM passthrough Jose Ignacio Tornos Martinez
2026-10-29 17:00   ` Jason Gunthorpe
2026-10-29 16:37 ` [PATCH v3 6/8] ath12k: add PCIe link recovery retry Jose Ignacio Tornos Martinez
2026-10-29 16:37 ` [PATCH v3 7/8] ath12k: use KVM MSI domain for VM passthrough Jose Ignacio Tornos Martinez
2026-10-29 16:37 ` [PATCH v3 8/8] PCI: add QCN9274 to reset quirks and KVM MSI domain Jose Ignacio Tornos Martinez

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®