From 0ea7e633ca5af79472ca326722beea376c173bb6 Mon Sep 17 00:00:00 2001 From: Sreenad Menon Date: Thu, 19 Nov 2020 22:49:04 +0530 Subject: [PATCH 1/2] virtual io: mmio: use bounce buffers for virtual io mmio devices In some cases, its possible that most of a VM's memory is not accessible to virtual io backend drivers that run in the context of a separate VM. A hypervisor could pre-share some limited memory between both the VMs. Use such pre-arranged shared memory to allow data exchange between front-end and back-end drivers. DMA ops are added to virtual io (mmio) devices, which bounces buffers to and from the pre-shared region. Change-Id: I6cf61cd1ca4c8b848a6d6d1aa1d5023ce9341174 Signed-off-by: Sreenad Menon Signed-off-by: Srivatsa Vaddagiri --- drivers/virtio/Kconfig | 12 ++ drivers/virtio/virtio_mmio.c | 283 ++++++++++++++++++++++++++++++++++- 2 files changed, 293 insertions(+), 2 deletions(-) diff --git a/drivers/virtio/Kconfig b/drivers/virtio/Kconfig index 6be003233240..393ed1ce36d1 100644 --- a/drivers/virtio/Kconfig +++ b/drivers/virtio/Kconfig @@ -148,6 +148,18 @@ config VIRTIO_MMIO If unsure, say N. +config VIRTIO_MMIO_SWIOTLB + bool "Instantiate swiotlb buffer pool for custom DMA ops" + depends on VIRTIO_MMIO + help + Activating this feature results in custom DMA ops being + setup up for virtio_mmio device, that uses swiotlb driver + APIs to bounce buffers in and out of a pre-defined memory + region. The pre-defined memory region is defined in + device-tree (/swiotlb) and is indicative of memory that is + shared with another virtual machine which is hosting virtio + backend drivers. + config VIRTIO_MMIO_CMDLINE_DEVICES bool "Memory mapped virtio devices parameter parsing" depends on VIRTIO_MMIO diff --git a/drivers/virtio/virtio_mmio.c b/drivers/virtio/virtio_mmio.c index 083ff1eb743d..7862d1b01861 100644 --- a/drivers/virtio/virtio_mmio.c +++ b/drivers/virtio/virtio_mmio.c @@ -69,7 +69,10 @@ #include #include #include - +#ifdef CONFIG_VIRTIO_MMIO_SWIOTLB +#include +#include +#endif /* The alignment to use between consumer and producer parts of vring. @@ -81,6 +84,16 @@ #define to_virtio_mmio_device(_plat_dev) \ container_of(_plat_dev, struct virtio_mmio_device, vdev) +#ifdef CONFIG_VIRTIO_MMIO_SWIOTLB +struct virtio_mem_pool { + void *virt_base; + dma_addr_t dma_base; + size_t size; + unsigned long *bitmap; + spinlock_t lock; +}; +#endif + struct virtio_mmio_device { struct virtio_device vdev; struct platform_device *pdev; @@ -91,6 +104,9 @@ struct virtio_mmio_device { /* a list of queues so we can dispatch IRQs */ spinlock_t lock; struct list_head virtqueues; +#ifdef CONFIG_VIRTIO_MMIO_SWIOTLB + struct virtio_mem_pool *mem_pool; +#endif }; struct virtio_mmio_vq_info { @@ -590,8 +606,259 @@ static void virtio_mmio_release_dev(struct device *_d) devm_kfree(&pdev->dev, vm_dev); } -/* Platform device */ +#ifdef CONFIG_VIRTIO_MMIO_SWIOTLB +static phys_addr_t virtio_swiotlb_base; +static phys_addr_t virtio_swiotlb_dma_base; +static size_t virtio_swiotlb_size; +static int get_swiotlb_base(struct device_node *np, phys_addr_t *base, + phys_addr_t *dma_base, size_t *size) +{ + const __be64 *val; + int len; + + val = of_get_property(np, "reg", &len); + if (!val || len != 16) + return -EINVAL; + + *base = __be64_to_cpup(val); + val++; + *size = __be64_to_cpup(val); + + if (!*base || !*size) + return -EINVAL; + + val = of_get_property(np, "dma_base", &len); + if (!val || len != 8) + return -EINVAL; + *dma_base = __be64_to_cpup(val); + + return 0; +} + +static int __init virtio_swiotlb_init(void) +{ + void __iomem *vbase; + int ret; + unsigned long nslabs; + phys_addr_t base; + phys_addr_t dma_base; + size_t size; + struct device_node *np; + + np = of_find_node_by_path("/swiotlb"); + if (!np) + return 0; + + ret = get_swiotlb_base(np, &base, &dma_base, &size); + of_node_put(np); + + if (ret) + return ret; + + nslabs = (size >> IO_TLB_SHIFT); + nslabs = ALIGN_DOWN(nslabs, IO_TLB_SEGSIZE); + + if (!nslabs) + return -EINVAL; + + vbase = __ioremap(base, size, __pgprot(PROT_NORMAL)); + if (!vbase) + return -EINVAL; + + ret = swiotlb_late_init_with_tblpaddr(vbase, dma_base, nslabs); + if (ret) { + iounmap(vbase); + return ret; + } + + virtio_swiotlb_base = base; + virtio_swiotlb_dma_base = dma_base; + virtio_swiotlb_size = size; + + swiotlb_force = SWIOTLB_FORCE; + + return 0; +} + +static void *virtio_alloc_coherent(struct device *dev, size_t size, + dma_addr_t *dma_handle, gfp_t flags, unsigned long attrs) +{ + struct platform_device *pdev = + container_of(dev, struct platform_device, dev); + struct virtio_mmio_device *vm_dev = platform_get_drvdata(pdev); + int pageno; + unsigned long irq_flags; + int order = get_order(size); + void *ret = NULL; + struct virtio_mem_pool *mem = vm_dev->mem_pool; + + if (!mem || (size > (mem->size << PAGE_SHIFT))) + return NULL; + + spin_lock_irqsave(&mem->lock, irq_flags); + + pageno = bitmap_find_free_region(mem->bitmap, mem->size, order); + if (pageno >= 0) { + *dma_handle = mem->dma_base + (pageno << PAGE_SHIFT); + ret = mem->virt_base + (pageno << PAGE_SHIFT); + } + + spin_unlock_irqrestore(&mem->lock, irq_flags); + + return ret; +} + + +static void virtio_free_coherent(struct device *dev, size_t size, void *vaddr, + dma_addr_t dma_handle, unsigned long attrs) +{ + struct platform_device *pdev = + container_of(dev, struct platform_device, dev); + struct virtio_mmio_device *vm_dev = platform_get_drvdata(pdev); + int pageno; + unsigned long flags; + struct virtio_mem_pool *mem = vm_dev->mem_pool; + + if (!mem) + return; + + spin_lock_irqsave(&mem->lock, flags); + if (vaddr >= mem->virt_base && + vaddr < (mem->virt_base + (mem->size << PAGE_SHIFT))) { + pageno = (vaddr - mem->virt_base) >> PAGE_SHIFT; + bitmap_release_region(mem->bitmap, pageno, get_order(size)); + } + + spin_unlock_irqrestore(&mem->lock, flags); +} + +static dma_addr_t virtio_map_page(struct device *dev, struct page *page, + unsigned long offset, size_t size, + enum dma_data_direction dir, + unsigned long attrs) +{ + phys_addr_t phys = page_to_phys(page) + offset; + + return swiotlb_map(dev, phys, size, dir, attrs); +} + +static void virtio_unmap_page(struct device *dev, dma_addr_t dev_addr, + size_t size, enum dma_data_direction dir, + unsigned long attrs) +{ + BUG_ON(!is_swiotlb_buffer(dev, dev_addr)); + + swiotlb_tbl_unmap_single(dev, dev_addr, size, dir, attrs); +} + +size_t virtio_max_mapping_size(struct device *dev) +{ + return ((size_t)1 << IO_TLB_SHIFT) * IO_TLB_SEGSIZE; +} + +static const struct dma_map_ops virtio_dma_ops = { + .alloc = virtio_alloc_coherent, + .free = virtio_free_coherent, + .map_page = virtio_map_page, + .unmap_page = virtio_unmap_page, + .max_mapping_size = virtio_max_mapping_size, +}; + +static inline int +get_ring_base(struct platform_device *pdev, phys_addr_t *ring_base, + phys_addr_t *ring_dma_base, size_t *ring_size) +{ + struct device *dev = &pdev->dev; + const __be64 *val; + int len; + + if (!virtio_swiotlb_base) + return 0; + + val = of_get_property(pdev->dev.of_node, "ring_base", &len); + if (!val || len != 24) + return 0; + + *ring_base = __be64_to_cpup(val); + val++; + *ring_dma_base = __be64_to_cpup(val); + val++; + *ring_size = __be64_to_cpup(val); + + dev_dbg(dev, "ring_base %pK ring_dma_base %pK ring_size %llx\n", + *ring_base, *ring_dma_base, *ring_size); + return 1; +} + +static int setup_virtio_dma_ops(struct platform_device *pdev) +{ + struct virtio_mmio_device *vm_dev = platform_get_drvdata(pdev); + phys_addr_t ring_base, ring_dma_base; + size_t ring_size, pages; + struct virtio_mem_pool *vmem_pool; + unsigned long bitmap_size; + + if (!vm_dev || !get_ring_base(pdev, &ring_base, + &ring_dma_base, &ring_size)) + return 0; + + vmem_pool = devm_kzalloc(&pdev->dev, sizeof(struct virtio_mem_pool), + GFP_KERNEL); + if (!vmem_pool) + return -ENOMEM; + + pages = ring_size >> PAGE_SHIFT; + if (!pages) { + pr_err("%s: Ring size too small\n", __func__); + return -EINVAL; + } + + if (ULONG_MAX / sizeof(unsigned long) < BITS_TO_LONGS(pages)) { + pr_err("%s: Ring size too large %lu\n", __func__, ring_size); + return -EINVAL; + } + + bitmap_size = BITS_TO_LONGS(pages) * sizeof(long); + vmem_pool->bitmap = devm_kzalloc(&pdev->dev, bitmap_size, GFP_KERNEL); + if (!vmem_pool->bitmap) + return -ENOMEM; + + /* Note: Mapped as 'normal/cacheable' memory */ + vmem_pool->virt_base = __ioremap(ring_base, ring_size, + __pgprot(PROT_NORMAL)); + if (!vmem_pool->virt_base) { + pr_err("Unable to ioremap %pK size %lx\n", + (void *)ring_base, ring_size); + return -ENOMEM; + } + memset(vmem_pool->virt_base, 0, ring_size); + + vmem_pool->dma_base = ring_dma_base; + vmem_pool->size = pages; + spin_lock_init(&vmem_pool->lock); + vm_dev->mem_pool = vmem_pool; + set_dma_ops(&pdev->dev, &virtio_dma_ops); + + dev_dbg(&pdev->dev, "virtio_mem_pool: virt_base %llx pages %lx\n", + vmem_pool->virt_base, pages); + return 0; +} +#else /* CONFIG_VIRTIO_MMIO_SWIOTLB */ + +static inline int setup_virtio_dma_ops(struct platform_device *pdev) +{ + return 0; +} + +static inline int virtio_swiotlb_init(void) +{ + return 0; +} + +#endif /* CONFIG_VIRTIO_MMIO_SWIOTLB */ + +/* Platform device */ static int virtio_mmio_probe(struct platform_device *pdev) { struct virtio_mmio_device *vm_dev; @@ -659,6 +926,12 @@ static int virtio_mmio_probe(struct platform_device *pdev) platform_set_drvdata(pdev, vm_dev); + rc = setup_virtio_dma_ops(pdev); + if (rc) { + put_device(&vm_dev->vdev.dev); + return rc; + } + rc = register_virtio_device(&vm_dev->vdev); if (rc) put_device(&vm_dev->vdev.dev); @@ -830,6 +1103,12 @@ static struct platform_driver virtio_mmio_driver = { static int __init virtio_mmio_init(void) { + int ret; + + ret = virtio_swiotlb_init(); + if (ret) + return ret; + return platform_driver_register(&virtio_mmio_driver); } From 2d3aafead0b8081e88db08ad233d5bb809e186f4 Mon Sep 17 00:00:00 2001 From: Sreenad Menon Date: Thu, 25 Mar 2021 16:37:47 +0530 Subject: [PATCH 2/2] virtual io: avoid setting swiotlb_force in virtual io mmio Setting swiotlb_force to SWIOTLB_FORCE would force non virtual io drivers also to use swiotlb. So avoid it. Change-Id: I419700e9f38b0b00da22c8522f94edffd2fd8f52 Signed-off-by: Sreenad Menon --- drivers/virtio/virtio_mmio.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/virtio/virtio_mmio.c b/drivers/virtio/virtio_mmio.c index 7862d1b01861..b7e5e617046e 100644 --- a/drivers/virtio/virtio_mmio.c +++ b/drivers/virtio/virtio_mmio.c @@ -676,8 +676,6 @@ static int __init virtio_swiotlb_init(void) virtio_swiotlb_dma_base = dma_base; virtio_swiotlb_size = size; - swiotlb_force = SWIOTLB_FORCE; - return 0; } @@ -754,7 +752,7 @@ static void virtio_unmap_page(struct device *dev, dma_addr_t dev_addr, size_t virtio_max_mapping_size(struct device *dev) { - return ((size_t)1 << IO_TLB_SHIFT) * IO_TLB_SEGSIZE; + return SZ_4K; } static const struct dma_map_ops virtio_dma_ops = {