| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Copyright (C) 2009-2010 PetaLogix |
| * Copyright (C) 2006 Benjamin Herrenschmidt, IBM Corporation |
| * |
| * Provide default implementations of the DMA mapping callbacks for |
| * directly mapped busses. |
| */ |
| |
| #include <linux/device.h> |
| #include <linux/dma-mapping.h> |
| #include <linux/gfp.h> |
| #include <linux/dma-debug.h> |
| #include <linux/export.h> |
| #include <linux/bug.h> |
| #include <asm/cacheflush.h> |
| |
| static void *dma_nommu_alloc_coherent(struct device *dev, size_t size, |
| dma_addr_t *dma_handle, gfp_t flag, |
| unsigned long attrs) |
| { |
| return consistent_alloc(flag, size, dma_handle); |
| } |
| |
| static void dma_nommu_free_coherent(struct device *dev, size_t size, |
| void *vaddr, dma_addr_t dma_handle, |
| unsigned long attrs) |
| { |
| consistent_free(size, vaddr); |
| } |
| |
| static inline void __dma_sync(unsigned long paddr, |
| size_t size, enum dma_data_direction direction) |
| { |
| switch (direction) { |
| case DMA_TO_DEVICE: |
| case DMA_BIDIRECTIONAL: |
| flush_dcache_range(paddr, paddr + size); |
| break; |
| case DMA_FROM_DEVICE: |
| invalidate_dcache_range(paddr, paddr + size); |
| break; |
| default: |
| BUG(); |
| } |
| } |
| |
| static int dma_nommu_map_sg(struct device *dev, struct scatterlist *sgl, |
| int nents, enum dma_data_direction direction, |
| unsigned long attrs) |
| { |
| struct scatterlist *sg; |
| int i; |
| |
| /* FIXME this part of code is untested */ |
| for_each_sg(sgl, sg, nents, i) { |
| sg->dma_address = sg_phys(sg); |
| |
| if (attrs & DMA_ATTR_SKIP_CPU_SYNC) |
| continue; |
| |
| __dma_sync(sg_phys(sg), sg->length, direction); |
| } |
| |
| return nents; |
| } |
| |
| static inline dma_addr_t dma_nommu_map_page(struct device *dev, |
| struct page *page, |
| unsigned long offset, |
| size_t size, |
| enum dma_data_direction direction, |
| unsigned long attrs) |
| { |
| if (!(attrs & DMA_ATTR_SKIP_CPU_SYNC)) |
| __dma_sync(page_to_phys(page) + offset, size, direction); |
| return page_to_phys(page) + offset; |
| } |
| |
| static inline void dma_nommu_unmap_page(struct device *dev, |
| dma_addr_t dma_address, |
| size_t size, |
| enum dma_data_direction direction, |
| unsigned long attrs) |
| { |
| /* There is not necessary to do cache cleanup |
| * |
| * phys_to_virt is here because in __dma_sync_page is __virt_to_phys and |
| * dma_address is physical address |
| */ |
| if (!(attrs & DMA_ATTR_SKIP_CPU_SYNC)) |
| __dma_sync(dma_address, size, direction); |
| } |
| |
| static inline void |
| dma_nommu_sync_single_for_cpu(struct device *dev, |
| dma_addr_t dma_handle, size_t size, |
| enum dma_data_direction direction) |
| { |
| /* |
| * It's pointless to flush the cache as the memory segment |
| * is given to the CPU |
| */ |
| |
| if (direction == DMA_FROM_DEVICE) |
| __dma_sync(dma_handle, size, direction); |
| } |
| |
| static inline void |
| dma_nommu_sync_single_for_device(struct device *dev, |
| dma_addr_t dma_handle, size_t size, |
| enum dma_data_direction direction) |
| { |
| /* |
| * It's pointless to invalidate the cache if the device isn't |
| * supposed to write to the relevant region |
| */ |
| |
| if (direction == DMA_TO_DEVICE) |
| __dma_sync(dma_handle, size, direction); |
| } |
| |
| static inline void |
| dma_nommu_sync_sg_for_cpu(struct device *dev, |
| struct scatterlist *sgl, int nents, |
| enum dma_data_direction direction) |
| { |
| struct scatterlist *sg; |
| int i; |
| |
| /* FIXME this part of code is untested */ |
| if (direction == DMA_FROM_DEVICE) |
| for_each_sg(sgl, sg, nents, i) |
| __dma_sync(sg->dma_address, sg->length, direction); |
| } |
| |
| static inline void |
| dma_nommu_sync_sg_for_device(struct device *dev, |
| struct scatterlist *sgl, int nents, |
| enum dma_data_direction direction) |
| { |
| struct scatterlist *sg; |
| int i; |
| |
| /* FIXME this part of code is untested */ |
| if (direction == DMA_TO_DEVICE) |
| for_each_sg(sgl, sg, nents, i) |
| __dma_sync(sg->dma_address, sg->length, direction); |
| } |
| |
| static |
| int dma_nommu_mmap_coherent(struct device *dev, struct vm_area_struct *vma, |
| void *cpu_addr, dma_addr_t handle, size_t size, |
| unsigned long attrs) |
| { |
| #ifdef CONFIG_MMU |
| unsigned long user_count = vma_pages(vma); |
| unsigned long count = PAGE_ALIGN(size) >> PAGE_SHIFT; |
| unsigned long off = vma->vm_pgoff; |
| unsigned long pfn; |
| |
| if (off >= count || user_count > (count - off)) |
| return -ENXIO; |
| |
| vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); |
| pfn = consistent_virt_to_pfn(cpu_addr); |
| return remap_pfn_range(vma, vma->vm_start, pfn + off, |
| vma->vm_end - vma->vm_start, vma->vm_page_prot); |
| #else |
| return -ENXIO; |
| #endif |
| } |
| |
| const struct dma_map_ops dma_nommu_ops = { |
| .alloc = dma_nommu_alloc_coherent, |
| .free = dma_nommu_free_coherent, |
| .mmap = dma_nommu_mmap_coherent, |
| .map_sg = dma_nommu_map_sg, |
| .map_page = dma_nommu_map_page, |
| .unmap_page = dma_nommu_unmap_page, |
| .sync_single_for_cpu = dma_nommu_sync_single_for_cpu, |
| .sync_single_for_device = dma_nommu_sync_single_for_device, |
| .sync_sg_for_cpu = dma_nommu_sync_sg_for_cpu, |
| .sync_sg_for_device = dma_nommu_sync_sg_for_device, |
| }; |
| EXPORT_SYMBOL(dma_nommu_ops); |