// SPDX-License-Identifier: GPL-2.0-only /* * VFIO PCI interrupt handling * * Copyright (C) 2012 Red Hat, Inc. All rights reserved. * Author: Alex Williamson <alex.williamson@redhat.com> * * Derived from original vfio: * Copyright 2010 Cisco Systems, Inc. All rights reserved. * Author: Tom Lyon, pugs@cisco.com
*/
/* Returns true if the INTx vfio_pci_irq_ctx.masked value is changed. */ staticbool __vfio_pci_intx_mask(struct vfio_pci_core_device *vdev)
{ struct pci_dev *pdev = vdev->pdev; struct vfio_pci_irq_ctx *ctx; unsignedlong flags; bool masked_changed = false;
lockdep_assert_held(&vdev->igate);
spin_lock_irqsave(&vdev->irqlock, flags);
/* * Masking can come from interrupt, ioctl, or config space * via INTx disable. The latter means this can get called * even when not using intx delivery. In this case, just * try to have the physical bit follow the virtual bit.
*/ if (unlikely(!is_intx(vdev))) { if (vdev->pci_2_3)
pci_intx(pdev, 0); goto out_unlock;
}
ctx = vfio_irq_ctx_get(vdev, 0); if (WARN_ON_ONCE(!ctx)) goto out_unlock;
if (!ctx->masked) { /* * Can't use check_and_mask here because we always want to * mask, not just when something is pending.
*/ if (vdev->pci_2_3)
pci_intx(pdev, 0); else
disable_irq_nosync(pdev->irq);
/* * If this is triggered by an eventfd, we can't call eventfd_signal * or else we'll deadlock on the eventfd wait queue. Return >0 when * a signal is necessary, which can then be handled via a work queue * or directly depending on the caller.
*/ staticint vfio_pci_intx_unmask_handler(void *opaque, void *data)
{ struct vfio_pci_core_device *vdev = opaque; struct pci_dev *pdev = vdev->pdev; struct vfio_pci_irq_ctx *ctx = data; unsignedlong flags; int ret = 0;
spin_lock_irqsave(&vdev->irqlock, flags);
/* * Unmasking comes from ioctl or config, so again, have the * physical bit follow the virtual even when not using INTx.
*/ if (unlikely(!is_intx(vdev))) { if (vdev->pci_2_3)
pci_intx(pdev, 1); goto out_unlock;
}
if (ctx->masked && !vdev->virq_disabled) { /* * A pending interrupt here would immediately trigger, * but we can avoid that overhead by just re-sending * the interrupt to the user.
*/ if (vdev->pci_2_3) { if (!pci_check_and_unmask_intx(pdev))
ret = 1;
} else
enable_irq(pdev->irq);
/* * Fill the initial masked state based on virq_disabled. After * enable, changing the DisINTx bit in vconfig directly changes INTx * masking. igate prevents races during setup, once running masked * is protected via irqlock. * * Devices supporting DisINTx also reflect the current mask state in * the physical DisINTx bit, which is not affected during IRQ setup. * * Devices without DisINTx support require an exclusive interrupt. * IRQ masking is performed at the IRQ chip. Again, igate protects * against races during setup and IRQ handlers and irqfds are not * yet active, therefore masked is stable and can be used to * conditionally auto-enable the IRQ. * * irq_type must be stable while the IRQ handler is registered, * therefore it must be set before request_irq().
*/
ctx->masked = vdev->virq_disabled; if (vdev->pci_2_3) {
pci_intx(pdev, !ctx->masked);
irqflags = IRQF_SHARED;
} else {
irqflags = ctx->masked ? IRQF_NO_AUTOEN : 0;
}
vdev->irq_type = VFIO_PCI_INTX_IRQ_INDEX;
if (!vdev->pci_2_3)
irq_set_status_flags(pdev->irq, IRQ_DISABLE_UNLAZY);
ret = request_irq(pdev->irq, vfio_intx_handler,
irqflags, ctx->name, ctx); if (ret) { if (!vdev->pci_2_3)
irq_clear_status_flags(pdev->irq, IRQ_DISABLE_UNLAZY);
vdev->irq_type = VFIO_PCI_NUM_IRQS;
kfree(name);
vfio_irq_ctx_free(vdev, ctx, 0); return ret;
}
staticint vfio_msi_enable(struct vfio_pci_core_device *vdev, int nvec, bool msix)
{ struct pci_dev *pdev = vdev->pdev; unsignedint flag = msix ? PCI_IRQ_MSIX : PCI_IRQ_MSI; int ret;
u16 cmd;
if (!is_irq_none(vdev)) return -EINVAL;
/* return the number of supported vectors if we can't get all: */
cmd = vfio_pci_memory_lock_and_enable(vdev);
ret = pci_alloc_irq_vectors(pdev, 1, nvec, flag); if (ret < nvec) { if (ret > 0)
pci_free_irq_vectors(pdev);
vfio_pci_memory_unlock_and_restore(vdev, cmd); return ret;
}
vfio_pci_memory_unlock_and_restore(vdev, cmd);
if (!msix) { /* * Compute the virtual hardware field for max msi vectors - * it is the log base 2 of the number of vectors.
*/
vdev->msi_qmax = fls(nvec * 2 - 1) - 1;
}
return 0;
}
/* * vfio_msi_alloc_irq() returns the Linux IRQ number of an MSI or MSI-X device * interrupt vector. If a Linux IRQ number is not available then a new * interrupt is allocated if dynamic MSI-X is supported. * * Where is vfio_msi_free_irq()? Allocated interrupts are maintained, * essentially forming a cache that subsequent allocations can draw from. * Interrupts are freed using pci_free_irq_vectors() when MSI/MSI-X is * disabled.
*/ staticint vfio_msi_alloc_irq(struct vfio_pci_core_device *vdev, unsignedint vector, bool msix)
{ struct pci_dev *pdev = vdev->pdev; struct msi_map map; int irq;
u16 cmd;
irq = pci_irq_vector(pdev, vector); if (WARN_ON_ONCE(irq == 0)) return -EINVAL; if (irq > 0 || !msix || !vdev->has_dyn_msix) return irq;
if (ctx) {
irq_bypass_unregister_producer(&ctx->producer);
irq = pci_irq_vector(pdev, vector);
cmd = vfio_pci_memory_lock_and_enable(vdev);
free_irq(irq, ctx->trigger);
vfio_pci_memory_unlock_and_restore(vdev, cmd); /* Interrupt stays allocated, will be freed at MSI-X disable. */
kfree(ctx->name);
eventfd_ctx_put(ctx->trigger);
vfio_irq_ctx_free(vdev, ctx, vector);
}
if (fd < 0) return 0;
if (irq == -EINVAL) { /* Interrupt stays allocated, will be freed at MSI-X disable. */
irq = vfio_msi_alloc_irq(vdev, vector, msix); if (irq < 0) return irq;
}
ctx = vfio_irq_ctx_alloc(vdev, vector); if (!ctx) return -ENOMEM;
ctx->name = kasprintf(GFP_KERNEL_ACCOUNT, "vfio-msi%s[%d](%s)",
msix ? "x" : "", vector, pci_name(pdev)); if (!ctx->name) {
ret = -ENOMEM; goto out_free_ctx;
}
trigger = eventfd_ctx_fdget(fd); if (IS_ERR(trigger)) {
ret = PTR_ERR(trigger); goto out_free_name;
}
/* * If the vector was previously allocated, refresh the on-device * message data before enabling in case it had been cleared or * corrupted (e.g. due to backdoor resets) since writing.
*/
cmd = vfio_pci_memory_lock_and_enable(vdev); if (msix) { struct msi_msg msg;
/* * Both disable paths above use pci_intx_for_msi() to clear DisINTx * via their shutdown paths. Restore for NoINTx devices.
*/ if (vdev->nointx)
pci_intx(pdev, 0);
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.