// SPDX-License-Identifier: GPL-2.0-or-later /* * Virtio PCI driver - common functionality for all device versions * * This module allows virtio devices to be used over a virtual PCI device. * This can be used with QEMU based VMMs like KVM or Xen. * * Copyright IBM Corp. 2007 * Copyright Red Hat, Inc. 2014 * * Authors: * Anthony Liguori <aliguori@us.ibm.com> * Rusty Russell <rusty@rustcorp.com.au> * Michael S. Tsirkin <mst@redhat.com>
*/
if (!virtio_has_feature(vdev, VIRTIO_F_ADMIN_VQ)) returnfalse;
return index == vp_dev->admin_vq.vq_index;
}
/* wait for pending irq handlers */ void vp_synchronize_vectors(struct virtio_device *vdev)
{ struct virtio_pci_device *vp_dev = to_vp_device(vdev); int i;
if (vp_dev->intx_enabled)
synchronize_irq(vp_dev->pci_dev->irq);
for (i = 0; i < vp_dev->msix_vectors; ++i)
synchronize_irq(pci_irq_vector(vp_dev->pci_dev, i));
}
/* the notify function used when creating a virt queue */ bool vp_notify(struct virtqueue *vq)
{ /* we write the queue's selector into the notification register to
* signal the other end */
iowrite16(vq->index, (void __iomem *)vq->priv); returntrue;
}
/* Notify all slow path virtqueues on an interrupt. */ staticvoid vp_vring_slow_path_interrupt(int irq, struct virtio_pci_device *vp_dev)
{ struct virtio_pci_vq_info *info; unsignedlong flags;
/* Notify all virtqueues on an interrupt. */ static irqreturn_t vp_vring_interrupt(int irq, void *opaque)
{ struct virtio_pci_device *vp_dev = opaque; struct virtio_pci_vq_info *info;
irqreturn_t ret = IRQ_NONE; unsignedlong flags;
spin_lock_irqsave(&vp_dev->lock, flags);
list_for_each_entry(info, &vp_dev->virtqueues, node) { if (vring_interrupt(irq, info->vq) == IRQ_HANDLED)
ret = IRQ_HANDLED;
}
spin_unlock_irqrestore(&vp_dev->lock, flags);
return ret;
}
/* A small wrapper to also acknowledge the interrupt when it's handled. * I really need an EIO hook for the vring so I can ack the interrupt once we * know that we'll be handling the IRQ but before we invoke the callback since * the callback may notify the host which results in the host attempting to * raise an interrupt that we would then mask once we acknowledged the
* interrupt. */ static irqreturn_t vp_interrupt(int irq, void *opaque)
{ struct virtio_pci_device *vp_dev = opaque;
u8 isr;
/* reading the ISR has the effect of also clearing it so it's very
* important to save off the value. */
isr = ioread8(vp_dev->isr);
/* It's definitely not us if the ISR was not high */ if (!isr) return IRQ_NONE;
/* Configuration change? Tell driver if it wants to know. */ if (isr & VIRTIO_PCI_ISR_CONFIG)
vp_config_changed(irq, opaque);
/* Set the vector used for configuration */
v = vp_dev->msix_used_vectors;
snprintf(vp_dev->msix_names[v], sizeof *vp_dev->msix_names, "%s-config", name);
err = request_irq(pci_irq_vector(vp_dev->pci_dev, v),
vp_config_changed, 0, vp_dev->msix_names[v],
vp_dev); if (err) goto error;
++vp_dev->msix_used_vectors;
v = vp_dev->config_vector(vp_dev, v); /* Verify we had enough resources to assign the vector */ if (v == VIRTIO_MSI_NO_VECTOR) {
err = -EBUSY; goto error;
}
if (!per_vq_vectors) { /* Shared vector for all VQs */
v = vp_dev->msix_used_vectors;
snprintf(vp_dev->msix_names[v], sizeof *vp_dev->msix_names, "%s-virtqueues", name);
err = request_irq(pci_irq_vector(vp_dev->pci_dev, v),
vp_vring_interrupt, 0, vp_dev->msix_names[v],
vp_dev); if (err) goto error;
++vp_dev->msix_used_vectors;
} return 0;
error: return err;
}
/* * If it fails during re-enable reset vq. This way we won't rejoin * info->node to the queue. Prevent unexpected irqs.
*/ if (!vq->reset) {
spin_lock_irqsave(&vp_dev->lock, flags);
list_del(&info->node);
spin_unlock_irqrestore(&vp_dev->lock, flags);
}
list_for_each_entry_safe(vq, n, &vdev->vqs, list) {
info = vp_is_avq(vdev, vq->index) ? vp_dev->admin_vq.info :
vp_dev->vqs[vq->index];
if (vp_dev->per_vq_vectors) { int v = info->msix_vector; if (v != VIRTIO_MSI_NO_VECTOR &&
!vp_is_slow_path_vector(v)) { int irq = pci_irq_vector(vp_dev->pci_dev, v);
if (per_vq_vectors) { /* Best option: one for change interrupt, one per vq. */
nvectors = 1; for (i = 0; i < nvqs; ++i) {
vqi = &vqs_info[i]; if (vqi->name && vqi->callback)
++nvectors;
} if (avq_num && vector_policy == VP_VQ_VECTOR_POLICY_EACH)
++nvectors;
} else { /* Second best: one for change, shared for all vqs. */
nvectors = 2;
}
err = vp_request_msix_vectors(vdev, nvectors, per_vq_vectors, desc); if (err) goto error_find;
vp_dev->per_vq_vectors = per_vq_vectors;
allocated_vectors = vp_dev->msix_used_vectors; for (i = 0; i < nvqs; ++i) {
vqi = &vqs_info[i]; if (!vqi->name) {
vqs[i] = NULL; continue;
}
vqs[i] = vp_find_one_vq_msix(vdev, queue_idx++, vqi->callback,
vqi->name, vqi->ctx, false,
&allocated_vectors, vector_policy,
&vp_dev->vqs[i]); if (IS_ERR(vqs[i])) {
err = PTR_ERR(vqs[i]); goto error_find;
}
}
/* the config->find_vqs() implementation */ int vp_find_vqs(struct virtio_device *vdev, unsignedint nvqs, struct virtqueue *vqs[], struct virtqueue_info vqs_info[], struct irq_affinity *desc)
{ int err;
/* Try MSI-X with one vector per queue. */
err = vp_find_vqs_msix(vdev, nvqs, vqs, vqs_info,
VP_VQ_VECTOR_POLICY_EACH, desc); if (!err) return 0; /* Fallback: MSI-X with one shared vector for config and * slow path queues, one vector per queue for the rest.
*/
err = vp_find_vqs_msix(vdev, nvqs, vqs, vqs_info,
VP_VQ_VECTOR_POLICY_SHARED_SLOW, desc); if (!err) return 0; /* Fallback: MSI-X with one vector for config, one shared for queues. */
err = vp_find_vqs_msix(vdev, nvqs, vqs, vqs_info,
VP_VQ_VECTOR_POLICY_SHARED, desc); if (!err) return 0; /* Is there an interrupt? If not give up. */ if (!(to_vp_device(vdev)->pci_dev->irq)) return err; /* Finally fall back to regular interrupts. */ return vp_find_vqs_intx(vdev, nvqs, vqs, vqs_info);
}
/* Setup the affinity for a virtqueue: * - force the affinity for per vq vector * - OR over all affinities for shared MSI * - ignore the affinity request if we're using INTX
*/ int vp_set_vq_affinity(struct virtqueue *vq, conststruct cpumask *cpu_mask)
{ struct virtio_device *vdev = vq->vdev; struct virtio_pci_device *vp_dev = to_vp_device(vdev); struct virtio_pci_vq_info *info = vp_dev->vqs[vq->index]; struct cpumask *mask; unsignedint irq;
/* As struct device is a kobject, it's not safe to * free the memory (including the reference counter itself)
* until it's release callback. */
kfree(vp_dev);
}
/* * Device is marked broken on surprise removal so that virtio upper * layers can abort any ongoing operation.
*/ if (!pci_device_is_present(pci_dev))
virtio_break_device(&vp_dev->vdev);
pci_disable_sriov(pci_dev);
unregister_virtio_device(&vp_dev->vdev);
if (vp_dev->is_legacy)
virtio_pci_legacy_remove(vp_dev); else
virtio_pci_modern_remove(vp_dev);
pci_disable_device(pci_dev);
put_device(dev);
}
staticint virtio_pci_sriov_configure(struct pci_dev *pci_dev, int num_vfs)
{ struct virtio_pci_device *vp_dev = pci_get_drvdata(pci_dev); struct virtio_device *vdev = &vp_dev->vdev; int ret;
if (!(vdev->config->get_status(vdev) & VIRTIO_CONFIG_S_DRIVER_OK)) return -EBUSY;
if (!__virtio_test_bit(vdev, VIRTIO_F_SR_IOV)) return -EINVAL;
if (pci_vfs_assigned(pci_dev)) return -EPERM;
if (num_vfs == 0) {
pci_disable_sriov(pci_dev); return 0;
}
ret = pci_enable_sriov(pci_dev, num_vfs); if (ret < 0) return ret;
return num_vfs;
}
staticvoid virtio_pci_reset_prepare(struct pci_dev *pci_dev)
{ struct virtio_pci_device *vp_dev = pci_get_drvdata(pci_dev); int ret = 0;
ret = virtio_device_reset_prepare(&vp_dev->vdev); if (ret) { if (ret != -EOPNOTSUPP)
dev_warn(&pci_dev->dev, "Reset prepare failure: %d",
ret); return;
}
if (pci_is_enabled(pci_dev))
pci_disable_device(pci_dev);
}
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.