303 lines
6.5 KiB
C
303 lines
6.5 KiB
C
/*
|
|
* drivers/video/tegra/nvmap/nvmap.c
|
|
*
|
|
* Memory manager for Tegra GPU
|
|
*
|
|
* Copyright (c) 2009-2018, NVIDIA CORPORATION. All rights reserved.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms and conditions of the GNU General Public License,
|
|
* version 2, as published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
* more details.
|
|
*/
|
|
|
|
#define pr_fmt(fmt) "nvmap: %s() " fmt, __func__
|
|
|
|
#include <linux/err.h>
|
|
#include <linux/highmem.h>
|
|
#include <linux/io.h>
|
|
#include <linux/rbtree.h>
|
|
#include <linux/vmalloc.h>
|
|
#include <linux/wait.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/export.h>
|
|
|
|
#include <asm/pgtable.h>
|
|
#include <asm/tlbflush.h>
|
|
|
|
#include <linux/nvmap.h>
|
|
#include <trace/events/nvmap.h>
|
|
|
|
#include "nvmap_priv.h"
|
|
|
|
static phys_addr_t handle_phys(struct nvmap_handle *h)
|
|
{
|
|
if (h->heap_pgalloc)
|
|
BUG();
|
|
return h->carveout->base;
|
|
}
|
|
|
|
void *__nvmap_kmap(struct nvmap_handle *h, unsigned int pagenum)
|
|
{
|
|
phys_addr_t paddr;
|
|
unsigned long kaddr;
|
|
pgprot_t prot;
|
|
struct vm_struct *area = NULL;
|
|
|
|
if (!virt_addr_valid(h))
|
|
return NULL;
|
|
|
|
h = nvmap_handle_get(h);
|
|
if (!h)
|
|
return NULL;
|
|
|
|
if (!h->alloc)
|
|
goto put_handle;
|
|
|
|
if (!(h->heap_type & nvmap_dev->cpu_access_mask))
|
|
goto put_handle;
|
|
|
|
nvmap_kmaps_inc(h);
|
|
if (pagenum >= h->size >> PAGE_SHIFT)
|
|
goto out;
|
|
|
|
if (h->vaddr) {
|
|
kaddr = (unsigned long)h->vaddr + pagenum * PAGE_SIZE;
|
|
} else {
|
|
prot = nvmap_pgprot(h, PG_PROT_KERNEL);
|
|
area = alloc_vm_area(PAGE_SIZE, NULL);
|
|
if (!area)
|
|
goto out;
|
|
kaddr = (ulong)area->addr;
|
|
|
|
if (h->heap_pgalloc)
|
|
paddr = page_to_phys(nvmap_to_page(
|
|
h->pgalloc.pages[pagenum]));
|
|
else
|
|
paddr = h->carveout->base + pagenum * PAGE_SIZE;
|
|
|
|
ioremap_page_range(kaddr, kaddr + PAGE_SIZE, paddr, prot);
|
|
}
|
|
return (void *)kaddr;
|
|
out:
|
|
nvmap_kmaps_dec(h);
|
|
put_handle:
|
|
nvmap_handle_put(h);
|
|
return NULL;
|
|
}
|
|
|
|
void __nvmap_kunmap(struct nvmap_handle *h, unsigned int pagenum,
|
|
void *addr)
|
|
{
|
|
phys_addr_t paddr;
|
|
struct vm_struct *area = NULL;
|
|
|
|
if (!h || !h->alloc ||
|
|
WARN_ON(!virt_addr_valid(h)) ||
|
|
WARN_ON(!addr) ||
|
|
!(h->heap_type & nvmap_dev->cpu_access_mask))
|
|
return;
|
|
|
|
if (WARN_ON(pagenum >= h->size >> PAGE_SHIFT))
|
|
return;
|
|
|
|
if (h->vaddr && (h->vaddr == (addr - pagenum * PAGE_SIZE)))
|
|
goto out;
|
|
|
|
if (h->heap_pgalloc)
|
|
paddr = page_to_phys(nvmap_to_page(h->pgalloc.pages[pagenum]));
|
|
else
|
|
paddr = h->carveout->base + pagenum * PAGE_SIZE;
|
|
|
|
if (h->flags != NVMAP_HANDLE_UNCACHEABLE &&
|
|
h->flags != NVMAP_HANDLE_WRITE_COMBINE) {
|
|
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 9, 0)
|
|
__dma_flush_range(addr, addr + PAGE_SIZE);
|
|
#else
|
|
__dma_flush_area(addr, PAGE_SIZE);
|
|
#endif
|
|
outer_flush_range(paddr, paddr + PAGE_SIZE); /* FIXME */
|
|
}
|
|
|
|
area = find_vm_area(addr);
|
|
if (area)
|
|
free_vm_area(area);
|
|
else
|
|
WARN(1, "Invalid address passed");
|
|
out:
|
|
nvmap_kmaps_dec(h);
|
|
nvmap_handle_put(h);
|
|
}
|
|
|
|
void *__nvmap_mmap(struct nvmap_handle *h)
|
|
{
|
|
pgprot_t prot;
|
|
void *vaddr;
|
|
unsigned long adj_size;
|
|
struct vm_struct *v;
|
|
struct page **pages;
|
|
|
|
if (!virt_addr_valid(h))
|
|
return NULL;
|
|
|
|
h = nvmap_handle_get(h);
|
|
if (!h)
|
|
return NULL;
|
|
|
|
if (!h->alloc)
|
|
goto put_handle;
|
|
|
|
if (!(h->heap_type & nvmap_dev->cpu_access_mask))
|
|
goto put_handle;
|
|
|
|
if (h->vaddr)
|
|
return h->vaddr;
|
|
|
|
nvmap_kmaps_inc(h);
|
|
prot = nvmap_pgprot(h, PG_PROT_KERNEL);
|
|
|
|
if (h->heap_pgalloc) {
|
|
pages = nvmap_pages(h->pgalloc.pages, h->size >> PAGE_SHIFT);
|
|
if (!pages)
|
|
goto out;
|
|
|
|
vaddr = vm_map_ram(pages, h->size >> PAGE_SHIFT, -1, prot);
|
|
nvmap_altfree(pages, (h->size >> PAGE_SHIFT) * sizeof(*pages));
|
|
if (!vaddr && !h->vaddr)
|
|
goto out;
|
|
|
|
if (vaddr && atomic_long_cmpxchg(&h->vaddr, 0, (long)vaddr)) {
|
|
nvmap_kmaps_dec(h);
|
|
vm_unmap_ram(vaddr, h->size >> PAGE_SHIFT);
|
|
}
|
|
return h->vaddr;
|
|
}
|
|
|
|
/* carveout - explicitly map the pfns into a vmalloc area */
|
|
adj_size = h->carveout->base & ~PAGE_MASK;
|
|
adj_size += h->size;
|
|
adj_size = PAGE_ALIGN(adj_size);
|
|
|
|
v = alloc_vm_area(adj_size, NULL);
|
|
if (!v)
|
|
goto out;
|
|
|
|
vaddr = v->addr + (h->carveout->base & ~PAGE_MASK);
|
|
ioremap_page_range((ulong)v->addr, (ulong)v->addr + adj_size,
|
|
h->carveout->base & PAGE_MASK, prot);
|
|
|
|
if (vaddr && atomic_long_cmpxchg(&h->vaddr, 0, (long)vaddr)) {
|
|
struct vm_struct *vm;
|
|
|
|
vaddr -= (h->carveout->base & ~PAGE_MASK);
|
|
vm = find_vm_area(vaddr);
|
|
BUG_ON(!vm);
|
|
free_vm_area(vm);
|
|
nvmap_kmaps_dec(h);
|
|
}
|
|
|
|
/* leave the handle ref count incremented by 1, so that
|
|
* the handle will not be freed while the kernel mapping exists.
|
|
* nvmap_handle_put will be called by unmapping this address */
|
|
return h->vaddr;
|
|
out:
|
|
nvmap_kmaps_dec(h);
|
|
put_handle:
|
|
nvmap_handle_put(h);
|
|
return NULL;
|
|
}
|
|
|
|
void __nvmap_munmap(struct nvmap_handle *h, void *addr)
|
|
{
|
|
if (!h || !h->alloc ||
|
|
WARN_ON(!virt_addr_valid(h)) ||
|
|
WARN_ON(!addr) ||
|
|
!(h->heap_type & nvmap_dev->cpu_access_mask))
|
|
return;
|
|
|
|
nvmap_handle_put(h);
|
|
}
|
|
|
|
void nvmap_handle_put(struct nvmap_handle *h)
|
|
{
|
|
int cnt;
|
|
|
|
if (WARN_ON(!virt_addr_valid(h)))
|
|
return;
|
|
cnt = atomic_dec_return(&h->ref);
|
|
|
|
if (WARN_ON(cnt < 0)) {
|
|
pr_err("%s: %s put to negative references\n",
|
|
__func__, current->comm);
|
|
} else if (cnt == 0)
|
|
_nvmap_handle_free(h);
|
|
}
|
|
|
|
struct sg_table *__nvmap_sg_table(struct nvmap_client *client,
|
|
struct nvmap_handle *h)
|
|
{
|
|
struct sg_table *sgt = NULL;
|
|
int err, npages;
|
|
struct page **pages;
|
|
|
|
if (!virt_addr_valid(h))
|
|
return ERR_PTR(-EINVAL);
|
|
|
|
h = nvmap_handle_get(h);
|
|
if (!h)
|
|
return ERR_PTR(-EINVAL);
|
|
|
|
if (!h->alloc) {
|
|
err = -EINVAL;
|
|
goto put_handle;
|
|
}
|
|
|
|
npages = PAGE_ALIGN(h->size) >> PAGE_SHIFT;
|
|
sgt = kzalloc(sizeof(*sgt), GFP_KERNEL);
|
|
if (!sgt) {
|
|
err = -ENOMEM;
|
|
goto err;
|
|
}
|
|
|
|
if (!h->heap_pgalloc) {
|
|
phys_addr_t paddr = handle_phys(h);
|
|
struct page *page = phys_to_page(paddr);
|
|
|
|
err = sg_alloc_table(sgt, 1, GFP_KERNEL);
|
|
if (err)
|
|
goto err;
|
|
|
|
sg_set_page(sgt->sgl, page, h->size, offset_in_page(paddr));
|
|
} else {
|
|
pages = nvmap_pages(h->pgalloc.pages, npages);
|
|
if (!pages) {
|
|
err = -ENOMEM;
|
|
goto err;
|
|
}
|
|
err = sg_alloc_table_from_pages(sgt, pages,
|
|
npages, 0, h->size, GFP_KERNEL);
|
|
nvmap_altfree(pages, npages * sizeof(*pages));
|
|
if (err)
|
|
goto err;
|
|
}
|
|
nvmap_handle_put(h);
|
|
return sgt;
|
|
|
|
err:
|
|
kfree(sgt);
|
|
put_handle:
|
|
nvmap_handle_put(h);
|
|
return ERR_PTR(err);
|
|
}
|
|
|
|
void __nvmap_free_sg_table(struct nvmap_client *client,
|
|
struct nvmap_handle *h, struct sg_table *sgt)
|
|
{
|
|
sg_free_table(sgt);
|
|
kfree(sgt);
|
|
}
|