securecrt 1672c9446f msm: kgsl: Optimize page_alloc allocations
User memory needs to be zeroed out before it is sent to the user.
To do this, the kernel maps the page, memsets it to zero and then
unmaps it.  By virtue of mapping it, this forces us to flush the
dcache to ensure cache coherency between kernel and user mappings.
Originally, the page_alloc loop was using GFP_ZERO (which does a
map, memset, and unmap for each individual page) and then we were
additionally calling flush_dcache_page() for each page killing us
on performance.  It is far more efficient, especially for large
allocations (> 1MB), to allocate the pages without GFP_ZERO and
then to vmap the entire allocation, memset it to zero, flush the
cache and then unmap. This process is slightly slower for very
small allocations, but only by a few microseconds, and is well
within the margin of acceptability. In all, the new scheme is
faster than the default for all sizes greater than 16k, and is
almost 4X faster for 2MB and 4MB allocations which are common for
textures and very large buffer objects.

The downside is that if there isn't enough vmalloc room for the
allocation that we are forced to fallback to a slow page by
page memset/flush, but this should happen rarely (if at all) and
is only included for completeness.
2012-07-26 14:45:24 +08:00

889 lines
22 KiB
C
Executable File

/* Copyright (c) 2002,2007-2012, Code Aurora Forum. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that 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.
*
*/
#include <linux/module.h>
#include <linux/vmalloc.h>
#include <linux/memory_alloc.h>
#include <asm/cacheflush.h>
#include <linux/slab.h>
#include <linux/kmemleak.h>
#include <linux/highmem.h>
#include "kgsl.h"
#include "kgsl_sharedmem.h"
#include "kgsl_cffdump.h"
#include "kgsl_device.h"
#include "adreno_ringbuffer.h"
/* An attribute for showing per-process memory statistics */
struct kgsl_mem_entry_attribute {
struct attribute attr;
int memtype;
ssize_t (*show)(struct kgsl_process_private *priv,
int type, char *buf);
};
#define to_mem_entry_attr(a) \
container_of(a, struct kgsl_mem_entry_attribute, attr)
#define __MEM_ENTRY_ATTR(_type, _name, _show) \
{ \
.attr = { .name = __stringify(_name), .mode = 0444 }, \
.memtype = _type, \
.show = _show, \
}
/*
* A structure to hold the attributes for a particular memory type.
* For each memory type in each process we store the current and maximum
* memory usage and display the counts in sysfs. This structure and
* the following macro allow us to simplify the definition for those
* adding new memory types
*/
struct mem_entry_stats {
int memtype;
struct kgsl_mem_entry_attribute attr;
struct kgsl_mem_entry_attribute max_attr;
};
#define MEM_ENTRY_STAT(_type, _name) \
{ \
.memtype = _type, \
.attr = __MEM_ENTRY_ATTR(_type, _name, mem_entry_show), \
.max_attr = __MEM_ENTRY_ATTR(_type, _name##_max, \
mem_entry_max_show), \
}
/*
* One page allocation for a guard region to protect against over-zealous
* GPU pre-fetch
*/
static struct page *kgsl_guard_page;
/**
* Given a kobj, find the process structure attached to it
*/
static struct kgsl_process_private *
_get_priv_from_kobj(struct kobject *kobj)
{
struct kgsl_process_private *private;
unsigned long name;
if (!kobj)
return NULL;
if (sscanf(kobj->name, "%ld", &name) != 1)
return NULL;
list_for_each_entry(private, &kgsl_driver.process_list, list) {
if (private->pid == name)
return private;
}
return NULL;
}
/**
* Show the current amount of memory allocated for the given memtype
*/
static ssize_t
mem_entry_show(struct kgsl_process_private *priv, int type, char *buf)
{
return snprintf(buf, PAGE_SIZE, "%d\n", priv->stats[type].cur);
}
/**
* Show the maximum memory allocated for the given memtype through the life of
* the process
*/
static ssize_t
mem_entry_max_show(struct kgsl_process_private *priv, int type, char *buf)
{
return snprintf(buf, PAGE_SIZE, "%d\n", priv->stats[type].max);
}
static void mem_entry_sysfs_release(struct kobject *kobj)
{
}
static ssize_t mem_entry_sysfs_show(struct kobject *kobj,
struct attribute *attr, char *buf)
{
struct kgsl_mem_entry_attribute *pattr = to_mem_entry_attr(attr);
struct kgsl_process_private *priv;
ssize_t ret;
mutex_lock(&kgsl_driver.process_mutex);
priv = _get_priv_from_kobj(kobj);
if (priv && pattr->show)
ret = pattr->show(priv, pattr->memtype, buf);
else
ret = -EIO;
mutex_unlock(&kgsl_driver.process_mutex);
return ret;
}
static const struct sysfs_ops mem_entry_sysfs_ops = {
.show = mem_entry_sysfs_show,
};
static struct kobj_type ktype_mem_entry = {
.sysfs_ops = &mem_entry_sysfs_ops,
.default_attrs = NULL,
.release = mem_entry_sysfs_release
};
static struct mem_entry_stats mem_stats[] = {
MEM_ENTRY_STAT(KGSL_MEM_ENTRY_KERNEL, kernel),
#ifdef CONFIG_ANDROID_PMEM
MEM_ENTRY_STAT(KGSL_MEM_ENTRY_PMEM, pmem),
#endif
#ifdef CONFIG_ASHMEM
MEM_ENTRY_STAT(KGSL_MEM_ENTRY_ASHMEM, ashmem),
#endif
MEM_ENTRY_STAT(KGSL_MEM_ENTRY_USER, user),
#ifdef CONFIG_ION
MEM_ENTRY_STAT(KGSL_MEM_ENTRY_ION, ion),
#endif
};
void
kgsl_process_uninit_sysfs(struct kgsl_process_private *private)
{
int i;
for (i = 0; i < ARRAY_SIZE(mem_stats); i++) {
sysfs_remove_file(&private->kobj, &mem_stats[i].attr.attr);
sysfs_remove_file(&private->kobj,
&mem_stats[i].max_attr.attr);
}
kobject_put(&private->kobj);
}
void
kgsl_process_init_sysfs(struct kgsl_process_private *private)
{
unsigned char name[16];
int i, ret;
snprintf(name, sizeof(name), "%d", private->pid);
if (kobject_init_and_add(&private->kobj, &ktype_mem_entry,
kgsl_driver.prockobj, name))
return;
for (i = 0; i < ARRAY_SIZE(mem_stats); i++) {
/* We need to check the value of sysfs_create_file, but we
* don't really care if it passed or not */
ret = sysfs_create_file(&private->kobj,
&mem_stats[i].attr.attr);
ret = sysfs_create_file(&private->kobj,
&mem_stats[i].max_attr.attr);
}
}
static int kgsl_drv_memstat_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
unsigned int val = 0;
if (!strncmp(attr->attr.name, "vmalloc", 7))
val = kgsl_driver.stats.vmalloc;
else if (!strncmp(attr->attr.name, "vmalloc_max", 11))
val = kgsl_driver.stats.vmalloc_max;
else if (!strncmp(attr->attr.name, "page_alloc", 10))
val = kgsl_driver.stats.page_alloc;
else if (!strncmp(attr->attr.name, "page_alloc_max", 14))
val = kgsl_driver.stats.page_alloc_max;
else if (!strncmp(attr->attr.name, "coherent", 8))
val = kgsl_driver.stats.coherent;
else if (!strncmp(attr->attr.name, "coherent_max", 12))
val = kgsl_driver.stats.coherent_max;
else if (!strncmp(attr->attr.name, "mapped", 6))
val = kgsl_driver.stats.mapped;
else if (!strncmp(attr->attr.name, "mapped_max", 10))
val = kgsl_driver.stats.mapped_max;
return snprintf(buf, PAGE_SIZE, "%u\n", val);
}
static int kgsl_drv_histogram_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
int len = 0;
int i;
for (i = 0; i < 16; i++)
len += snprintf(buf + len, PAGE_SIZE - len, "%d ",
kgsl_driver.stats.histogram[i]);
len += snprintf(buf + len, PAGE_SIZE - len, "\n");
return len;
}
DEVICE_ATTR(vmalloc, 0444, kgsl_drv_memstat_show, NULL);
DEVICE_ATTR(vmalloc_max, 0444, kgsl_drv_memstat_show, NULL);
DEVICE_ATTR(page_alloc, 0444, kgsl_drv_memstat_show, NULL);
DEVICE_ATTR(page_alloc_max, 0444, kgsl_drv_memstat_show, NULL);
DEVICE_ATTR(coherent, 0444, kgsl_drv_memstat_show, NULL);
DEVICE_ATTR(coherent_max, 0444, kgsl_drv_memstat_show, NULL);
DEVICE_ATTR(mapped, 0444, kgsl_drv_memstat_show, NULL);
DEVICE_ATTR(mapped_max, 0444, kgsl_drv_memstat_show, NULL);
DEVICE_ATTR(histogram, 0444, kgsl_drv_histogram_show, NULL);
static struct device_attribute *drv_attr_list[] = {
&dev_attr_vmalloc,
&dev_attr_vmalloc_max,
&dev_attr_page_alloc,
&dev_attr_page_alloc_max,
&dev_attr_coherent,
&dev_attr_coherent_max,
&dev_attr_mapped,
&dev_attr_mapped_max,
&dev_attr_histogram,
NULL
};
void
kgsl_sharedmem_uninit_sysfs(void)
{
kgsl_remove_device_sysfs_files(&kgsl_driver.virtdev, drv_attr_list);
}
int
kgsl_sharedmem_init_sysfs(void)
{
return kgsl_create_device_sysfs_files(&kgsl_driver.virtdev,
drv_attr_list);
}
#ifdef CONFIG_OUTER_CACHE
static void _outer_cache_range_op(int op, unsigned long addr, size_t size)
{
switch (op) {
case KGSL_CACHE_OP_FLUSH:
outer_flush_range(addr, addr + size);
break;
case KGSL_CACHE_OP_CLEAN:
outer_clean_range(addr, addr + size);
break;
case KGSL_CACHE_OP_INV:
outer_inv_range(addr, addr + size);
break;
}
}
static void outer_cache_range_op_sg(struct scatterlist *sg, int sglen, int op)
{
struct scatterlist *s;
int i;
for_each_sg(sg, s, sglen, i) {
unsigned int paddr = kgsl_get_sg_pa(s);
_outer_cache_range_op(op, paddr, s->length);
}
}
#else
static void outer_cache_range_op_sg(struct scatterlist *sg, int sglen, int op)
{
}
#endif
static int kgsl_page_alloc_vmfault(struct kgsl_memdesc *memdesc,
struct vm_area_struct *vma,
struct vm_fault *vmf)
{
unsigned long offset;
struct page *page;
int i;
offset = (unsigned long) vmf->virtual_address - vma->vm_start;
i = offset >> PAGE_SHIFT;
page = sg_page(&memdesc->sg[i]);
if (page == NULL)
return VM_FAULT_SIGBUS;
get_page(page);
vmf->page = page;
return 0;
}
static int kgsl_page_alloc_vmflags(struct kgsl_memdesc *memdesc)
{
return VM_RESERVED | VM_DONTEXPAND;
}
static void kgsl_page_alloc_free(struct kgsl_memdesc *memdesc)
{
int i = 0;
struct scatterlist *sg;
int sglen = memdesc->sglen;
/* Don't free the guard page if it was used */
if (memdesc->flags & KGSL_MEMDESC_GUARD_PAGE)
sglen--;
kgsl_driver.stats.page_alloc -= memdesc->size;
if (memdesc->hostptr) {
vunmap(memdesc->hostptr);
kgsl_driver.stats.vmalloc -= memdesc->size;
}
if (memdesc->sg)
for_each_sg(memdesc->sg, sg, sglen, i)
__free_page(sg_page(sg));
}
static int kgsl_contiguous_vmflags(struct kgsl_memdesc *memdesc)
{
return VM_RESERVED | VM_IO | VM_PFNMAP | VM_DONTEXPAND;
}
/*
* kgsl_page_alloc_map_kernel - Map the memory in memdesc to kernel address
* space
*
* @memdesc - The memory descriptor which contains information about the memory
*
* Return: 0 on success else error code
*/
static int kgsl_page_alloc_map_kernel(struct kgsl_memdesc *memdesc)
{
if (!memdesc->hostptr) {
pgprot_t page_prot = pgprot_writecombine(PAGE_KERNEL);
struct page **pages = NULL;
struct scatterlist *sg;
int sglen = memdesc->sglen;
int i;
/* Don't map the guard page if it exists */
if (memdesc->flags & KGSL_MEMDESC_GUARD_PAGE)
sglen--;
/* create a list of pages to call vmap */
pages = vmalloc(sglen * sizeof(struct page *));
if (!pages) {
KGSL_CORE_ERR("vmalloc(%d) failed\n",
sglen * sizeof(struct page *));
return -ENOMEM;
}
for_each_sg(memdesc->sg, sg, sglen, i)
pages[i] = sg_page(sg);
memdesc->hostptr = vmap(pages, sglen,
VM_IOREMAP, page_prot);
KGSL_STATS_ADD(memdesc->size, kgsl_driver.stats.vmalloc,
kgsl_driver.stats.vmalloc_max);
vfree(pages);
}
if (!memdesc->hostptr)
return -ENOMEM;
return 0;
}
static int kgsl_contiguous_vmfault(struct kgsl_memdesc *memdesc,
struct vm_area_struct *vma,
struct vm_fault *vmf)
{
unsigned long offset, pfn;
int ret;
offset = ((unsigned long) vmf->virtual_address - vma->vm_start) >>
PAGE_SHIFT;
pfn = (memdesc->physaddr >> PAGE_SHIFT) + offset;
ret = vm_insert_pfn(vma, (unsigned long) vmf->virtual_address, pfn);
if (ret == -ENOMEM || ret == -EAGAIN)
return VM_FAULT_OOM;
else if (ret == -EFAULT)
return VM_FAULT_SIGBUS;
return VM_FAULT_NOPAGE;
}
static void kgsl_ebimem_free(struct kgsl_memdesc *memdesc)
{
kgsl_driver.stats.coherent -= memdesc->size;
if (memdesc->hostptr)
iounmap(memdesc->hostptr);
free_contiguous_memory_by_paddr(memdesc->physaddr);
}
static void kgsl_coherent_free(struct kgsl_memdesc *memdesc)
{
kgsl_driver.stats.coherent -= memdesc->size;
dma_free_coherent(NULL, memdesc->size,
memdesc->hostptr, memdesc->physaddr);
}
/* Global - also used by kgsl_drm.c */
struct kgsl_memdesc_ops kgsl_page_alloc_ops = {
.free = kgsl_page_alloc_free,
.vmflags = kgsl_page_alloc_vmflags,
.vmfault = kgsl_page_alloc_vmfault,
.map_kernel_mem = kgsl_page_alloc_map_kernel,
};
EXPORT_SYMBOL(kgsl_page_alloc_ops);
static struct kgsl_memdesc_ops kgsl_ebimem_ops = {
.free = kgsl_ebimem_free,
.vmflags = kgsl_contiguous_vmflags,
.vmfault = kgsl_contiguous_vmfault,
};
static struct kgsl_memdesc_ops kgsl_coherent_ops = {
.free = kgsl_coherent_free,
};
void kgsl_cache_range_op(struct kgsl_memdesc *memdesc, int op)
{
void *addr = memdesc->hostptr;
int size = memdesc->size;
switch (op) {
case KGSL_CACHE_OP_FLUSH:
dmac_flush_range(addr, addr + size);
break;
case KGSL_CACHE_OP_CLEAN:
dmac_clean_range(addr, addr + size);
break;
case KGSL_CACHE_OP_INV:
dmac_inv_range(addr, addr + size);
break;
}
outer_cache_range_op_sg(memdesc->sg, memdesc->sglen, op);
}
EXPORT_SYMBOL(kgsl_cache_range_op);
static int
_kgsl_sharedmem_page_alloc(struct kgsl_memdesc *memdesc,
struct kgsl_pagetable *pagetable,
size_t size, unsigned int protflags)
{
int i, order, ret = 0;
int sglen = PAGE_ALIGN(size) / PAGE_SIZE;
struct page **pages = NULL;
pgprot_t page_prot = pgprot_writecombine(PAGE_KERNEL);
void *ptr;
/*
* Add guard page to the end of the allocation when the
* IOMMU is in use.
*/
if (kgsl_mmu_get_mmutype() == KGSL_MMU_TYPE_IOMMU)
sglen++;
memdesc->size = size;
memdesc->pagetable = pagetable;
memdesc->priv = KGSL_MEMFLAGS_CACHED;
memdesc->ops = &kgsl_page_alloc_ops;
memdesc->sg = kgsl_sg_alloc(sglen);
if (memdesc->sg == NULL) {
KGSL_CORE_ERR("vmalloc(%d) failed\n",
sglen * sizeof(struct scatterlist));
ret = -ENOMEM;
goto done;
}
/*
* Allocate space to store the list of pages to send to vmap.
* This is an array of pointers so we can track 1024 pages per page of
* allocation which means we can handle up to a 8MB buffer request with
* two pages; well within the acceptable limits for using kmalloc.
*/
pages = kmalloc(sglen * sizeof(struct page *), GFP_KERNEL);
if (pages == NULL) {
KGSL_CORE_ERR("kmalloc (%d) failed\n",
sglen * sizeof(struct page *));
ret = -ENOMEM;
goto done;
}
kmemleak_not_leak(memdesc->sg);
memdesc->sglen = sglen;
sg_init_table(memdesc->sg, sglen);
for (i = 0; i < PAGE_ALIGN(size) / PAGE_SIZE; i++) {
/*
* Don't use GFP_ZERO here because it is faster to memset the
* range ourselves (see below)
*/
pages[i] = alloc_page(GFP_KERNEL | __GFP_HIGHMEM);
if (pages[i] == NULL) {
ret = -ENOMEM;
memdesc->sglen = i;
goto done;
}
sg_set_page(&memdesc->sg[i], pages[i], PAGE_SIZE, 0);
}
/* ADd the guard page to the end of the sglist */
if (kgsl_mmu_get_mmutype() == KGSL_MMU_TYPE_IOMMU) {
/*
* It doesn't matter if we use GFP_ZERO here, this never
* gets mapped, and we only allocate it once in the life
* of the system
*/
if (kgsl_guard_page == NULL)
kgsl_guard_page = alloc_page(GFP_KERNEL | __GFP_ZERO |
__GFP_HIGHMEM);
if (kgsl_guard_page != NULL) {
sg_set_page(&memdesc->sg[sglen - 1], kgsl_guard_page,
PAGE_SIZE, 0);
memdesc->flags |= KGSL_MEMDESC_GUARD_PAGE;
} else
memdesc->sglen--;
}
/*
* All memory that goes to the user has to be zeroed out before it gets
* exposed to userspace. This means that the memory has to be mapped in
* the kernel, zeroed (memset) and then unmapped. This also means that
* the dcache has to be flushed to ensure coherency between the kernel
* and user pages. We used to pass __GFP_ZERO to alloc_page which mapped
* zeroed and unmaped each individual page, and then we had to turn
* around and call flush_dcache_page() on that page to clear the caches.
* This was killing us for performance. Instead, we found it is much
* faster to allocate the pages without GFP_ZERO, map the entire range,
* memset it, flush the range and then unmap - this results in a factor
* of 4 improvement for speed for large buffers. There is a small
* increase in speed for small buffers, but only on the order of a few
* microseconds at best. The only downside is that there needs to be
* enough temporary space in vmalloc to accomodate the map. This
* shouldn't be a problem, but if it happens, fall back to a much slower
* path
*/
ptr = vmap(pages, i, VM_IOREMAP, page_prot);
if (ptr != NULL) {
memset(ptr, 0, memdesc->size);
dmac_flush_range(ptr, ptr + memdesc->size);
vunmap(ptr);
} else {
int j;
/* Very, very, very slow path */
for (j = 0; j < i; j++) {
ptr = kmap_atomic(pages[j],KM_BOUNCE_READ);
memset(ptr, 0, PAGE_SIZE);
dmac_flush_range(ptr, ptr + PAGE_SIZE);
kunmap_atomic(ptr,KM_BOUNCE_READ);
}
}
outer_cache_range_op_sg(memdesc->sg, memdesc->sglen,
KGSL_CACHE_OP_FLUSH);
ret = kgsl_mmu_map(pagetable, memdesc, protflags);
if (ret)
goto done;
KGSL_STATS_ADD(size, kgsl_driver.stats.page_alloc,
kgsl_driver.stats.page_alloc_max);
order = get_order(size);
if (order < 16)
kgsl_driver.stats.histogram[order]++;
done:
kfree(pages);
if (ret)
kgsl_sharedmem_free(memdesc);
return ret;
}
int
kgsl_sharedmem_page_alloc(struct kgsl_memdesc *memdesc,
struct kgsl_pagetable *pagetable, size_t size)
{
int ret = 0;
BUG_ON(size == 0);
size = ALIGN(size, PAGE_SIZE * 2);
ret = _kgsl_sharedmem_page_alloc(memdesc, pagetable, size,
GSL_PT_PAGE_RV | GSL_PT_PAGE_WV);
if (!ret)
ret = kgsl_page_alloc_map_kernel(memdesc);
if (ret)
kgsl_sharedmem_free(memdesc);
return ret;
}
EXPORT_SYMBOL(kgsl_sharedmem_page_alloc);
int
kgsl_sharedmem_page_alloc_user(struct kgsl_memdesc *memdesc,
struct kgsl_pagetable *pagetable,
size_t size, int flags)
{
unsigned int protflags;
BUG_ON(size == 0);
protflags = GSL_PT_PAGE_RV;
if (!(flags & KGSL_MEMFLAGS_GPUREADONLY))
protflags |= GSL_PT_PAGE_WV;
return _kgsl_sharedmem_page_alloc(memdesc, pagetable, size,
protflags);
}
EXPORT_SYMBOL(kgsl_sharedmem_page_alloc_user);
int
kgsl_sharedmem_alloc_coherent(struct kgsl_memdesc *memdesc, size_t size)
{
int result = 0;
size = ALIGN(size, PAGE_SIZE);
memdesc->size = size;
memdesc->ops = &kgsl_coherent_ops;
memdesc->hostptr = dma_alloc_coherent(NULL, size, &memdesc->physaddr,
GFP_KERNEL);
if (memdesc->hostptr == NULL) {
KGSL_CORE_ERR("dma_alloc_coherent(%d) failed\n", size);
result = -ENOMEM;
goto err;
}
result = memdesc_sg_phys(memdesc, memdesc->physaddr, size);
if (result)
goto err;
/* Record statistics */
KGSL_STATS_ADD(size, kgsl_driver.stats.coherent,
kgsl_driver.stats.coherent_max);
err:
if (result)
kgsl_sharedmem_free(memdesc);
return result;
}
EXPORT_SYMBOL(kgsl_sharedmem_alloc_coherent);
void kgsl_sharedmem_free(struct kgsl_memdesc *memdesc)
{
if (memdesc == NULL || memdesc->size == 0)
return;
if (memdesc->gpuaddr)
kgsl_mmu_unmap(memdesc->pagetable, memdesc);
if (memdesc->ops && memdesc->ops->free)
memdesc->ops->free(memdesc);
kgsl_sg_free(memdesc->sg, memdesc->sglen);
memset(memdesc, 0, sizeof(*memdesc));
}
EXPORT_SYMBOL(kgsl_sharedmem_free);
static int
_kgsl_sharedmem_ebimem(struct kgsl_memdesc *memdesc,
struct kgsl_pagetable *pagetable, size_t size)
{
int result = 0;
memdesc->size = size;
memdesc->pagetable = pagetable;
memdesc->ops = &kgsl_ebimem_ops;
memdesc->physaddr = allocate_contiguous_ebi_nomap(size, SZ_8K);
if (memdesc->physaddr == 0) {
KGSL_CORE_ERR("allocate_contiguous_ebi_nomap(%d) failed\n",
size);
return -ENOMEM;
}
result = memdesc_sg_phys(memdesc, memdesc->physaddr, size);
if (result)
goto err;
result = kgsl_mmu_map(pagetable, memdesc,
GSL_PT_PAGE_RV | GSL_PT_PAGE_WV);
if (result)
goto err;
KGSL_STATS_ADD(size, kgsl_driver.stats.coherent,
kgsl_driver.stats.coherent_max);
err:
if (result)
kgsl_sharedmem_free(memdesc);
return result;
}
int
kgsl_sharedmem_ebimem_user(struct kgsl_memdesc *memdesc,
struct kgsl_pagetable *pagetable,
size_t size, int flags)
{
size = ALIGN(size, PAGE_SIZE);
return _kgsl_sharedmem_ebimem(memdesc, pagetable, size);
}
EXPORT_SYMBOL(kgsl_sharedmem_ebimem_user);
int
kgsl_sharedmem_ebimem(struct kgsl_memdesc *memdesc,
struct kgsl_pagetable *pagetable, size_t size)
{
int result;
size = ALIGN(size, 8192);
result = _kgsl_sharedmem_ebimem(memdesc, pagetable, size);
if (result)
return result;
memdesc->hostptr = ioremap(memdesc->physaddr, size);
if (memdesc->hostptr == NULL) {
KGSL_CORE_ERR("ioremap failed\n");
kgsl_sharedmem_free(memdesc);
return -ENOMEM;
}
return 0;
}
EXPORT_SYMBOL(kgsl_sharedmem_ebimem);
int
kgsl_sharedmem_readl(const struct kgsl_memdesc *memdesc,
uint32_t *dst,
unsigned int offsetbytes)
{
uint32_t *src;
BUG_ON(memdesc == NULL || memdesc->hostptr == NULL || dst == NULL);
WARN_ON(offsetbytes % sizeof(uint32_t) != 0);
if (offsetbytes % sizeof(uint32_t) != 0)
return -EINVAL;
WARN_ON(offsetbytes + sizeof(uint32_t) > memdesc->size);
if (offsetbytes + sizeof(uint32_t) > memdesc->size)
return -ERANGE;
src = (uint32_t *)(memdesc->hostptr + offsetbytes);
*dst = *src;
return 0;
}
EXPORT_SYMBOL(kgsl_sharedmem_readl);
int
kgsl_sharedmem_writel(const struct kgsl_memdesc *memdesc,
unsigned int offsetbytes,
uint32_t src)
{
uint32_t *dst;
BUG_ON(memdesc == NULL || memdesc->hostptr == NULL);
WARN_ON(offsetbytes % sizeof(uint32_t) != 0);
if (offsetbytes % sizeof(uint32_t) != 0)
return -EINVAL;
WARN_ON(offsetbytes + sizeof(uint32_t) > memdesc->size);
if (offsetbytes + sizeof(uint32_t) > memdesc->size)
return -ERANGE;
kgsl_cffdump_setmem(memdesc->gpuaddr + offsetbytes,
src, sizeof(uint32_t));
dst = (uint32_t *)(memdesc->hostptr + offsetbytes);
*dst = src;
return 0;
}
EXPORT_SYMBOL(kgsl_sharedmem_writel);
int
kgsl_sharedmem_set(const struct kgsl_memdesc *memdesc, unsigned int offsetbytes,
unsigned int value, unsigned int sizebytes)
{
BUG_ON(memdesc == NULL || memdesc->hostptr == NULL);
BUG_ON(offsetbytes + sizebytes > memdesc->size);
kgsl_cffdump_setmem(memdesc->gpuaddr + offsetbytes, value,
sizebytes);
memset(memdesc->hostptr + offsetbytes, value, sizebytes);
return 0;
}
EXPORT_SYMBOL(kgsl_sharedmem_set);
/*
* kgsl_sharedmem_map_vma - Map a user vma to physical memory
*
* @vma - The user vma to map
* @memdesc - The memory descriptor which contains information about the
* physical memory
*
* Return: 0 on success else error code
*/
int
kgsl_sharedmem_map_vma(struct vm_area_struct *vma,
const struct kgsl_memdesc *memdesc)
{
unsigned long addr = vma->vm_start;
unsigned long size = vma->vm_end - vma->vm_start;
int ret, i = 0;
if (!memdesc->sg || (size != memdesc->size) ||
(memdesc->sglen != (size / PAGE_SIZE)))
return -EINVAL;
for (; addr < vma->vm_end; addr += PAGE_SIZE, i++) {
ret = vm_insert_page(vma, addr, sg_page(&memdesc->sg[i]));
if (ret)
return ret;
}
return 0;
}
EXPORT_SYMBOL(kgsl_sharedmem_map_vma);