blob: 92530a1cc90f51c944dc09e1799dd3a260cdf0e6 [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2013--2024 Intel Corporation
*/
#include <linux/cacheflush.h>
#include <linux/dma-mapping.h>
#include <linux/iova.h>
#include <linux/list.h>
#include <linux/mm.h>
#include <linux/vmalloc.h>
#include <linux/scatterlist.h>
#include <linux/slab.h>
#include <linux/types.h>
#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,
};