// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (C) 2013--2024 Intel Corporation */ #include #include #include #include #include #include #include #include #include #include "ipu6.h" #include "ipu6-bus.h" #include "ipu6-dma.h" #include "ipu6-mmu.h" struct vm_info { struct list_head list; struct page **pages; dma_addr_t ipu6_iova; void *vaddr; unsigned long size; }; static struct vm_info *get_vm_info(struct ipu6_mmu *mmu, dma_addr_t iova) { struct vm_info *info, *save; list_for_each_entry_safe(info, save, &mmu->vma_list, list) { if (iova >= info->ipu6_iova && iova < (info->ipu6_iova + info->size)) return info; } return NULL; } static void __dma_clear_buffer(struct page *page, size_t size, unsigned long attrs) { void *ptr; if (!page) return; /* * Ensure that the allocated pages are zeroed, and that any data * lurking in the kernel direct-mapped region is invalidated. */ ptr = page_address(page); memset(ptr, 0, size); if ((attrs & DMA_ATTR_SKIP_CPU_SYNC) == 0) clflush_cache_range(ptr, size); } static struct page **__dma_alloc_buffer(struct device *dev, size_t size, gfp_t gfp, unsigned long attrs) { int count = PHYS_PFN(size); int array_size = count * sizeof(struct page *); struct page **pages; int i = 0; pages = kvzalloc(array_size, GFP_KERNEL); if (!pages) return NULL; gfp |= __GFP_NOWARN; while (count) { int j, order = __fls(count); pages[i] = alloc_pages(gfp, order); while (!pages[i] && order) pages[i] = alloc_pages(gfp, --order); if (!pages[i]) goto error; if (order) { split_page(pages[i], order); j = 1 << order; while (j--) pages[i + j] = pages[i] + j; } __dma_clear_buffer(pages[i], PAGE_SIZE << order, attrs); i += 1 << order; count -= 1 << order; } return pages; error: while (i--) if (pages[i]) __free_pages(pages[i], 0); kvfree(pages); return NULL; } static void __dma_free_buffer(struct device *dev, struct page **pages, size_t size, unsigned long attrs) { int count = PHYS_PFN(size); unsigned int i; for (i = 0; i < count && pages[i]; i++) { __dma_clear_buffer(pages[i], PAGE_SIZE, attrs); __free_pages(pages[i], 0); } kvfree(pages); } static void ipu6_dma_sync_single_for_cpu(struct device *dev, dma_addr_t dma_handle, size_t size, enum dma_data_direction dir) { void *vaddr; u32 offset; struct vm_info *info; struct ipu6_mmu *mmu = to_ipu6_bus_device(dev)->mmu; info = get_vm_info(mmu, dma_handle); if (WARN_ON(!info)) return; offset = dma_handle - info->ipu6_iova; if (WARN_ON(size > (info->size - offset))) return; vaddr = info->vaddr + offset; clflush_cache_range(vaddr, size); } static void ipu6_dma_sync_sg_for_cpu(struct device *dev, struct scatterlist *sglist, int nents, enum dma_data_direction dir) { struct scatterlist *sg; int i; for_each_sg(sglist, sg, nents, i) clflush_cache_range(page_to_virt(sg_page(sg)), sg->length); } static void *ipu6_dma_alloc(struct device *dev, size_t size, dma_addr_t *dma_handle, gfp_t gfp, unsigned long attrs) { struct ipu6_mmu *mmu = to_ipu6_bus_device(dev)->mmu; struct pci_dev *pdev = to_ipu6_bus_device(dev)->isp->pdev; dma_addr_t pci_dma_addr, ipu6_iova; struct vm_info *info; unsigned long count; struct page **pages; struct iova *iova; unsigned int i; int ret; info = kzalloc(sizeof(*info), GFP_KERNEL); if (!info) return NULL; size = PAGE_ALIGN(size); count = PHYS_PFN(size); iova = alloc_iova(&mmu->dmap->iovad, count, PHYS_PFN(dma_get_mask(dev)), 0); if (!iova) goto out_kfree; pages = __dma_alloc_buffer(dev, size, gfp, attrs); if (!pages) goto out_free_iova; dev_dbg(dev, "dma_alloc: size %zu iova low pfn %lu, high pfn %lu\n", size, iova->pfn_lo, iova->pfn_hi); for (i = 0; iova->pfn_lo + i <= iova->pfn_hi; i++) { pci_dma_addr = dma_map_page_attrs(&pdev->dev, pages[i], 0, PAGE_SIZE, DMA_BIDIRECTIONAL, attrs); dev_dbg(dev, "dma_alloc: mapped pci_dma_addr %pad\n", &pci_dma_addr); if (dma_mapping_error(&pdev->dev, pci_dma_addr)) { dev_err(dev, "pci_dma_mapping for page[%d] failed", i); goto out_unmap; } ret = ipu6_mmu_map(mmu->dmap->mmu_info, PFN_PHYS(iova->pfn_lo + i), pci_dma_addr, PAGE_SIZE); if (ret) { dev_err(dev, "ipu6_mmu_map for pci_dma[%d] %pad failed", i, &pci_dma_addr); dma_unmap_page_attrs(&pdev->dev, pci_dma_addr, PAGE_SIZE, DMA_BIDIRECTIONAL, attrs); goto out_unmap; } } info->vaddr = vmap(pages, count, VM_USERMAP, PAGE_KERNEL); if (!info->vaddr) goto out_unmap; *dma_handle = PFN_PHYS(iova->pfn_lo); info->pages = pages; info->ipu6_iova = *dma_handle; info->size = size; list_add(&info->list, &mmu->vma_list); return info->vaddr; out_unmap: while (i--) { ipu6_iova = PFN_PHYS(iova->pfn_lo + i); pci_dma_addr = ipu6_mmu_iova_to_phys(mmu->dmap->mmu_info, ipu6_iova); dma_unmap_page_attrs(&pdev->dev, pci_dma_addr, PAGE_SIZE, DMA_BIDIRECTIONAL, attrs); ipu6_mmu_unmap(mmu->dmap->mmu_info, ipu6_iova, PAGE_SIZE); } __dma_free_buffer(dev, pages, size, attrs); out_free_iova: __free_iova(&mmu->dmap->iovad, iova); out_kfree: kfree(info); return NULL; } static void ipu6_dma_free(struct device *dev, size_t size, void *vaddr, dma_addr_t dma_handle, unsigned long attrs) { struct ipu6_mmu *mmu = to_ipu6_bus_device(dev)->mmu; struct pci_dev *pdev = to_ipu6_bus_device(dev)->isp->pdev; struct iova *iova = find_iova(&mmu->dmap->iovad, PHYS_PFN(dma_handle)); dma_addr_t pci_dma_addr, ipu6_iova; struct vm_info *info; struct page **pages; unsigned int i; if (WARN_ON(!iova)) return; info = get_vm_info(mmu, dma_handle); if (WARN_ON(!info)) return; if (WARN_ON(!info->vaddr)) return; if (WARN_ON(!info->pages)) return; list_del(&info->list); size = PAGE_ALIGN(size); pages = info->pages; vunmap(vaddr); for (i = 0; i < PHYS_PFN(size); i++) { ipu6_iova = PFN_PHYS(iova->pfn_lo + i); pci_dma_addr = ipu6_mmu_iova_to_phys(mmu->dmap->mmu_info, ipu6_iova); dma_unmap_page_attrs(&pdev->dev, pci_dma_addr, PAGE_SIZE, DMA_BIDIRECTIONAL, attrs); } ipu6_mmu_unmap(mmu->dmap->mmu_info, PFN_PHYS(iova->pfn_lo), PFN_PHYS(iova_size(iova))); __dma_free_buffer(dev, pages, size, attrs); mmu->tlb_invalidate(mmu); __free_iova(&mmu->dmap->iovad, iova); kfree(info); } static int ipu6_dma_mmap(struct device *dev, struct vm_area_struct *vma, void *addr, dma_addr_t iova, size_t size, unsigned long attrs) { struct ipu6_mmu *mmu = to_ipu6_bus_device(dev)->mmu; size_t count = PHYS_PFN(PAGE_ALIGN(size)); struct vm_info *info; size_t i; int ret; info = get_vm_info(mmu, iova); if (!info) return -EFAULT; if (!info->vaddr) return -EFAULT; if (vma->vm_start & ~PAGE_MASK) return -EINVAL; if (size > info->size) return -EFAULT; for (i = 0; i < count; i++) { ret = vm_insert_page(vma, vma->vm_start + PFN_PHYS(i), info->pages[i]); if (ret < 0) return ret; } return 0; } static void ipu6_dma_unmap_sg(struct device *dev, struct scatterlist *sglist, int nents, enum dma_data_direction dir, unsigned long attrs) { struct pci_dev *pdev = to_ipu6_bus_device(dev)->isp->pdev; struct ipu6_mmu *mmu = to_ipu6_bus_device(dev)->mmu; struct iova *iova = find_iova(&mmu->dmap->iovad, PHYS_PFN(sg_dma_address(sglist))); int i, npages, count; struct scatterlist *sg; dma_addr_t pci_dma_addr; if (!nents) return; if (WARN_ON(!iova)) return; if ((attrs & DMA_ATTR_SKIP_CPU_SYNC) == 0) ipu6_dma_sync_sg_for_cpu(dev, sglist, nents, DMA_BIDIRECTIONAL); /* get the nents as orig_nents given by caller */ count = 0; npages = iova_size(iova); for_each_sg(sglist, sg, nents, i) { if (sg_dma_len(sg) == 0 || sg_dma_address(sg) == DMA_MAPPING_ERROR) break; npages -= PHYS_PFN(PAGE_ALIGN(sg_dma_len(sg))); count++; if (npages <= 0) break; } /* * Before IPU6 mmu unmap, return the pci dma address back to sg * assume the nents is less than orig_nents as the least granule * is 1 SZ_4K page */ dev_dbg(dev, "trying to unmap concatenated %u ents\n", count); for_each_sg(sglist, sg, count, i) { dev_dbg(dev, "ipu unmap sg[%d] %pad\n", i, &sg_dma_address(sg)); pci_dma_addr = ipu6_mmu_iova_to_phys(mmu->dmap->mmu_info, sg_dma_address(sg)); dev_dbg(dev, "return pci_dma_addr %pad back to sg[%d]\n", &pci_dma_addr, i); sg_dma_address(sg) = pci_dma_addr; } dev_dbg(dev, "ipu6_mmu_unmap low pfn %lu high pfn %lu\n", iova->pfn_lo, iova->pfn_hi); ipu6_mmu_unmap(mmu->dmap->mmu_info, PFN_PHYS(iova->pfn_lo), PFN_PHYS(iova_size(iova))); mmu->tlb_invalidate(mmu); dma_unmap_sg_attrs(&pdev->dev, sglist, nents, dir, attrs); __free_iova(&mmu->dmap->iovad, iova); } static int ipu6_dma_map_sg(struct device *dev, struct scatterlist *sglist, int nents, enum dma_data_direction dir, unsigned long attrs) { struct ipu6_mmu *mmu = to_ipu6_bus_device(dev)->mmu; struct pci_dev *pdev = to_ipu6_bus_device(dev)->isp->pdev; struct scatterlist *sg; struct iova *iova; size_t npages = 0; unsigned long iova_addr; int i, count; for_each_sg(sglist, sg, nents, i) { if (sg->offset) { dev_err(dev, "Unsupported non-zero sg[%d].offset %x\n", i, sg->offset); return -EFAULT; } } dev_dbg(dev, "pci_dma_map_sg trying to map %d ents\n", nents); count = dma_map_sg_attrs(&pdev->dev, sglist, nents, dir, attrs); if (count <= 0) { dev_err(dev, "pci_dma_map_sg %d ents failed\n", nents); return 0; } dev_dbg(dev, "pci_dma_map_sg %d ents mapped\n", count); for_each_sg(sglist, sg, count, i) npages += PHYS_PFN(PAGE_ALIGN(sg_dma_len(sg))); iova = alloc_iova(&mmu->dmap->iovad, npages, PHYS_PFN(dma_get_mask(dev)), 0); if (!iova) return 0; dev_dbg(dev, "dmamap: iova low pfn %lu, high pfn %lu\n", iova->pfn_lo, iova->pfn_hi); iova_addr = iova->pfn_lo; for_each_sg(sglist, sg, count, i) { int ret; dev_dbg(dev, "mapping entry %d: iova 0x%llx phy %pad size %d\n", i, PFN_PHYS(iova_addr), &sg_dma_address(sg), sg_dma_len(sg)); ret = ipu6_mmu_map(mmu->dmap->mmu_info, PFN_PHYS(iova_addr), sg_dma_address(sg), PAGE_ALIGN(sg_dma_len(sg))); if (ret) goto out_fail; sg_dma_address(sg) = PFN_PHYS(iova_addr); iova_addr += PHYS_PFN(PAGE_ALIGN(sg_dma_len(sg))); } if ((attrs & DMA_ATTR_SKIP_CPU_SYNC) == 0) ipu6_dma_sync_sg_for_cpu(dev, sglist, nents, DMA_BIDIRECTIONAL); return count; out_fail: ipu6_dma_unmap_sg(dev, sglist, i, dir, attrs); return 0; } /* * Create scatter-list for the already allocated DMA buffer */ static int ipu6_dma_get_sgtable(struct device *dev, struct sg_table *sgt, void *cpu_addr, dma_addr_t handle, size_t size, unsigned long attrs) { struct ipu6_mmu *mmu = to_ipu6_bus_device(dev)->mmu; struct vm_info *info; int n_pages; int ret = 0; info = get_vm_info(mmu, handle); if (!info) return -EFAULT; if (!info->vaddr) return -EFAULT; if (WARN_ON(!info->pages)) return -ENOMEM; n_pages = PHYS_PFN(PAGE_ALIGN(size)); ret = sg_alloc_table_from_pages(sgt, info->pages, n_pages, 0, size, GFP_KERNEL); if (ret) dev_warn(dev, "IPU6 get sgt table failed\n"); return ret; } const struct dma_map_ops ipu6_dma_ops = { .alloc = ipu6_dma_alloc, .free = ipu6_dma_free, .mmap = ipu6_dma_mmap, .map_sg = ipu6_dma_map_sg, .unmap_sg = ipu6_dma_unmap_sg, .sync_single_for_cpu = ipu6_dma_sync_single_for_cpu, .sync_single_for_device = ipu6_dma_sync_single_for_cpu, .sync_sg_for_cpu = ipu6_dma_sync_sg_for_cpu, .sync_sg_for_device = ipu6_dma_sync_sg_for_cpu, .get_sgtable = ipu6_dma_get_sgtable, };