Merge branch 'pci/controller/vmd'

- Flush DMA writes before handling MSI on Meteor Lake: MSI may arrive
  before write has completed, resulting in an nvme 30 second stall before a
  QID timeout and possibly other similar problems (Rickey Bartlett)

- Flush DMA writes before handling MSI on Arrow Lake (Vladimir Nedoshivin)

* pci/controller/vmd:
  PCI: vmd: Flush DMA writes before handling MSI on Arrow Lake
  PCI: vmd: Flush DMA writes before handling MSI on Meteor Lake
This commit is contained in:
Bjorn Helgaas 2026-10-21 13:20:38 -05:00
commit abed5c0eb0

View File

@ -92,6 +92,23 @@ enum vmd_features {
* referred to as MEMBAR2 or MSI-X BAR.
*/
VMD_FEAT_USE_BIOS_INFO = (1 << 6),
/*
* Some Intel VMD devices are affected by interrupt ordering errata,
* including MTL016 (0x7d0b) and ARL004 (0xad0b):
* the VMD may signal its MSI before the posted writes that carry the
* child device's DMA data have landed in memory. The demuxed handler
* then runs against a not-yet-coherent completion queue and misses the
* completion entirely, so the I/O is only recovered when the block
* layer timeout fires and polls the queue ("timeout, completion
* polled"). Intel's documented workaround is to issue a dummy read
* to the MSI initiator (the child device) before handling the
* interrupt: the read completion cannot pass the device's earlier
* posted writes, so it pulls them into memory per PCIe ordering
* rules. A read that terminates at the VMD itself does not order
* against the child's writes and is not sufficient (measured).
*/
VMD_FEAT_INTERRUPT_QUIRK = (1 << 7),
};
#define VMD_BIOS_PM_QUIRK_LTR 0x1003 /* 3145728 ns */
@ -114,6 +131,9 @@ static DEFINE_RAW_SPINLOCK(list_lock);
* @irq: back pointer to parent.
* @enabled: true if driver enabled IRQ
* @virq: the virtual IRQ value provided to the requesting driver.
* @flush_addr: config space address of the initiating device, read before
* demuxing to flush its posted writes; NULL if the
* VMD is not affected.
*
* Every MSI/MSI-X IRQ requested for a device in a VMD domain will be mapped to
* a VMD IRQ using this structure.
@ -123,8 +143,14 @@ struct vmd_irq {
struct vmd_irq_list *irq;
bool enabled;
unsigned int virq;
void __iomem *flush_addr;
};
struct vmd_dev;
static void __iomem *vmd_cfg_addr(struct vmd_dev *vmd, struct pci_bus *bus,
unsigned int devfn, int reg, int len);
/**
* struct vmd_irq_list - list of driver requested IRQs mapping to a VMD vector
* @irq_list: the list of irq's the VMD one demuxes to.
@ -132,12 +158,15 @@ struct vmd_irq {
* @count: number of child IRQs assigned to this vector; used to track
* sharing.
* @virq: The underlying VMD Linux interrupt number
* @vmd: back pointer to the owning VMD device; serializes the MTL016
* flush read against other config space access.
*/
struct vmd_irq_list {
struct list_head irq_list;
struct srcu_struct srcu;
unsigned int count;
unsigned int virq;
struct vmd_dev *vmd;
};
struct vmd_dev {
@ -295,6 +324,13 @@ static int vmd_msi_alloc(struct irq_domain *domain, unsigned int virq,
INIT_LIST_HEAD(&vmdirq->node);
vmdirq->irq = vmd_next_irq(vmd, desc);
vmdirq->virq = virq + i;
if (vmd->features & VMD_FEAT_INTERRUPT_QUIRK) {
struct pci_dev *pdev = msi_desc_to_pci_dev(desc);
vmdirq->flush_addr = vmd_cfg_addr(vmd, pdev->bus,
pdev->devfn,
PCI_VENDOR_ID, 2);
}
irq_domain_set_info(domain, virq + i, vmdirq->irq->virq,
&vmd_msi_controller, vmdirq,
@ -756,8 +792,20 @@ static irqreturn_t vmd_irq(int irq, void *data)
int idx;
idx = srcu_read_lock(&irqs->srcu);
list_for_each_entry_rcu(vmdirq, &irqs->irq_list, node)
list_for_each_entry_rcu(vmdirq, &irqs->irq_list, node) {
/*
* MTL016: the MSI may have outrun the initiating device's
* posted writes (e.g. its NVMe completion entry). A read
* that completes at the initiator flushes them, so the
* demuxed handler observes a coherent completion queue.
* The value is discarded; only the ordering matters.
*/
if (vmdirq->flush_addr) {
guard(raw_spinlock)(&irqs->vmd->cfg_lock);
readw(vmdirq->flush_addr);
}
generic_handle_irq(vmdirq->virq);
}
srcu_read_unlock(&irqs->srcu, idx);
return IRQ_HANDLED;
@ -788,6 +836,7 @@ static int vmd_alloc_irqs(struct vmd_dev *vmd)
return err;
INIT_LIST_HEAD(&vmd->irqs[i].irq_list);
vmd->irqs[i].vmd = vmd;
vmd->irqs[i].virq = pci_irq_vector(dev, i);
err = devm_request_irq(&dev->dev, vmd->irqs[i].virq,
vmd_irq, IRQF_NO_THREAD,
@ -1250,9 +1299,9 @@ static const struct pci_device_id vmd_ids[] = {
{PCI_VDEVICE(INTEL, 0xa77f),
.driver_data = VMD_FEATS_CLIENT,},
{PCI_VDEVICE(INTEL, 0x7d0b),
.driver_data = VMD_FEATS_CLIENT,},
.driver_data = VMD_FEATS_CLIENT | VMD_FEAT_INTERRUPT_QUIRK,},
{PCI_VDEVICE(INTEL, 0xad0b),
.driver_data = VMD_FEATS_CLIENT,},
.driver_data = VMD_FEATS_CLIENT | VMD_FEAT_INTERRUPT_QUIRK,},
{PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_VMD_9A0B),
.driver_data = VMD_FEATS_CLIENT,},
{PCI_VDEVICE(INTEL, 0xb60b),