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.shir...@bytedance.com> --- 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