* [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; 19+ 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] 19+ 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:53 ` sashiko-bot
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, 1 reply; 19+ 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 related [flat|nested] 19+ 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:59 ` sashiko-bot
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, 2 replies; 19+ 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 related [flat|nested] 19+ 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 17:06 ` sashiko-bot
2026-10-29 16:37 ` [PATCH v3 4/8] ath11k: add PCIe link recovery retry Jose Ignacio Tornos Martinez
` (4 subsequent siblings)
7 siblings, 1 reply; 19+ 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 related [flat|nested] 19+ 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:45 ` sashiko-bot
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, 1 reply; 19+ 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 related [flat|nested] 19+ 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 16:54 ` sashiko-bot
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, 2 replies; 19+ 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 related [flat|nested] 19+ 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 17:00 ` sashiko-bot
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, 1 reply; 19+ 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 related [flat|nested] 19+ 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 17:04 ` sashiko-bot
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, 1 reply; 19+ 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 related [flat|nested] 19+ 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
2026-10-29 17:02 ` sashiko-bot
7 siblings, 1 reply; 19+ 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 related [flat|nested] 19+ messages in thread
* Re: [PATCH v3 4/8] ath11k: add PCIe link recovery retry
2026-10-29 16:37 ` [PATCH v3 4/8] ath11k: add PCIe link recovery retry Jose Ignacio Tornos Martinez
@ 2026-10-29 16:45 ` sashiko-bot
0 siblings, 0 replies; 19+ messages in thread
From: sashiko-bot @ 2026-10-29 16:45 UTC (permalink / raw)
To: Jose Ignacio Tornos Martinez; +Cc: linux-pci
> 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>
Sashiko has reviewed this patch and found no issues.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261029163713.1366077-1-jtornosm@redhat.com?part=4
^ permalink raw reply [flat|nested] 19+ messages in thread
* Re: [PATCH v3 1/8] vfio: add helper to find PCI device by vendor/device ID
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:53 ` sashiko-bot
0 siblings, 0 replies; 19+ messages in thread
From: sashiko-bot @ 2026-10-29 16:53 UTC (permalink / raw)
To: Jose Ignacio Tornos Martinez; +Cc: linux-pci, kvm
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [Critical] vfio: unreferenced vfio_device return in vfio_file_find_pci_device()
- [High] vfio: broken cdev lookup in vfio_file_find_pci_device() when !VFIO_GROUP
- [Medium] vfio: ambiguous device lookup by vendor/device ID in vfio_file_find_pci_device()
--
commit df00c747a1dfff6dd20e305bf98f78e19636f45e
Author: Jose Ignacio Tornos Martinez <jtornosm@redhat.com>
vfio: add helper to find PCI device by vendor/device ID
This commit adds vfio_file_find_pci_device() and vfio_file_device() to allow
other subsystems to locate a VFIO-owned PCI device by its vendor and device
IDs from a VFIO group or cdev file descriptor.
> diff --git a/drivers/vfio/group.c b/drivers/vfio/group.c
> index 185f359f75d38..0b6cf90edda30 100644
> --- a/drivers/vfio/group.c
> +++ b/drivers/vfio/group.c
[ ... ]
> @@ -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;
> + }
[Severity: Medium]
What happens if a VFIO group contains multiple PCI devices or functions
with the same vendor and device IDs (for example, two identical cards
behind a PCIe switch or bridge without ACS isolation, or a multi-function
device with identical function IDs)?
Since this loop matches only on vendor and device_id and breaks on the
first match in group->device_list without BDF/devfn disambiguation or
checking whether the device is opened by the VM, could this return an
unopened sibling device in the same group or cause multiple assigned
devices to resolve to the first entry in the list?
> + }
> + mutex_unlock(&group->device_lock);
> + return ret;
> + }
[Severity: Critical]
Can returning ret here after dropping group->device_lock lead to a
use-after-free of struct vfio_device and vdev->dev in the caller?
Unlike a VFIO device file, which holds a registration reference until
vfio_device_fops_release(), an open VFIO group file only pins struct
vfio_group and does not hold a reference on the struct vfio_device entries
in group->device_list. Existing group list lookup code in
drivers/vfio/group.c acquires a reference while holding group->device_lock:
drivers/vfio/group.c:vfio_device_get_from_name() {
...
if (ret && vfio_device_try_get_registration(it)) {
device = it;
break;
}
}
mutex_unlock(&group->device_lock);
}
If the matched device in the group does not have an open device fd (or its
device fd is concurrently closed while the group fd remains open), a
concurrent driver unbind or hot-unplug via:
vfio_pci_remove()
-> vfio_pci_core_unregister_device()
-> vfio_unregister_group_dev()
-> vfio_device_group_unregister()
-> vfio_device_put_registration()
-> vfio_put_device()
-> vfio_device_release()
-> kvfree(device)
can remove vdev from group->device_list and free it as soon as
mutex_unlock(&group->device_lock) is called. Should this acquire a
reference on vdev before unlocking and provide a corresponding put helper
for callers?
[ ... ]
> diff --git a/include/linux/vfio.h b/include/linux/vfio.h
> index 0cc91c6f96d2b..a62fbf42458a4 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
[Severity: High]
Will vfio_file_find_pci_device() work for cdev-based device files when
CONFIG_VFIO_GROUP is disabled and CONFIG_VFIO_DEVICE_CDEV is enabled?
Because vfio_file_find_pci_device() is defined in drivers/vfio/group.c and
stubbed to return NULL when !CONFIG_VFIO_GROUP, the vfio_file_device(file)
cdev lookup path is never compiled in a cdev-only kernel configuration
(and symbol_get(vfio_file_find_pci_device) callers will not find the
exported symbol).
Should vfio_file_find_pci_device() be placed in drivers/vfio/vfio_main.c
and declared unconditionally in include/linux/vfio.h alongside
vfio_file_is_valid(), vfio_file_enforced_coherent(), and
vfio_file_set_kvm()?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261029163713.1366077-1-jtornosm@redhat.com?part=1
^ permalink raw reply [flat|nested] 19+ 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 16:54 ` sashiko-bot
2026-10-29 17:00 ` Jason Gunthorpe
1 sibling, 0 replies; 19+ messages in thread
From: sashiko-bot @ 2026-10-29 16:54 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] wifi: ath11k: hard dependency on pci-kvm-msi breaks module load on baremetal
- [Low] wifi: ath11k: vmlinux link failure when PCI_KVM_MSI_DOMAIN=m and ATH11K_PCI=y
--
commit fd35a9fb43489e50093e9e8bd845b666d46403f3
Author: Jose Ignacio Tornos Martinez <jtornosm@redhat.com>
ath11k: use KVM MSI domain for VM passthrough
When CONFIG_PCI_KVM_MSI_DOMAIN is enabled, this patch uses
kvm_pci_msi_wait_ready() and kvm_pci_msi_get_address() in
ath11k_pci_power_up() to obtain the host physical MSI address for VM
passthrough, and adds a pre-softdep on pci-kvm-msi.
> diff --git a/drivers/net/wireless/ath/ath11k/pci.c b/drivers/net/wireless/ath/ath11k/pci.c
> index 7e29ebaf97731..31889e5caa8d7 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
[Severity: Low]
Will vmlinux fail to link when CONFIG_ATH11K_PCI=y and
CONFIG_PCI_KVM_MSI_DOMAIN=m?
In drivers/pci/controller/Kconfig, PCI_KVM_MSI_DOMAIN is defined as a
tristate option:
config PCI_KVM_MSI_DOMAIN
tristate "KVM PCI passthrough MSI domain"
depends on X86_64 && KVM_GUEST && PCI_MSI
select IRQ_MSI_LIB
And in drivers/net/wireless/ath/ath11k/Kconfig, ATH11K_PCI is also tristate
without a dependency on PCI_KVM_MSI_DOMAIN:
config ATH11K_PCI
tristate "Atheros ath11k PCI support"
depends on ATH11K && PCI
When CONFIG_ATH11K_PCI=y and CONFIG_PCI_KVM_MSI_DOMAIN=m,
IS_ENABLED(CONFIG_PCI_KVM_MSI_DOMAIN) evaluates to true in
drivers/net/wireless/ath/ath11k/pci.c, so ath11k_pci_power_up() emits direct
calls from built-in code to kvm_pci_msi_wait_ready() and
kvm_pci_msi_get_address(), which are only exported by the loadable module
pci-kvm-msi.ko.
Should ATH11K_PCI add a dependency such as:
depends on PCI_KVM_MSI_DOMAIN || !PCI_KVM_MSI_DOMAIN
or should this be guarded with IS_REACHABLE(CONFIG_PCI_KVM_MSI_DOMAIN)?
> +
> 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
[Severity: High]
Does this prevent ath11k_pci.ko from loading on baremetal or with devices
not listed in kvm_msi_devids when both CONFIG_ATH11K_PCI=m and
CONFIG_PCI_KVM_MSI_DOMAIN=m?
Because ath11k_pci_power_up() in drivers/net/wireless/ath/ath11k/pci.c
directly calls kvm_pci_msi_wait_ready() and kvm_pci_msi_get_address(),
ath11k_pci.ko has a hard ELF symbol dependency on pci-kvm-msi.ko.
MODULE_SOFTDEP("pre: pci-kvm-msi") only instructs modprobe to attempt
loading pci-kvm-msi before ath11k_pci, and does not make symbol resolution
optional.
In drivers/pci/controller/pci-kvm-msi.c, kvm_pci_msi_init() refuses to load
the module on baremetal or when no matching device in kvm_msi_devids is
found (which also omits ath11k devices such as QCA6390 0x1101 and QCN9074
0x1104):
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;
}
When kvm_pci_msi_init() returns -ENODEV, pci-kvm-msi.ko is not loaded, so
the kernel module loader cannot resolve kvm_pci_msi_wait_ready and
kvm_pci_msi_get_address when loading ath11k_pci.ko.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261029163713.1366077-1-jtornosm@redhat.com?part=5
^ permalink raw reply [flat|nested] 19+ 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
2026-10-29 16:59 ` sashiko-bot
1 sibling, 0 replies; 19+ 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] 19+ 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
@ 2026-10-29 16:59 ` sashiko-bot
1 sibling, 0 replies; 19+ messages in thread
From: sashiko-bot @ 2026-10-29 16:59 UTC (permalink / raw)
To: Jose Ignacio Tornos Martinez; +Cc: linux-pci, kvm
Thank you for your contribution! Sashiko AI review found 9 potential issue(s) to consider:
- [Critical] kvm: vfio: use-after-free on struct kvm_vfio and race on pci_msi_alloc
- [Critical] kvm: vfio: use-after-free on vfio_device, msi_desc, and irq_data
- [High] kvm: deadlock acquiring kvm->lock in kvm_vfio_pci_msi_alloc()
- [High] kvm: vfio: guest-triggerable WARN_ON_ONCE via unvalidated MSI vector
- [High] kvm: vfio: broken Multi-MSI message lookup in kvm_vfio_pci_msi_alloc()
- [High] kvm: vfio: ambiguous and unisolated device lookup in kvm_vfio_pci_msi_alloc()
- [Medium] kvm: vfio: link failure calling vfio_file_find_pci_device() directly
- [Medium] kvm: x86: KVM_HC_PCI_MSI_ALLOC lacks feature enumeration and opt-in
- [Low] kvm: vfio: build failure accessing irq_data.parent_data directly
--
commit dc68213ab54a0729231a44c10317169580ab3c78
Author: Jose Ignacio Tornos Martinez <jtornosm@redhat.com>
KVM: add PCI MSI hypercall for VFIO passthrough
This commit introduces a new x86 hypercall, KVM_HC_PCI_MSI_ALLOC, to allow a
guest to query the host physical MSI address and data for a VFIO passthrough
PCI device. It connects arch/x86/kvm/x86.c to a new kvm_vfio_pci_msi_alloc()
helper in virt/kvm/vfio.c via a pci_msi_alloc callback on struct kvm.
> diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
> index 32563a91fba99..2f0cae07f9786 100644
> --- a/arch/x86/kvm/x86.c
> +++ b/arch/x86/kvm/x86.c
[ ... ]
> @@ -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;
> + }
[Severity: Medium]
Should KVM_HC_PCI_MSI_ALLOC be gated behind a paravirtual CPUID feature bit
checked via guest_pv_has() or an explicit userspace capability opt-in?
In ____kvm_emulate_hypercall() in arch/x86/kvm/x86.c, other KVM hypercalls
such as KVM_HC_KICK_CPU, KVM_HC_SEND_IPI, and KVM_HC_SCHED_YIELD check
guest_pv_has(vcpu, KVM_FEATURE_*), and KVM_HC_MAP_GPA_RANGE checks
user_exit_on_hypercall(). Here, kvm_vfio_create() unconditionally sets
dev->kvm->pci_msi_alloc = kvm_vfio_pci_msi_alloc whenever a kvm-vfio device
is created, making this hypercall and the host physical MSI routing
information available to any guest by default without enumeration or
userspace opt-in.
Also, since the guest memory layout of resp is part of the guest-host ABI,
should that structure be defined in include/uapi/linux/kvm_para.h rather than
as an anonymous local struct in ____kvm_emulate_hypercall(), and should
64-bit hypercall arguments a0 and a1 be validated for upper-bit truncation
before being passed as u32 and unsigned int?
> +
> + /*
> + * 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);
[ ... ]
> diff --git a/virt/kvm/vfio.c b/virt/kvm/vfio.c
> index 19548a4309426..60356173e69f3 100644
> --- a/virt/kvm/vfio.c
> +++ b/virt/kvm/vfio.c
[ ... ]
> @@ -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;
> + }
> + }
> + }
[Severity: High]
Can acquiring kvm->lock here in kvm_vfio_pci_msi_alloc() cause a deadlock?
When a guest invokes KVM_HC_PCI_MSI_ALLOC, the call chain is:
kvm_vcpu_ioctl() [holds vcpu->mutex]
-> kvm_arch_vcpu_ioctl_run()
-> vcpu_enter_guest() [holds kvm->srcu via kvm_vcpu_srcu_read_lock()]
-> handle_exit()
-> kvm_emulate_hypercall()
-> ____kvm_emulate_hypercall()
-> kvm_vfio_pci_msi_alloc()
-> mutex_lock(&kvm->lock)
According to Documentation/virt/kvm/locking.rst, kvm->lock must be acquired
outside vcpu->mutex (for example, kvm_lock_all_vcpus() and
kvm_vm_ioctl_create_vcpu() in virt/kvm/kvm_main.c take kvm->lock and then
vcpu->mutex). In addition, synchronize_srcu(&kvm->srcu) is called while
holding kvm->lock or vcpu->mutex.
Doesn't taking kvm->lock while holding both vcpu->mutex and the kvm->srcu
read lock introduce an ABBA lock inversion and an SRCU deadlock?
> +
> + if (!kv)
> + return -ENODEV;
> +
> + guard(mutex)(&kv->lock);
[Severity: Critical]
What prevents struct kvm_vfio (kv) and its kv->file_list entries from being
freed before or during kvm_vfio_pci_msi_alloc()?
In kvm_vfio_pci_msi_alloc(), kvm->lock is released at the end of the
scoped_guard block without pinning dev or kv. If userspace closes the
kvm-vfio device fd concurrently:
kvm_device_release()
-> mutex_lock(&kvm->lock)
-> list_del(&dev->vm_node)
-> mutex_unlock(&kvm->lock)
-> synchronize_rcu()
-> kvm_vfio_release(dev)
-> list_for_each_entry_safe(kvf, tmp, &kv->file_list, node)
kvm_vfio_file_free(dev, kvf);
-> dev->kvm->pci_msi_alloc = NULL;
-> kfree(kv);
Because synchronize_rcu() does not wait for preemptible vCPU threads in
____kvm_emulate_hypercall(), and kvm_vfio_release() frees kv->file_list
without taking kv->lock and then frees kv, can kvm_vfio_pci_msi_alloc()
access freed memory when locking kv->lock or iterating kv->file_list?
Similarly, in ____kvm_emulate_hypercall():
if (vcpu->kvm->pci_msi_alloc == NULL) {
ret = -KVM_ENOSYS;
break;
}
ret = vcpu->kvm->pci_msi_alloc(vcpu->kvm, a0, a1, &msg);
could kvm_vfio_release() set dev->kvm->pci_msi_alloc = NULL between the NULL
check and the indirect call, resulting in a NULL function pointer call?
> +
> + vdev = NULL;
> + list_for_each_entry(kvf, &kv->file_list, node) {
> + vdev = vfio_file_find_pci_device(kvf->file, vendor, device);
> + if (vdev)
> + break;
> + }
[Severity: Medium]
Will calling vfio_file_find_pci_device() directly break the build when
CONFIG_KVM=y and CONFIG_VFIO=m (with CONFIG_VFIO_GROUP=y)?
In virt/kvm/vfio.c, all other VFIO helpers (such as vfio_file_set_kvm and
vfio_file_enforced_coherent) are looked up dynamically via symbol_get() and
symbol_put() so that built-in KVM does not have a link-time dependency on
vfio.ko. Calling vfio_file_find_pci_device() directly from virt/kvm/vfio.c
causes an undefined symbol reference at link time when KVM is built-in and
VFIO is a module.
Also, in include/linux/vfio.h, vfio_file_find_pci_device() is guarded by
#if IS_ENABLED(CONFIG_VFIO_GROUP). When CONFIG_VFIO_DEVICE_CDEV=y and
CONFIG_VFIO_GROUP=n, vfio_file_find_pci_device() compiles to the inline stub
returning NULL, causing kvm_vfio_pci_msi_alloc() to always return -ENODEV on
cdev-only builds.
[Severity: High]
Is matching solely by 16-bit vendor and device ID sufficient when looking up
the VFIO device in kvm_vfio_pci_msi_alloc()?
If a VM has two passthrough PCI devices with the same vendor:device ID, this
loop always stops at the first device in kv->file_list and returns its MSI
message for both devices, which will trigger IOMMU source-ID validation
faults on the second device.
In addition, when kvf->file is a VFIO group fd, vfio_file_find_pci_device()
walks group->device_list and matches any PCI device in the host IOMMU group
without checking whether that device is opened or assigned to this VM. Can a
guest use this hypercall to read the host physical MSI address and data of
unassigned sibling PCI devices in the same IOMMU group?
> +
> + 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);
[Severity: High]
Can a guest trigger a host WARN_ON_ONCE in msi_domain_get_virq() by passing
a vector index greater than MSI_MAX_INDEX (0xffff)?
In ____kvm_emulate_hypercall(), the guest-controlled register a1 is passed
directly as vector to kvm_vfio_pci_msi_alloc(), which passes it unvalidated
to pci_irq_vector(pdev, vector) -> msi_get_virq() -> msi_domain_get_virq():
kernel/irq/msi.c:msi_domain_get_virq() {
if (WARN_ON_ONCE(index > MSI_MAX_INDEX || domid >= MSI_MAX_DEVICE_IRQDOMAINS))
return 0;
}
Should vector be bounds-checked before calling pci_irq_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;
[Severity: Critical]
What protects vdev, pdev, irqd, and desc from concurrent teardown while
kvm_vfio_pci_msi_alloc() accesses them?
First, for a VFIO group file, vfio_file_find_pci_device() drops
group->device_lock before returning vdev without acquiring a reference on
vdev. Holding the group file in kv->file_list does not prevent an individual
vfio_device in group->device_list from being unregistered via
vfio_unregister_group_dev(), so vdev and pdev can be freed while
kvm_vfio_pci_msi_alloc() dereferences them.
Second, pci_irq_vector() only holds msi_descs_lock(&pdev->dev) internally
while looking up the Linux IRQ number. Once it returns irq,
kvm_vfio_pci_msi_alloc() calls irq_get_irq_data(irq), irq_get_msi_desc(irq),
and reads desc->msg (or walks irqd->parent_data) without holding
msi_descs_lock(&pdev->dev), vdev->igate, or the IRQ descriptor lock. If host
userspace concurrently reconfigures or disables MSI/MSI-X via
VFIO_DEVICE_SET_IRQS -> vfio_msi_disable() -> pci_free_irq_vectors(), can
both the irq_desc and struct msi_desc be freed while kvm_vfio_pci_msi_alloc()
is dereferencing desc or irqd?
[Severity: High]
Does reading desc->msg return the wrong MSI message for Multi-MSI vectors
where vector > 0?
For PCI Multi-MSI, __msi_domain_alloc_irqs() in kernel/irq/msi.c attaches the
same shared struct msi_desc to all allocated Linux IRQs in the Multi-MSI
group:
for (i = 0; i < desc->nvec_used; i++) {
irq_set_msi_desc_off(virq, i, desc);
...
}
And pci_msi_domain_write_msg() in drivers/pci/msi/irqdomain.c only caches
desc->msg for the first vector:
if (desc->irq == irq_data->irq)
__pci_write_msi_msg(desc, msg);
Because irq_get_msi_desc(irq) is non-NULL for every MSI/MSI-X vector, the
if (desc) branch is always taken (making the else branch unreachable dead
code), and any Multi-MSI request with vector > 0 receives vector 0's cached
desc->msg instead of the composed message for that vector. Should this use
irq_chip_compose_msi_msg() or adjust the message for Multi-MSI vectors?
> + } 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;
> + }
> + }
[Severity: Low]
Can accessing pos->parent_data directly fail to compile when
CONFIG_IRQ_DOMAIN_HIERARCHY is disabled?
In include/linux/irq.h, struct irq_data only defines parent_data under
#ifdef CONFIG_IRQ_DOMAIN_HIERARCHY:
struct irq_data {
...
#ifdef CONFIG_IRQ_DOMAIN_HIERARCHY
struct irq_data *parent_data;
#endif
void *chip_data;
};
On architectures that select CONFIG_KVM_VFIO without unconditionally
selecting CONFIG_IRQ_DOMAIN_HIERARCHY (such as s390 or powerpc configs
without CONFIG_PCI_MSI), referencing pos->parent_data directly in
virt/kvm/vfio.c will break the build unless irq_chip_compose_msi_msg() or a
config guard is used.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261029163713.1366077-1-jtornosm@redhat.com?part=2
^ permalink raw reply [flat|nested] 19+ 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 16:54 ` sashiko-bot
@ 2026-10-29 17:00 ` Jason Gunthorpe
1 sibling, 0 replies; 19+ 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] 19+ messages in thread
* Re: [PATCH v3 6/8] ath12k: add PCIe link recovery retry
2026-10-29 16:37 ` [PATCH v3 6/8] ath12k: add PCIe link recovery retry Jose Ignacio Tornos Martinez
@ 2026-10-29 17:00 ` sashiko-bot
0 siblings, 0 replies; 19+ messages in thread
From: sashiko-bot @ 2026-10-29 17:00 UTC (permalink / raw)
To: Jose Ignacio Tornos Martinez; +Cc: linux-pci
> 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>
Sashiko has reviewed this patch and found no issues.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261029163713.1366077-1-jtornosm@redhat.com?part=6
^ permalink raw reply [flat|nested] 19+ messages in thread
* Re: [PATCH v3 8/8] PCI: add QCN9274 to reset quirks and KVM MSI domain
2026-10-29 16:37 ` [PATCH v3 8/8] PCI: add QCN9274 to reset quirks and KVM MSI domain Jose Ignacio Tornos Martinez
@ 2026-10-29 17:02 ` sashiko-bot
0 siblings, 0 replies; 19+ messages in thread
From: sashiko-bot @ 2026-10-29 17:02 UTC (permalink / raw)
To: Jose Ignacio Tornos Martinez; +Cc: linux-pci
> 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>
Sashiko has reviewed this patch and found no issues.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261029163713.1366077-1-jtornosm@redhat.com?part=8
^ permalink raw reply [flat|nested] 19+ messages in thread
* Re: [PATCH v3 7/8] ath12k: use KVM MSI domain for VM passthrough
2026-10-29 16:37 ` [PATCH v3 7/8] ath12k: use KVM MSI domain for VM passthrough Jose Ignacio Tornos Martinez
@ 2026-10-29 17:04 ` sashiko-bot
0 siblings, 0 replies; 19+ messages in thread
From: sashiko-bot @ 2026-10-29 17:04 UTC (permalink / raw)
To: Jose Ignacio Tornos Martinez; +Cc: linux-pci, kvm
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] wifi: ath12k: stale msi_ep_base_data not updated in ath12k_pci_power_up()
- [High] wifi: ath12k: module load failure on baremetal when PCI_KVM_MSI_DOMAIN=m
- [Medium] wifi: ath12k: vmlinux link failure when ATH12K=y and PCI_KVM_MSI_DOMAIN=m
--
commit ea9e51c37aa4f1f3d5125a423b52732232e12c47
Author: Jose Ignacio Tornos Martinez <jtornosm@redhat.com>
ath12k: use KVM MSI domain for VM passthrough
This commit adds support for synchronizing and retrieving the host physical
message signaled interrupt address via the kvm pci msi domain in
ath12k_pci_power_up() and ath12k_pci_get_msi_address() when running in a
virtual machine.
> diff --git a/drivers/net/wireless/ath/ath12k/pci.c b/drivers/net/wireless/ath/ath12k/pci.c
> index f7879fb48d32e..dbd960f88d386 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
[Severity: Medium]
Will this cause a vmlinux link failure when ath12k is built-in and the kvm
msi domain driver is built as a loadable module?
Both Kconfig options are defined as tristate without a dependency between
them:
drivers/net/wireless/ath/ath12k/Kconfig:
config ATH12K
tristate "Qualcomm Wi-Fi 7 support (ath12k)"
depends on MAC80211 && HAS_DMA && PCI
drivers/pci/controller/Kconfig:
config PCI_KVM_MSI_DOMAIN
tristate "KVM PCI passthrough MSI domain"
depends on X86_64 && KVM_GUEST && PCI_MSI
Because the calls to kvm_pci_msi_get_address() in
ath12k_pci_get_msi_address() and kvm_pci_msi_wait_ready() in
ath12k_pci_power_up() are guarded with:
#if IS_ENABLED(CONFIG_PCI_KVM_MSI_DOMAIN)
building with ath12k=y and pci_kvm_msi_domain=m causes built-in
drivers/net/wireless/ath/ath12k/pci.o to emit direct calls to symbols in
pci-kvm-msi.ko, failing at the vmlinux link stage. Should this use
IS_REACHABLE() or enforce a Kconfig dependency?
[Severity: High]
What happens on baremetal when both ath12k and pci-kvm-msi are built as
modules?
The direct calls to kvm_pci_msi_get_address() and kvm_pci_msi_wait_ready()
create a hard symbol dependency from ath12k.ko onto pci-kvm-msi.ko. When
modprobe loads pci-kvm-msi.ko first, kvm_pci_msi_init() aborts module
loading on baremetal or when no device in kvm_msi_devids is present on the
bus:
drivers/pci/controller/pci-kvm-msi.c:kvm_pci_msi_init() {
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;
}
Note that kvm_msi_devids only matches 0x1103 and 0x1107 here, and even
with later commit 1d17e12c16ad ("PCI: add QCN9274 to reset quirks and KVM
MSI domain") adding 0x1109, baremetal systems, guests with only other
ath12k devices such as 0x1112, or guests prior to hotplug still return
-ENODEV from kvm_pci_msi_init().
When pci-kvm-msi.ko fails to load with -ENODEV, its exported symbols are
never registered, preventing ath12k.ko from loading on baremetal. Could
that break ath12k module loading on distribution kernels where both options
are enabled as modules?
>
> 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
[Severity: High]
Does ab_pci->msi_ep_base_data remain stale with the guest virtual message
data after kvm_pci_msi_wait_ready() completes in ath12k_pci_power_up()?
During ath12k_pci_probe(), before ath12k_pci_power_up() runs,
ath12k_pci_config_msi_data() latches msi_desc->msg.data into
ab_pci->msi_ep_base_data:
drivers/net/wireless/ath/ath12k/pci.c:ath12k_pci_config_msi_data() {
...
ab_pci->msi_ep_base_data = msi_desc->msg.data;
At that point kvm_pci_msi_wait_ready() has not yet been called, so
msi_desc->msg.data contains the guest virtual vector data rather than the
host physical base data.
Later, when ath12k_pci_power_up() calls kvm_pci_msi_wait_ready(), the host
address and data are fetched and cached in kvm_data->msg and desc->msg,
but ab_pci->msi_ep_base_data is never updated here, and
ath12k_pci_get_msi_address() passes NULL for the data argument of
kvm_pci_msi_get_address().
When copy engine and data path rings are subsequently configured via
ath12k_ce_srng_msi_ring_params_setup() and ath12k_dp_srng_msi_setup(),
ath12k_hif_get_user_msi_vector() calls ath12k_pci_get_user_msi_assignment():
drivers/net/wireless/ath/ath12k/pci.c:ath12k_pci_get_user_msi_assignment() {
...
*num_vectors = msi_config->users[idx].num_vectors;
*base_vector = msi_config->users[idx].base_vector;
*user_base_data = *base_vector + ab_pci->msi_ep_base_data;
Wouldn't this program the device registers and firmware ring setup messages
with the stale guest message data instead of the host base data retrieved
by kvm_pci_msi_wait_ready()?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261029163713.1366077-1-jtornosm@redhat.com?part=7
^ permalink raw reply [flat|nested] 19+ messages in thread
* Re: [PATCH v3 3/8] PCI: add KVM MSI domain guest module
2026-10-29 16:37 ` [PATCH v3 3/8] PCI: add KVM MSI domain guest module Jose Ignacio Tornos Martinez
@ 2026-10-29 17:06 ` sashiko-bot
0 siblings, 0 replies; 19+ messages in thread
From: sashiko-bot @ 2026-10-29 17:06 UTC (permalink / raw)
To: Jose Ignacio Tornos Martinez; +Cc: linux-pci, kvm
Thank you for your contribution! Sashiko AI review found 9 potential issue(s) to consider:
- [High] PCI: kvm-msi: use-after-free on module unload in kvm_pci_msi_exit()
- [High] PCI: kvm-msi: broken IRQ affinity in kvm_pcie_init_dev_msi_info()
- [High] PCI: kvm-msi: host MSI message corrupts guest config in kvm_compose_msi_msg()
- [High] PCI: kvm-msi: global kvm_msi_ctx clobbered in kvm_pcie_domain_alloc()
- [High] PCI: kvm-msi: init failure in kvm_pci_msi_init() breaks hotplug and modprobe
- [High] PCI: kvm-msi: race and use-after-free on kvm_data in kvm_pcie_domain_free()
- [Medium] PCI: kvm-msi: missing stubs in pci-kvm-msi.h break modular link
- [Medium] PCI: kvm-msi: kvm_pci_msi_wait_ready() polls until timeout on hypercall error
- [Medium] PCI: kvm-msi: missing device_lock() in kvm_msi_attach_device()
--
commit 56d70781d8bf9094f446115196e63d6a77876b7c
Author: Jose Ignacio Tornos Martinez <jtornosm@redhat.com>
PCI: add KVM MSI domain guest module
This commit adds a guest-side PCI MSI IRQ domain driver for KVM virtual
machines that intercepts MSI composition and provides kvm_pci_msi_wait_ready()
and kvm_pci_msi_get_address() to obtain host physical MSI addresses via a KVM
hypercall for devices whose firmware programs MSI addresses into BAR registers.
> diff --git a/drivers/pci/controller/Kconfig b/drivers/pci/controller/Kconfig
> index d246bbe37948e..cdf65d1c26741 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
[ ... ]
> diff --git a/drivers/pci/controller/pci-kvm-msi.c b/drivers/pci/controller/pci-kvm-msi.c
> new file mode 100644
> index 0000000000000..e2b0a4114aa88
> --- /dev/null
> +++ b/drivers/pci/controller/pci-kvm-msi.c
[ ... ]
> +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;
[Severity: Medium]
Should kvm_pci_msi_wait_ready() in drivers/pci/controller/pci-kvm-msi.c
check for negative hypercall return values before sleeping and retrying?
In ____kvm_emulate_hypercall() in arch/x86/kvm/x86.c, KVM_HC_PCI_MSI_ALLOC
returns negative error codes on permanent failures, such as -KVM_ENOSYS when
the host kernel does not implement the hypercall or has no kvm-vfio device
(vcpu->kvm->pci_msi_alloc == NULL), and -KVM_EINVAL when the PCI device is not
found in kvm-vfio. It returns 0 with msg.address_lo == 0 only when the VFIO
device exists on the host and is waiting for MSI enablement.
Because the polling loop only checks whether hret == 0 && resp.address_lo != 0
and ignores hret < 0, running a guest on an older KVM host or with a device
not backed by kvm-vfio will sleep for the full timeout_ms duration on every
device power-up or reset and log a timeout error.
> +
> +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);
[ ... ]
> +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);
> +}
[Severity: High]
Won't overwriting desc->msg in kvm_pci_msi_wait_ready() and returning the
cached host physical MSI message from kvm_compose_msi_msg() in
drivers/pci/controller/pci-kvm-msi.c corrupt the guest's virtual PCI MSI
config space when MSI state is restored or recomposed?
In a KVM guest with VFIO passthrough, the PCI config space MSI capability
(PCI_MSI_ADDRESS_LO, PCI_MSI_ADDRESS_HI, PCI_MSI_DATA) is emulated by QEMU
VFIO. QEMU traps guest config writes from __pci_write_msi_msg(), decodes them
as a guest virtual APIC MSI message (guest vCPU APIC ID and guest IDT vector),
and updates KVM's GSI routing table (KVM_SET_GSI_ROUTING). Only the device's
BAR MMIO registers programmed by firmware need the host's physical IOMMU IRTE
MSI address and data.
Because kvm_pci_msi_wait_ready() overwrites desc->msg with the host's physical
IRTE address/data and sets kvm_data->cached = true so kvm_compose_msi_msg()
returns kvm_data->msg, any subsequent PCI config space write by the guest MSI
core will write the host's physical IRTE message into QEMU's emulated PCI
config space:
1. On PCI reset or resume, __pci_restore_msi_state() calls
__pci_write_msi_msg(entry, &entry->msg), writing the overwritten desc->msg
to PCI config space.
2. If an IRQ is freed and re-requested (msi_domain_activate()) or recomposed
(msi_domain_set_affinity()), irq_chip_compose_msi_msg() calls
kvm_compose_msi_msg(), which returns the cached host IRTE message to
pci_msi_domain_write_msg() -> __pci_write_msi_msg().
On Intel VT-d interrupt remapping, the host IRTE data for subhandle 0 is 0, so
QEMU interprets the config space write as a guest APIC MSI targeting guest
vector 0, corrupting KVM GSI routing and breaking interrupt delivery to the
guest.
> +
> +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;
[Severity: High]
What happens in kvm_pcie_domain_alloc() in drivers/pci/controller/pci-kvm-msi.c
when more than one matching PCI device is present in the VM, or when a device
allocates multiple MSI-X vectors?
1. kvm_pci_msi_init() and kvm_msi_bus_notify() attach kvm_msi_domain to all
PCI devices matching kvm_msi_devids, but kvm_msi_ctx is a single global
structure that kvm_pcie_domain_alloc() overwrites on every allocation. If
a second matching device probes and allocates MSI vectors, it overwrites
kvm_msi_ctx.pdev, causing subsequent calls to kvm_pci_msi_wait_ready() or
runtime calls to kvm_pci_msi_get_address() for the first device to fail the
kvm_msi_ctx.pdev != pdev check and return 0.
2. kvm_pcie_msi_parent_ops sets MSI_FLAG_PCI_MSIX in supported_flags. For
MSI-X, __msi_domain_alloc_irqs() calls __irq_domain_alloc_irqs() ->
kvm_pcie_domain_alloc() once per MSI-X descriptor with nr_irqs = 1. Each
vector allocation overwrites kvm_msi_ctx.first_virq = virq and
kvm_msi_ctx.nr_irqs = 1, losing all earlier vectors so
kvm_pci_msi_wait_ready() only populates the last vector with vector 0's
data (and irq_get_msi_desc(pdev->irq) returns NULL for MSI-X because
pdev->irq is not set).
3. Conversely, for Multi-MSI, kvm_pcie_msi_parent_ops.supported_flags omits
MSI_FLAG_MULTI_PCI_MSI, so msi_lib_init_dev_msi_info() masks out
MSI_FLAG_MULTI_PCI_MSI from pci_msi_template even when the parent domain
supports it.
> +
> + 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);
> +}
[Severity: High]
Can kvm_pci_msi_wait_ready() or kvm_pci_msi_get_address() race with
kvm_pcie_domain_free() in drivers/pci/controller/pci-kvm-msi.c and trigger a
use-after-free on kvm_data?
kvm_pcie_domain_alloc() and kvm_pcie_domain_free() run under
domain->root->mutex, whereas kvm_pci_msi_wait_ready() and
kvm_pci_msi_get_address() read kvm_msi_ctx, call irq_domain_get_irq_data(),
and dereference irqd->chip_data (kvm_data) without holding any lock.
In kvm_pci_msi_wait_ready(), kvm_msi_ctx.pdev != pdev is checked only once at
entry before the loop calls msleep(KVM_MSI_POLL_INTERVAL_MS). Meanwhile,
kvm_pcie_domain_free() calls kfree(kvm_data) before irq_domain_free_irqs_top()
clears irq_data->chip_data and unmaps the virq. If a device's IRQs are freed
(or another device probes or unwinds) while kvm_pci_msi_wait_ready() is
sleeping or while kvm_pci_msi_get_address() is running, they can read or write
freed kvm_data memory.
> +
> +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;
> +}
[Severity: High]
Does overwriting chip->irq_set_affinity with irq_chip_set_affinity_parent in
kvm_pcie_init_dev_msi_info() in drivers/pci/controller/pci-kvm-msi.c break
guest MSI interrupt delivery whenever IRQ affinity is set or changed?
In msi_lib_init_dev_msi_info() in drivers/irqchip/irq-msi-lib.c, the
per-device MSI irq_chip's irq_set_affinity callback is initialized to
msi_domain_set_affinity():
if (!chip->irq_set_affinity && !(info->flags & MSI_FLAG_NO_AFFINITY))
chip->irq_set_affinity = msi_domain_set_affinity;
msi_domain_set_affinity() calls parent->chip->irq_set_affinity() and then
calls irq_chip_compose_msi_msg() and irq_chip_write_msi_msg()
(pci_msi_domain_write_msg() -> __pci_write_msi_msg()) so that the updated
guest virtual APIC target CPU and vector are written to PCI config space and
trapped by QEMU VFIO to update KVM's GSI routing.
By overwriting chip->irq_set_affinity on the per-device MSI chip with
irq_chip_set_affinity_parent (while kvm_msi_irq_chip also uses
irq_chip_set_affinity_parent), any affinity change (such as
ath11k_pci_set_irq_affinity_hint() -> irq_set_affinity_and_hint() during probe
or irqbalance in userspace) delegates directly to x86_vector_domain's
apic_set_affinity(). That allocates a new guest vector/CPU and returns
IRQ_SET_MASK_OK without ever composing or writing the new MSI message to PCI
config space, leaving msi_desc->msg and QEMU/KVM GSI routing pointing to the
old guest vector.
[ ... ]
> +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);
> +}
[Severity: Medium]
Can kvm_msi_attach_device() in drivers/pci/controller/pci-kvm-msi.c race with
concurrent driver probe or unbind when called from kvm_pci_msi_init() without
holding device_lock(&pdev->dev)?
In drivers/pci/pci-driver.c, local_pci_probe() and pci_device_remove() set and
clear pdev->driver under device_lock(&pdev->dev). When kvm_pci_msi_init()
iterates over existing PCI devices with for_each_pci_dev(pdev) and calls
kvm_msi_attach_device(pdev) without holding device_lock(&pdev->dev), a
concurrent unbind or failed probe can clear pdev->driver between the
if (pdev->driver) check and the pdev->driver->name dereference, or a
concurrent probe can race with dev_set_msi_domain(&pdev->dev, kvm_msi_domain)
while setting up MSI vectors.
[ ... ]
> +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;
> +}
[Severity: High]
Won't returning -ENODEV from kvm_pci_msi_init() in
drivers/pci/controller/pci-kvm-msi.c prevent dependent wireless drivers from
loading and break PCI hotplug?
1. When CONFIG_PCI_KVM_MSI_DOMAIN=m, consumer modules (such as ath11k_pci.ko
and ath12k.ko) reference the exported symbols kvm_pci_msi_wait_ready() and
kvm_pci_msi_get_address(). When modprobe loads pci-kvm-msi.ko on baremetal
(!kvm_para_available()) or when no device in kvm_msi_devids is currently
present (!first, for example with other ath11k/ath12k PCI IDs or before
hotplug), kvm_pci_msi_init() returns -ENODEV. That causes pci-kvm-msi.ko
to fail to load, which in turn prevents ath11k_pci.ko and ath12k.ko from
loading due to unresolved symbols.
2. Because bus_register_notifier(&pci_bus_type, &kvm_msi_nb) is only reached
if a matching PCI device already exists when kvm_pci_msi_init() runs,
hotplugging a matching VFIO PCI device later will never attach
kvm_msi_domain.
3. In kvm_msi_attach_device(), if pdev->driver is already bound at init time,
it warns that unbind/rebind is needed and skips dev_set_msi_domain().
However, kvm_msi_bus_notify() only acts on BUS_NOTIFY_ADD_DEVICE (which
fires on device_add(), not on driver unbind/rebind), so unbinding and
rebinding the driver never attaches kvm_msi_domain.
> +
> +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;
> + }
> +}
[Severity: High]
Doesn't kvm_pci_msi_exit() in drivers/pci/controller/pci-kvm-msi.c leave
dangling pointers to the freed kvm_msi_domain on attached PCI devices when the
module is unloaded?
During kvm_pci_msi_init() and kvm_msi_bus_notify(), kvm_msi_attach_device()
overwrites pdev->dev.msi.domain with kvm_msi_domain via dev_set_msi_domain()
without saving the original parent MSI domain. In addition, when a driver
allocates MSI vectors via pci_alloc_irq_vectors() ->
pci_setup_msi_device_domain() -> msi_create_device_irq_domain(), a per-device
child irq_domain is created in pdev->dev.msi.data->__domains with
domain->parent pointing to kvm_msi_domain, and that child domain persists
across pci_free_irq_vectors() and driver unbind until the pci_dev is removed.
When kvm_pci_msi_exit() runs on module unload, it calls
irq_domain_remove(kvm_msi_domain) (which frees kvm_msi_domain) without
removing per-device child MSI domains or restoring pdev->dev.msi.domain on
attached PCI devices:
1. Any subsequent driver probe, MSI allocation (msi_create_device_irq_domain()
dereferencing parent = dev->msi.domain and
parent->msi_parent_ops->init_dev_msi_info), or PCI device removal will
dereference the freed kvm_msi_domain and unmapped module callbacks.
2. Reloading pci-kvm-msi after rmmod causes kvm_msi_create_domain() to call
dev_get_msi_domain(&ref_dev->dev), which returns the freed kvm_msi_domain
from the previous load and passes it as info.parent to
msi_create_parent_irq_domain().
> diff --git a/include/linux/pci-kvm-msi.h b/include/linux/pci-kvm-msi.h
> new file mode 100644
> index 0000000000000..cc615e913a3bf
> --- /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 */
[Severity: Medium]
Should include/linux/pci-kvm-msi.h provide static inline fallback stubs
guarded by IS_REACHABLE(CONFIG_PCI_KVM_MSI_DOMAIN)?
Because PCI_KVM_MSI_DOMAIN is a tristate Kconfig option, a built-in consumer
driver (=y) calling kvm_pci_msi_wait_ready() or kvm_pci_msi_get_address()
under IS_ENABLED(CONFIG_PCI_KVM_MSI_DOMAIN) when CONFIG_PCI_KVM_MSI_DOMAIN=m
(or an unguarded caller when CONFIG_PCI_KVM_MSI_DOMAIN=n) will fail to link
vmlinux with undefined references to kvm_pci_msi_wait_ready() and
kvm_pci_msi_get_address().
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261029163713.1366077-1-jtornosm@redhat.com?part=3
^ permalink raw reply [flat|nested] 19+ messages in thread
end of thread, other threads:[~2026-10-29 17:06 UTC | newest]
Thread overview: 19+ 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:53 ` sashiko-bot
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:59 ` sashiko-bot
2026-10-29 16:37 ` [PATCH v3 3/8] PCI: add KVM MSI domain guest module Jose Ignacio Tornos Martinez
2026-10-29 17:06 ` sashiko-bot
2026-10-29 16:37 ` [PATCH v3 4/8] ath11k: add PCIe link recovery retry Jose Ignacio Tornos Martinez
2026-10-29 16:45 ` sashiko-bot
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:54 ` sashiko-bot
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 17:00 ` sashiko-bot
2026-10-29 16:37 ` [PATCH v3 7/8] ath12k: use KVM MSI domain for VM passthrough Jose Ignacio Tornos Martinez
2026-10-29 17:04 ` sashiko-bot
2026-10-29 16:37 ` [PATCH v3 8/8] PCI: add QCN9274 to reset quirks and KVM MSI domain Jose Ignacio Tornos Martinez
2026-10-29 17:02 ` sashiko-bot
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox