189 lines
3.7 KiB
C
189 lines
3.7 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Copyright (c) 2020 MediaTek Inc.
|
|
*/
|
|
|
|
#include <linux/errno.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/dma-direction.h>
|
|
#include <linux/scatterlist.h>
|
|
#include <linux/dma-mapping.h>
|
|
#include <linux/highmem.h>
|
|
#include <linux/kmemleak.h>
|
|
#include <asm/mman.h>
|
|
|
|
#include "reviser_cmn.h"
|
|
#include "reviser_mem.h"
|
|
#include "reviser_ioctl.h"
|
|
#include "reviser_reg.h"
|
|
|
|
static int __reviser_get_sgt(const char *buf,
|
|
size_t len, struct sg_table *sgt)
|
|
{
|
|
struct page **pages = NULL;
|
|
unsigned int nr_pages;
|
|
unsigned int index;
|
|
const char *p;
|
|
int ret;
|
|
|
|
nr_pages = DIV_ROUND_UP((unsigned long)buf + len, PAGE_SIZE)
|
|
- ((unsigned long)buf / PAGE_SIZE);
|
|
pages = kmalloc_array(nr_pages, sizeof(struct page *), GFP_KERNEL);
|
|
|
|
if (!pages)
|
|
return -ENOMEM;
|
|
|
|
p = buf - offset_in_page(buf);
|
|
LOG_DEBUG("start p: %llx buf: %llx\n",
|
|
(uint64_t)p, (uint64_t)buf);
|
|
|
|
for (index = 0; index < nr_pages; index++) {
|
|
if (is_vmalloc_addr(p))
|
|
pages[index] = vmalloc_to_page(p);
|
|
else
|
|
pages[index] = kmap_to_page((void *)p);
|
|
if (!pages[index]) {
|
|
kfree(pages);
|
|
LOG_ERR("map failed\n");
|
|
return -EFAULT;
|
|
}
|
|
p += PAGE_SIZE;
|
|
//LOG_DEBUG("p: %llx PAGE_SIZE: %llx\n",
|
|
// (uint64_t)p, (uint64_t)PAGE_SIZE);
|
|
}
|
|
|
|
|
|
ret = sg_alloc_table_from_pages(sgt, pages, index,
|
|
offset_in_page(buf), len, GFP_KERNEL);
|
|
kfree(pages);
|
|
if (ret) {
|
|
LOG_ERR("sg_alloc_table_from_pages: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
|
|
|
|
LOG_DEBUG("buf: %p, len: %lx, sgt: %p nr_pages: %d\n",
|
|
buf, len, sgt, nr_pages);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static dma_addr_t __reviser_get_iova(struct device *dev,
|
|
struct scatterlist *sg, unsigned int nents, size_t len,
|
|
dma_addr_t given_iova)
|
|
{
|
|
dma_addr_t mask;
|
|
dma_addr_t iova = 0;
|
|
int ret;
|
|
|
|
mask = (given_iova + len - 1) | BOUNDARY_MASK;
|
|
given_iova |= BOUNDARY_MASK;
|
|
|
|
LOG_DEBUG("mask: %llx given_iova: %llx\n",
|
|
(uint64_t)mask, (uint64_t)given_iova);
|
|
|
|
dma_set_mask_and_coherent(dev, mask);
|
|
|
|
if (!dev->dma_parms) {
|
|
dev->dma_parms =
|
|
devm_kzalloc(dev, sizeof(*dev->dma_parms), GFP_KERNEL);
|
|
}
|
|
if (dev->dma_parms) {
|
|
ret = dma_set_max_seg_size(dev, (unsigned int)DMA_BIT_MASK(34));
|
|
if (ret)
|
|
dev_info(dev, "Failed to set DMA segment size\n");
|
|
}
|
|
|
|
ret = dma_map_sg_attrs(dev, sg, nents,
|
|
DMA_BIDIRECTIONAL, DMA_ATTR_SKIP_CPU_SYNC);
|
|
|
|
if (ret <= 0) {
|
|
LOG_ERR("dma_map_sg_attrs: failed with %d\n", ret);
|
|
return 0;
|
|
}
|
|
|
|
iova = sg_dma_address(&sg[0]);
|
|
|
|
LOG_DEBUG("sg_dma_address: size: %lx, mapped iova: 0x%llx\n",
|
|
len, (uint64_t)iova);
|
|
|
|
|
|
return iova;
|
|
|
|
}
|
|
|
|
int reviser_mem_free(struct reviser_mem *mem)
|
|
{
|
|
kfree((void *) mem->kva);
|
|
LOG_DEBUG("Done\n");
|
|
return 0;
|
|
}
|
|
|
|
int reviser_mem_alloc(struct device *dev, struct reviser_mem *mem)
|
|
{
|
|
int ret = 0;
|
|
void *kva;
|
|
dma_addr_t iova;
|
|
|
|
kva = kvmalloc(mem->size, GFP_KERNEL);
|
|
|
|
if (!kva) {
|
|
dev_info(dev, "%s: kvmalloc: failed\n",
|
|
__func__);
|
|
ret = -ENOMEM;
|
|
goto error;
|
|
}
|
|
memset((void *)kva, 0, mem->size);
|
|
|
|
if (__reviser_get_sgt(kva, mem->size, &mem->sgt)) {
|
|
dev_info(dev, "%s: __reviser_get_sgt: failed\n",
|
|
__func__);
|
|
ret = -ENOMEM;
|
|
goto error;
|
|
}
|
|
|
|
iova = __reviser_get_iova(dev, mem->sgt.sgl, mem->sgt.nents,
|
|
mem->size, REMAP_DRAM_BASE);
|
|
|
|
if ((!iova) || ((uint32_t)iova != REMAP_DRAM_BASE)) {
|
|
LOG_ERR("iova wrong (0x%llx)\n", iova);
|
|
goto error;
|
|
}
|
|
|
|
/*
|
|
* Avoid a kmemleak false positive.
|
|
* The pointer is using for debugging,
|
|
* but it will be used by other apusys HW
|
|
*/
|
|
kmemleak_no_scan(kva);
|
|
|
|
mem->kva = (uint64_t)kva;
|
|
mem->iova = (uint32_t)iova;
|
|
|
|
LOG_INFO("mem(0x%x/%d/0x%llx)\n",
|
|
mem->iova, mem->size, mem->kva);
|
|
|
|
goto out;
|
|
|
|
error:
|
|
kvfree(kva);
|
|
out:
|
|
return ret;
|
|
|
|
}
|
|
|
|
int reviser_mem_invalidate(struct device *dev, struct reviser_mem *mem)
|
|
{
|
|
dma_sync_sg_for_cpu(dev, mem->sgt.sgl, mem->sgt.nents,
|
|
DMA_FROM_DEVICE);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|