diff --git a/include/linux/device.h b/include/linux/device.h index b34047520407..3e7e81cd80f8 100644 --- a/include/linux/device.h +++ b/include/linux/device.h @@ -1053,6 +1053,7 @@ struct device { struct cma *cma_area; /* contiguous memory area for dma allocations */ #endif + struct removed_region *removed_mem; /* arch specific additions */ struct dev_archdata archdata; diff --git a/kernel/dma/Makefile b/kernel/dma/Makefile index 6de44e4eb454..c6d0040e2a66 100644 --- a/kernel/dma/Makefile +++ b/kernel/dma/Makefile @@ -2,7 +2,7 @@ obj-$(CONFIG_HAS_DMA) += mapping.o obj-$(CONFIG_DMA_CMA) += contiguous.o -obj-$(CONFIG_HAVE_GENERIC_DMA_COHERENT) += coherent.o +obj-$(CONFIG_HAVE_GENERIC_DMA_COHERENT) += coherent.o removed.o obj-$(CONFIG_DMA_DIRECT_OPS) += direct.o obj-$(CONFIG_DMA_NONCOHERENT_OPS) += noncoherent.o obj-$(CONFIG_DMA_VIRT_OPS) += virt.o diff --git a/kernel/dma/removed.c b/kernel/dma/removed.c new file mode 100644 index 000000000000..bb4024a3195d --- /dev/null +++ b/kernel/dma/removed.c @@ -0,0 +1,272 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2013-2014,2018 The Linux Foundation. All rights reserved. + * Copyright (C) 2000-2004 Russell King + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct removed_region { + phys_addr_t base; + int nr_pages; + unsigned long *bitmap; + struct mutex lock; +}; + +#define NO_KERNEL_MAPPING_DUMMY 0x2222 + +static int dma_init_removed_memory(phys_addr_t phys_addr, size_t size, + struct removed_region **mem) +{ + struct removed_region *dma_mem = NULL; + int pages = size >> PAGE_SHIFT; + int bitmap_size = BITS_TO_LONGS(pages) * sizeof(long); + + dma_mem = kzalloc(sizeof(struct removed_region), GFP_KERNEL); + if (!dma_mem) + goto out; + dma_mem->bitmap = kzalloc(bitmap_size, GFP_KERNEL); + if (!dma_mem->bitmap) + goto free1_out; + + dma_mem->base = phys_addr; + dma_mem->nr_pages = pages; + mutex_init(&dma_mem->lock); + + *mem = dma_mem; + + return 0; + +free1_out: + kfree(dma_mem); +out: + return -ENOMEM; +} + +static int dma_assign_removed_region(struct device *dev, + struct removed_region *mem) +{ + if (dev->removed_mem) + return -EBUSY; + + dev->removed_mem = mem; + return 0; +} + +void *removed_alloc(struct device *dev, size_t size, dma_addr_t *handle, + gfp_t gfp, unsigned long attrs) +{ + bool no_kernel_mapping = attrs & DMA_ATTR_NO_KERNEL_MAPPING; + bool skip_zeroing = attrs & DMA_ATTR_SKIP_ZEROING; + int pageno; + unsigned long order; + void __iomem *addr = NULL; + struct removed_region *dma_mem = dev->removed_mem; + int nbits; + unsigned int align; + + if (!gfpflags_allow_blocking(gfp)) + return NULL; + + size = PAGE_ALIGN(size); + nbits = size >> PAGE_SHIFT; + order = get_order(size); + + if (order > get_order(SZ_1M)) + order = get_order(SZ_1M); + + align = (1 << order) - 1; + + + mutex_lock(&dma_mem->lock); + pageno = bitmap_find_next_zero_area(dma_mem->bitmap, dma_mem->nr_pages, + 0, nbits, align); + + if (pageno < dma_mem->nr_pages) { + phys_addr_t base = dma_mem->base + pageno * PAGE_SIZE; + *handle = base; + + bitmap_set(dma_mem->bitmap, pageno, nbits); + + if (no_kernel_mapping && skip_zeroing) { + addr = (void *)NO_KERNEL_MAPPING_DUMMY; + goto out; + } + + addr = ioremap_wc(base, size); + if (WARN_ON(!addr)) { + bitmap_clear(dma_mem->bitmap, pageno, nbits); + } else { + if (!skip_zeroing) + memset_io(addr, 0, size); + if (no_kernel_mapping) { + iounmap(addr); + addr = (void *)NO_KERNEL_MAPPING_DUMMY; + } + *handle = base; + } + } + +out: + mutex_unlock(&dma_mem->lock); + return addr; +} + + +int removed_mmap(struct device *dev, struct vm_area_struct *vma, + void *cpu_addr, dma_addr_t dma_addr, size_t size, + unsigned long attrs) +{ + return -ENXIO; +} + +void removed_free(struct device *dev, size_t size, void *cpu_addr, + dma_addr_t handle, unsigned long attrs) +{ + bool no_kernel_mapping = attrs & DMA_ATTR_NO_KERNEL_MAPPING; + struct removed_region *dma_mem = dev->removed_mem; + + size = PAGE_ALIGN(size); + if (!no_kernel_mapping) + iounmap(cpu_addr); + mutex_lock(&dma_mem->lock); + bitmap_clear(dma_mem->bitmap, (handle - dma_mem->base) >> PAGE_SHIFT, + size >> PAGE_SHIFT); + mutex_unlock(&dma_mem->lock); +} + +static dma_addr_t removed_map_page(struct device *dev, struct page *page, + unsigned long offset, size_t size, + enum dma_data_direction dir, + unsigned long attrs) +{ + return ~(dma_addr_t)0; +} + +static void removed_unmap_page(struct device *dev, dma_addr_t dma_handle, + size_t size, enum dma_data_direction dir, + unsigned long attrs) +{ +} + +static int removed_map_sg(struct device *dev, struct scatterlist *sg, + int nents, enum dma_data_direction dir, + unsigned long attrs) +{ + return 0; +} + +static void removed_unmap_sg(struct device *dev, + struct scatterlist *sg, int nents, + enum dma_data_direction dir, + unsigned long attrs) +{ +} + +static void removed_sync_single_for_cpu(struct device *dev, + dma_addr_t dma_handle, size_t size, + enum dma_data_direction dir) +{ +} + +void removed_sync_single_for_device(struct device *dev, + dma_addr_t dma_handle, size_t size, + enum dma_data_direction dir) +{ +} + +void removed_sync_sg_for_cpu(struct device *dev, + struct scatterlist *sg, int nents, + enum dma_data_direction dir) +{ +} + +void removed_sync_sg_for_device(struct device *dev, + struct scatterlist *sg, int nents, + enum dma_data_direction dir) +{ +} + +static void __iomem *removed_remap(struct device *dev, void *cpu_addr, + dma_addr_t handle, size_t size, unsigned long attrs) +{ + return ioremap_wc(handle, size); +} + +void removed_unremap(struct device *dev, void *remapped_address, size_t size) +{ + iounmap(remapped_address); +} + +const struct dma_map_ops removed_dma_ops = { + .alloc = removed_alloc, + .free = removed_free, + .mmap = removed_mmap, + .map_page = removed_map_page, + .unmap_page = removed_unmap_page, + .map_sg = removed_map_sg, + .unmap_sg = removed_unmap_sg, + .sync_single_for_cpu = removed_sync_single_for_cpu, + .sync_single_for_device = removed_sync_single_for_device, + .sync_sg_for_cpu = removed_sync_sg_for_cpu, + .sync_sg_for_device = removed_sync_sg_for_device, + .remap = removed_remap, + .unremap = removed_unremap, +}; +EXPORT_SYMBOL(removed_dma_ops); + +#ifdef CONFIG_OF_RESERVED_MEM +#include +#include +#include + +static int rmem_dma_device_init(struct reserved_mem *rmem, struct device *dev) +{ + struct removed_region *mem = rmem->priv; + + if (!mem && dma_init_removed_memory(rmem->base, rmem->size, &mem)) { + pr_info("Reserved memory: failed to init DMA memory pool at %pa, size %ld MiB\n", + &rmem->base, (unsigned long)rmem->size / SZ_1M); + return -EINVAL; + } + set_dma_ops(dev, &removed_dma_ops); + rmem->priv = mem; + dma_assign_removed_region(dev, mem); + return 0; +} + +static void rmem_dma_device_release(struct reserved_mem *rmem, + struct device *dev) +{ + dev->dma_mem = NULL; +} + +static const struct reserved_mem_ops removed_mem_ops = { + .device_init = rmem_dma_device_init, + .device_release = rmem_dma_device_release, +}; + +static int __init removed_dma_setup(struct reserved_mem *rmem) +{ + rmem->ops = &removed_mem_ops; + pr_info("Removed memory: created DMA memory pool at %pa, size %ld MiB\n", + &rmem->base, (unsigned long)rmem->size / SZ_1M); + return 0; +} +RESERVEDMEM_OF_DECLARE(dma, "removed-dma-pool", removed_dma_setup); +#endif