mirror of
https://github.com/torvalds/linux.git
synced 2026-11-07 06:17:02 +01:00
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:
commit
abed5c0eb0
|
|
@ -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),
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user