[PATCH 2/2] virtio-mmio: support multiple interrupt vectors

From: Fei Li
Date: Fri Jul 19 2019 - 09:31:54 EST


Rework vm_find_vqs() to support multiple interrupt vectors for
virtio-mmio device. Considering without msi/msi-x only limited irq
routing entries (only 24) are allocated, to support more interrupts
for device, esp. virtio-net device with multi rx/tx queue pairs, this
patch requests one vector for the config change and one vector for
two continuous vqs (e.g. each rx/tx queue pair).

If failing to request multiple interrupt vectors, fall back to the
old style: request only one irq for both the config and all vqs.

Add irq_first & irq_last to store the irq information when
processing the device command line.

Signed-off-by: Fei Li <lifei.shirley@xxxxxxxxxxxxx>
---
drivers/virtio/virtio_mmio.c | 237 +++++++++++++++++++++++++++++++++++--------
1 file changed, 194 insertions(+), 43 deletions(-)

diff --git a/drivers/virtio/virtio_mmio.c b/drivers/virtio/virtio_mmio.c
index 9b42502b2204..92d16c86ea8f 100644
--- a/drivers/virtio/virtio_mmio.c
+++ b/drivers/virtio/virtio_mmio.c
@@ -75,7 +75,7 @@
* Currently hardcoded to the page size. */
#define VIRTIO_MMIO_VRING_ALIGN PAGE_SIZE

-
+#define VQ_NAME_LEN 20

#define to_virtio_mmio_device(_plat_dev) \
container_of(_plat_dev, struct virtio_mmio_device, vdev)
@@ -90,6 +90,9 @@ struct virtio_mmio_device {
/* a list of queues so we can dispatch IRQs */
spinlock_t lock;
struct list_head virtqueues;
+
+ /* Add name for each virtqueue, can be used for the callback. */
+ char *vq_names;
};

struct virtio_mmio_vq_info {
@@ -279,7 +282,16 @@ static bool vm_notify(struct virtqueue *vq)
return true;
}

-/* Notify all virtqueues on an interrupt. */
+/* Only handle the config change, then return. */
+static irqreturn_t vm_config_changed(int irq, void *opaque)
+{
+ struct virtio_mmio_device *vm_dev = opaque;
+
+ virtio_config_changed(&vm_dev->vdev);
+ return IRQ_HANDLED;
+}
+
+/* For old style: only one interrupt for both the config and all virtqueues. */
static irqreturn_t vm_interrupt(int irq, void *opaque)
{
struct virtio_mmio_device *vm_dev = opaque;
@@ -336,11 +348,31 @@ static void vm_del_vqs(struct virtio_device *vdev)
{
struct virtio_mmio_device *vm_dev = to_virtio_mmio_device(vdev);
struct virtqueue *vq, *n;
+ unsigned int start;
+ int i = 0, shared = 0;
+ struct resource *res = platform_get_resource(vm_dev->pdev,
+ IORESOURCE_IRQ, 0);

- list_for_each_entry_safe(vq, n, &vdev->vqs, list)
- vm_del_vq(vq);
+ if (res == NULL)
+ return;
+ start = res->start;
+ if (res->end == start) {
+ free_irq(start, vm_dev);
+ } else {
+ /* Try to free_irq for vq[i] */
+ list_for_each_entry_safe(vq, n, &vdev->vqs, list) {
+ free_irq(start + shared + 1, vq);
+ if (i % 2 != 0)
+ shared++;
+ vm_del_vq(vq);
+ i++;
+ }
+ /* Try to free_irq for config */
+ free_irq(start, vdev);
+ }

- free_irq(platform_get_irq(vm_dev->pdev, 0), vm_dev);
+ kfree(vm_dev->vq_names);
+ vm_dev->vq_names = NULL;
}

static struct virtqueue *vm_setup_vq(struct virtio_device *vdev, unsigned index,
@@ -453,26 +485,66 @@ static struct virtqueue *vm_setup_vq(struct virtio_device *vdev, unsigned index,
return ERR_PTR(err);
}

-static int vm_find_vqs(struct virtio_device *vdev, unsigned nvqs,
- struct virtqueue *vqs[],
- vq_callback_t *callbacks[],
- const char * const names[],
- const bool *ctx,
- struct irq_affinity *desc)
+static int vm_request_multi_vectors(struct virtio_device *vdev,
+ unsigned int start, int nvectors)
{
struct virtio_mmio_device *vm_dev = to_virtio_mmio_device(vdev);
- int irq = platform_get_irq(vm_dev->pdev, 0);
- int i, err, queue_idx = 0;
+ int err = -ENOMEM;

- if (irq < 0) {
- dev_err(&vdev->dev, "Cannot get IRQ resource\n");
- return irq;
- }
+ vm_dev->vq_names = kmalloc(nvectors * VQ_NAME_LEN, GFP_KERNEL);
+ if (!vm_dev->vq_names)
+ goto error;

- err = request_irq(irq, vm_interrupt, IRQF_SHARED,
- dev_name(&vdev->dev), vm_dev);
+ /* Firstly, request one irq vector for config */
+ snprintf(vm_dev->vq_names, VQ_NAME_LEN,
+ "%s-config", dev_name(&vdev->dev));
+ err = request_irq(start, vm_config_changed, 0,
+ vm_dev->vq_names, vm_dev);
if (err)
- return err;
+ goto error;
+
+ return 0;
+error:
+ vm_del_vqs(vdev);
+ return err;
+}
+
+static int vm_try_to_find_vqs(struct virtio_device *vdev, unsigned int nvqs,
+ struct virtqueue *vqs[],
+ vq_callback_t *callbacks[],
+ const char * const names[],
+ const bool *ctx,
+ struct irq_affinity *desc,
+ bool multi_vectors)
+{
+ struct virtio_mmio_device *vm_dev = to_virtio_mmio_device(vdev);
+ unsigned int start, shared = 0;
+ int i, err = 0, nvectors = 0, queue_idx = 0;
+ struct resource *res = platform_get_resource(vm_dev->pdev,
+ IORESOURCE_IRQ, 0);
+
+ if (res == NULL)
+ return -EINVAL;
+
+ start = res->start;
+ if (!multi_vectors) {
+ /* Old style: only one interrupt for config and all vqs. */
+ err = request_irq(start, vm_interrupt, IRQF_SHARED,
+ dev_name(&vdev->dev), vm_dev);
+ if (err)
+ goto error_request;
+ res->end = start;
+ } else {
+ /* Optimizing: one for config change, one per vq pair. */
+ nvectors = 1;
+ for (i = 0; i < nvqs; i++)
+ if (callbacks[i])
+ ++nvectors;
+
+ err = vm_request_multi_vectors(vdev, start, nvectors);
+ if (err)
+ goto error_request;
+ }

for (i = 0; i < nvqs; ++i) {
if (!names[i]) {
@@ -483,14 +555,65 @@ static int vm_find_vqs(struct virtio_device *vdev, unsigned nvqs,
vqs[i] = vm_setup_vq(vdev, queue_idx++, callbacks[i], names[i],
ctx ? ctx[i] : false);
if (IS_ERR(vqs[i])) {
- vm_del_vqs(vdev);
- return PTR_ERR(vqs[i]);
+ err = PTR_ERR(vqs[i]);
+ goto error_vq;
}
+
+ /* Do not request_irq for vq without a callback, e.i. control */
+ if (!callbacks[i])
+ continue;
+
+ /* If multi-vectors not supported: don't request more vectors */
+ if (start == res->end)
+ break;
+
+ /* For multi-vectors: choose vq as the dev_id for request_irq */
+ snprintf(vm_dev->vq_names + (i + 1) * VQ_NAME_LEN, VQ_NAME_LEN,
+ "%s-%s", dev_name(&vdev->dev), names[i]);
+ err = request_irq(start + shared + 1, vring_interrupt,
+ IRQF_SHARED,
+ vm_dev->vq_names + (i + 1) * VQ_NAME_LEN,
+ vqs[i]);
+ if (err)
+ goto error_vq;
+
+ if (i % 2 != 0)
+ shared += 1;
}
+ return err;
+error_vq:
+ vm_del_vqs(vdev);
+error_request:
+ return err;
+}

- return 0;
+static int vm_find_vqs(struct virtio_device *vdev, unsigned int nvqs,
+ struct virtqueue *vqs[],
+ vq_callback_t *callbacks[],
+ const char * const names[],
+ const bool *ctx,
+ struct irq_affinity *desc)
+{
+ struct virtio_mmio_device *vm_dev = to_virtio_mmio_device(vdev);
+ struct resource *res = platform_get_resource(vm_dev->pdev,
+ IORESOURCE_IRQ, 0);
+
+ if (res == NULL)
+ return -EINVAL;
+ /* If supports multi-vectors: request more vectors for config and vqs */
+ if (res->start < res->end)
+ if (!vm_try_to_find_vqs(vdev, nvqs, vqs, callbacks,
+ names, ctx, desc, true))
+ return 0;
+ /* Only request one vector for both the config and all vqs:
+ * 1. Handle for devices not supporting multi vectors;
+ * 2. Fallback to the old style in case requesting multi-vectors failed
+ */
+ return vm_try_to_find_vqs(vdev, nvqs, vqs, callbacks, names,
+ ctx, desc, false);
}

+
static const char *vm_bus_name(struct virtio_device *vdev)
{
struct virtio_mmio_device *vm_dev = to_virtio_mmio_device(vdev);
@@ -637,32 +760,42 @@ static int vm_cmdline_set(const char *device,
struct resource resources[2] = {};
char *str;
long long int base, size;
- unsigned int irq;
+ unsigned int irq_first, irq_last;
int processed, consumed = 0;
struct platform_device *pdev;

/* Consume "size" part of the command line parameter */
size = memparse(device, &str);

- /* Get "@<base>:<irq>[:<id>]" chunks */
- processed = sscanf(str, "@%lli:%u%n:%d%n",
- &base, &irq, &consumed,
- &vm_cmdline_id, &consumed);
-
- /*
- * sscanf() must processes at least 2 chunks; also there
- * must be no extra characters after the last chunk, so
- * str[consumed] must be '\0'
- */
- if (processed < 2 || str[consumed])
- return -EINVAL;
+ if (strchr(str, '[') == NULL && strchr(str, ']') == NULL) {
+ /* For old style: parse as "@<base>:<irq>[:<id>]" chunks */
+ processed = sscanf(str, "@%lli:%u%n:%d%n",
+ &base, &irq_first, &consumed,
+ &vm_cmdline_id, &consumed);
+ /*
+ * sscanf() must processes at least 2 chunks; also there
+ * must be no extra characters after the last chunk, so
+ * str[consumed] must be '\0'
+ */
+ if (processed < 2 || str[consumed])
+ return -EINVAL;
+ irq_last = irq_first;
+ } else {
+ /* For multi-vectors: @<base>:[<irq_first>-<irq_last>][:<id>] */
+ processed = sscanf(str, "@%lli:[%u-%u]%n:%d%n",
+ &base, &irq_first, &irq_last, &consumed,
+ &vm_cmdline_id, &consumed);
+ if (processed < 3 || str[consumed])
+ return -EINVAL;
+ }

resources[0].flags = IORESOURCE_MEM;
resources[0].start = base;
resources[0].end = base + size - 1;

resources[1].flags = IORESOURCE_IRQ;
- resources[1].start = resources[1].end = irq;
+ resources[1].start = irq_first;
+ resources[1].end = irq_last;

if (!vm_cmdline_parent_registered) {
err = device_register(&vm_cmdline_parent);
@@ -673,11 +806,19 @@ static int vm_cmdline_set(const char *device,
vm_cmdline_parent_registered = 1;
}

- pr_info("Registering device virtio-mmio.%d at 0x%llx-0x%llx, IRQ %d.\n",
- vm_cmdline_id,
- (unsigned long long)resources[0].start,
- (unsigned long long)resources[0].end,
- (int)resources[1].start);
+ if (resources[1].end > resources[1].start)
+ pr_info("Registering device virtio-mmio.%d at 0x%llx-0x%llx, IRQs %d-%d.\n",
+ vm_cmdline_id,
+ (unsigned long long)resources[0].start,
+ (unsigned long long)resources[0].end,
+ (int)resources[1].start,
+ (int)resources[1].end);
+ else
+ pr_info("Registering device virtio-mmio.%d at 0x%llx-0x%llx, IRQ %d.\n",
+ vm_cmdline_id,
+ (unsigned long long)resources[0].start,
+ (unsigned long long)resources[0].end,
+ (int)resources[1].start);

pdev = platform_device_register_resndata(&vm_cmdline_parent,
"virtio-mmio", vm_cmdline_id++,
@@ -692,7 +833,17 @@ static int vm_cmdline_get_device(struct device *dev, void *data)
unsigned int len = strlen(buffer);
struct platform_device *pdev = to_platform_device(dev);

- snprintf(buffer + len, PAGE_SIZE - len, "0x%llx@0x%llx:%llu:%d\n",
+ if (pdev->resource[1].end > pdev->resource[1].start)
+ snprintf(buffer + len, PAGE_SIZE - len,
+ "0x%llx@0x%llx:[%llu-%llu]:%d\n",
+ pdev->resource[0].end - pdev->resource[0].start + 1ULL,
+ (unsigned long long)pdev->resource[0].start,
+ (unsigned long long)pdev->resource[1].start,
+ (unsigned long long)pdev->resource[1].end,
+ pdev->id);
+ else
+ snprintf(buffer + len, PAGE_SIZE - len,
+ "0x%llx@0x%llx:%llu:%d\n",
pdev->resource[0].end - pdev->resource[0].start + 1ULL,
(unsigned long long)pdev->resource[0].start,
(unsigned long long)pdev->resource[1].start,
--
2.11.0