Commit e969fed5 authored by Michael S. Tsirkin's avatar Michael S. Tsirkin Committed by Rusty Russell

virtio: refactor find_vqs

This refactors find_vqs, making it more readable and robust, and fixing
two regressions from 2.6.30:
- double free_irq causing BUG_ON on device removal
- probe failure when vq can't be assigned to msi-x vector
  (reported on old host kernels)
Tested-by: default avatarAmit Shah <amit.shah@redhat.com>
Signed-off-by: default avatarMichael S. Tsirkin <mst@redhat.com>
Signed-off-by: default avatarRusty Russell <rusty@rustcorp.com.au>
parent f6c82507
...@@ -52,8 +52,10 @@ struct virtio_pci_device ...@@ -52,8 +52,10 @@ struct virtio_pci_device
char (*msix_names)[256]; char (*msix_names)[256];
/* Number of available vectors */ /* Number of available vectors */
unsigned msix_vectors; unsigned msix_vectors;
/* Vectors allocated */ /* Vectors allocated, excluding per-vq vectors if any */
unsigned msix_used_vectors; unsigned msix_used_vectors;
/* Whether we have vector per vq */
bool per_vq_vectors;
}; };
/* Constants for MSI-X */ /* Constants for MSI-X */
...@@ -278,27 +280,24 @@ static void vp_free_vectors(struct virtio_device *vdev) ...@@ -278,27 +280,24 @@ static void vp_free_vectors(struct virtio_device *vdev)
vp_dev->msix_entries = NULL; vp_dev->msix_entries = NULL;
} }
static int vp_enable_msix(struct pci_dev *dev, struct msix_entry *entries, static int vp_request_vectors(struct virtio_device *vdev, int nvectors,
int *options, int noptions) bool per_vq_vectors)
{
int i;
for (i = 0; i < noptions; ++i)
if (!pci_enable_msix(dev, entries, options[i]))
return options[i];
return -EBUSY;
}
static int vp_request_vectors(struct virtio_device *vdev, unsigned max_vqs)
{ {
struct virtio_pci_device *vp_dev = to_vp_device(vdev); struct virtio_pci_device *vp_dev = to_vp_device(vdev);
const char *name = dev_name(&vp_dev->vdev.dev); const char *name = dev_name(&vp_dev->vdev.dev);
unsigned i, v; unsigned i, v;
int err = -ENOMEM; int err = -ENOMEM;
/* We want at most one vector per queue and one for config changes.
* Fallback to separate vectors for config and a shared for queues. if (!nvectors) {
* Finally fall back to regular interrupts. */ /* Can't allocate MSI-X vectors, use regular interrupt */
int options[] = { max_vqs + 1, 2 }; vp_dev->msix_vectors = 0;
int nvectors = max(options[0], options[1]); err = request_irq(vp_dev->pci_dev->irq, vp_interrupt,
IRQF_SHARED, name, vp_dev);
if (err)
return err;
vp_dev->intx_enabled = 1;
return 0;
}
vp_dev->msix_entries = kmalloc(nvectors * sizeof *vp_dev->msix_entries, vp_dev->msix_entries = kmalloc(nvectors * sizeof *vp_dev->msix_entries,
GFP_KERNEL); GFP_KERNEL);
...@@ -312,18 +311,12 @@ static int vp_request_vectors(struct virtio_device *vdev, unsigned max_vqs) ...@@ -312,18 +311,12 @@ static int vp_request_vectors(struct virtio_device *vdev, unsigned max_vqs)
for (i = 0; i < nvectors; ++i) for (i = 0; i < nvectors; ++i)
vp_dev->msix_entries[i].entry = i; vp_dev->msix_entries[i].entry = i;
err = vp_enable_msix(vp_dev->pci_dev, vp_dev->msix_entries, err = pci_enable_msix(vp_dev->pci_dev, vp_dev->msix_entries, nvectors);
options, ARRAY_SIZE(options)); if (err > 0)
if (err < 0) { err = -ENOSPC;
/* Can't allocate enough MSI-X vectors, use regular interrupt */
vp_dev->msix_vectors = 0;
err = request_irq(vp_dev->pci_dev->irq, vp_interrupt,
IRQF_SHARED, name, vp_dev);
if (err) if (err)
goto error; goto error;
vp_dev->intx_enabled = 1; vp_dev->msix_vectors = nvectors;
} else {
vp_dev->msix_vectors = err;
vp_dev->msix_enabled = 1; vp_dev->msix_enabled = 1;
/* Set the vector used for configuration */ /* Set the vector used for configuration */
...@@ -344,9 +337,8 @@ static int vp_request_vectors(struct virtio_device *vdev, unsigned max_vqs) ...@@ -344,9 +337,8 @@ static int vp_request_vectors(struct virtio_device *vdev, unsigned max_vqs)
err = -EBUSY; err = -EBUSY;
goto error; goto error;
} }
}
if (vp_dev->msix_vectors && vp_dev->msix_vectors != max_vqs + 1) { if (!per_vq_vectors) {
/* Shared vector for all VQs */ /* Shared vector for all VQs */
v = vp_dev->msix_used_vectors; v = vp_dev->msix_used_vectors;
snprintf(vp_dev->msix_names[v], sizeof *vp_dev->msix_names, snprintf(vp_dev->msix_names[v], sizeof *vp_dev->msix_names,
...@@ -366,13 +358,14 @@ error: ...@@ -366,13 +358,14 @@ error:
static struct virtqueue *vp_find_vq(struct virtio_device *vdev, unsigned index, static struct virtqueue *vp_find_vq(struct virtio_device *vdev, unsigned index,
void (*callback)(struct virtqueue *vq), void (*callback)(struct virtqueue *vq),
const char *name) const char *name,
u16 vector)
{ {
struct virtio_pci_device *vp_dev = to_vp_device(vdev); struct virtio_pci_device *vp_dev = to_vp_device(vdev);
struct virtio_pci_vq_info *info; struct virtio_pci_vq_info *info;
struct virtqueue *vq; struct virtqueue *vq;
unsigned long flags, size; unsigned long flags, size;
u16 num, vector; u16 num;
int err; int err;
/* Select the queue we're interested in */ /* Select the queue we're interested in */
...@@ -391,7 +384,7 @@ static struct virtqueue *vp_find_vq(struct virtio_device *vdev, unsigned index, ...@@ -391,7 +384,7 @@ static struct virtqueue *vp_find_vq(struct virtio_device *vdev, unsigned index,
info->queue_index = index; info->queue_index = index;
info->num = num; info->num = num;
info->vector = VIRTIO_MSI_NO_VECTOR; info->vector = vector;
size = PAGE_ALIGN(vring_size(num, VIRTIO_PCI_VRING_ALIGN)); size = PAGE_ALIGN(vring_size(num, VIRTIO_PCI_VRING_ALIGN));
info->queue = alloc_pages_exact(size, GFP_KERNEL|__GFP_ZERO); info->queue = alloc_pages_exact(size, GFP_KERNEL|__GFP_ZERO);
...@@ -415,22 +408,7 @@ static struct virtqueue *vp_find_vq(struct virtio_device *vdev, unsigned index, ...@@ -415,22 +408,7 @@ static struct virtqueue *vp_find_vq(struct virtio_device *vdev, unsigned index,
vq->priv = info; vq->priv = info;
info->vq = vq; info->vq = vq;
/* allocate per-vq vector if available and necessary */ if (vector != VIRTIO_MSI_NO_VECTOR) {
if (callback && vp_dev->msix_used_vectors < vp_dev->msix_vectors) {
vector = vp_dev->msix_used_vectors;
snprintf(vp_dev->msix_names[vector], sizeof *vp_dev->msix_names,
"%s-%s", dev_name(&vp_dev->vdev.dev), name);
err = request_irq(vp_dev->msix_entries[vector].vector,
vring_interrupt, 0,
vp_dev->msix_names[vector], vq);
if (err)
goto out_request_irq;
info->vector = vector;
++vp_dev->msix_used_vectors;
} else
vector = VP_MSIX_VQ_VECTOR;
if (callback && vp_dev->msix_enabled) {
iowrite16(vector, vp_dev->ioaddr + VIRTIO_MSI_QUEUE_VECTOR); iowrite16(vector, vp_dev->ioaddr + VIRTIO_MSI_QUEUE_VECTOR);
vector = ioread16(vp_dev->ioaddr + VIRTIO_MSI_QUEUE_VECTOR); vector = ioread16(vp_dev->ioaddr + VIRTIO_MSI_QUEUE_VECTOR);
if (vector == VIRTIO_MSI_NO_VECTOR) { if (vector == VIRTIO_MSI_NO_VECTOR) {
...@@ -446,11 +424,6 @@ static struct virtqueue *vp_find_vq(struct virtio_device *vdev, unsigned index, ...@@ -446,11 +424,6 @@ static struct virtqueue *vp_find_vq(struct virtio_device *vdev, unsigned index,
return vq; return vq;
out_assign: out_assign:
if (info->vector != VIRTIO_MSI_NO_VECTOR) {
free_irq(vp_dev->msix_entries[info->vector].vector, vq);
--vp_dev->msix_used_vectors;
}
out_request_irq:
vring_del_virtqueue(vq); vring_del_virtqueue(vq);
out_activate_queue: out_activate_queue:
iowrite32(0, vp_dev->ioaddr + VIRTIO_PCI_QUEUE_PFN); iowrite32(0, vp_dev->ioaddr + VIRTIO_PCI_QUEUE_PFN);
...@@ -472,9 +445,6 @@ static void vp_del_vq(struct virtqueue *vq) ...@@ -472,9 +445,6 @@ static void vp_del_vq(struct virtqueue *vq)
iowrite16(info->queue_index, vp_dev->ioaddr + VIRTIO_PCI_QUEUE_SEL); iowrite16(info->queue_index, vp_dev->ioaddr + VIRTIO_PCI_QUEUE_SEL);
if (info->vector != VIRTIO_MSI_NO_VECTOR)
free_irq(vp_dev->msix_entries[info->vector].vector, vq);
if (vp_dev->msix_enabled) { if (vp_dev->msix_enabled) {
iowrite16(VIRTIO_MSI_NO_VECTOR, iowrite16(VIRTIO_MSI_NO_VECTOR,
vp_dev->ioaddr + VIRTIO_MSI_QUEUE_VECTOR); vp_dev->ioaddr + VIRTIO_MSI_QUEUE_VECTOR);
...@@ -495,44 +465,100 @@ static void vp_del_vq(struct virtqueue *vq) ...@@ -495,44 +465,100 @@ static void vp_del_vq(struct virtqueue *vq)
/* the config->del_vqs() implementation */ /* the config->del_vqs() implementation */
static void vp_del_vqs(struct virtio_device *vdev) static void vp_del_vqs(struct virtio_device *vdev)
{ {
struct virtio_pci_device *vp_dev = to_vp_device(vdev);
struct virtqueue *vq, *n; struct virtqueue *vq, *n;
struct virtio_pci_vq_info *info;
list_for_each_entry_safe(vq, n, &vdev->vqs, list) list_for_each_entry_safe(vq, n, &vdev->vqs, list) {
info = vq->priv;
if (vp_dev->per_vq_vectors)
free_irq(vp_dev->msix_entries[info->vector].vector, vq);
vp_del_vq(vq); vp_del_vq(vq);
}
vp_dev->per_vq_vectors = false;
vp_free_vectors(vdev); vp_free_vectors(vdev);
} }
/* the config->find_vqs() implementation */ static int vp_try_to_find_vqs(struct virtio_device *vdev, unsigned nvqs,
static int vp_find_vqs(struct virtio_device *vdev, unsigned nvqs,
struct virtqueue *vqs[], struct virtqueue *vqs[],
vq_callback_t *callbacks[], vq_callback_t *callbacks[],
const char *names[]) const char *names[],
int nvectors,
bool per_vq_vectors)
{ {
int vectors = 0; struct virtio_pci_device *vp_dev = to_vp_device(vdev);
int i, err; u16 vector;
int i, err, allocated_vectors;
/* How many vectors would we like? */
for (i = 0; i < nvqs; ++i)
if (callbacks[i])
++vectors;
err = vp_request_vectors(vdev, vectors); err = vp_request_vectors(vdev, nvectors, per_vq_vectors);
if (err) if (err)
goto error_request; goto error_request;
vp_dev->per_vq_vectors = per_vq_vectors;
allocated_vectors = vp_dev->msix_used_vectors;
for (i = 0; i < nvqs; ++i) { for (i = 0; i < nvqs; ++i) {
vqs[i] = vp_find_vq(vdev, i, callbacks[i], names[i]); if (!callbacks[i] || !vp_dev->msix_enabled)
if (IS_ERR(vqs[i])) vector = VIRTIO_MSI_NO_VECTOR;
else if (vp_dev->per_vq_vectors)
vector = allocated_vectors++;
else
vector = VP_MSIX_VQ_VECTOR;
vqs[i] = vp_find_vq(vdev, i, callbacks[i], names[i], vector);
if (IS_ERR(vqs[i])) {
err = PTR_ERR(vqs[i]);
goto error_find; goto error_find;
} }
/* allocate per-vq irq if available and necessary */
if (vp_dev->per_vq_vectors && vector != VIRTIO_MSI_NO_VECTOR) {
snprintf(vp_dev->msix_names[vector], sizeof *vp_dev->msix_names,
"%s-%s", dev_name(&vp_dev->vdev.dev), names[i]);
err = request_irq(vp_dev->msix_entries[vector].vector,
vring_interrupt, 0,
vp_dev->msix_names[vector], vqs[i]);
if (err) {
vp_del_vq(vqs[i]);
goto error_find;
}
}
}
return 0; return 0;
error_find: error_find:
vp_del_vqs(vdev); vp_del_vqs(vdev);
error_request: error_request:
return PTR_ERR(vqs[i]); return err;
}
/* the config->find_vqs() implementation */
static int vp_find_vqs(struct virtio_device *vdev, unsigned nvqs,
struct virtqueue *vqs[],
vq_callback_t *callbacks[],
const char *names[])
{
int vectors = 0;
int i, uninitialized_var(err);
/* How many vectors would we like? */
for (i = 0; i < nvqs; ++i)
if (callbacks[i])
++vectors;
/* We want at most one vector per queue and one for config changes. */
err = vp_try_to_find_vqs(vdev, nvqs, vqs, callbacks, names,
vectors + 1, true);
if (!err)
return 0;
/* Fallback to separate vectors for config and a shared for queues. */
err = vp_try_to_find_vqs(vdev, nvqs, vqs, callbacks, names,
2, false);
if (!err)
return 0;
/* Finally fall back to regular interrupts. */
err = vp_try_to_find_vqs(vdev, nvqs, vqs, callbacks, names,
0, false);
return err;
} }
static struct virtio_config_ops virtio_pci_config_ops = { static struct virtio_config_ops virtio_pci_config_ops = {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment