301 lines
7.7 KiB
C
301 lines
7.7 KiB
C
|
/*
|
||
|
* (C) Copyright Advanced Micro Devices, Inc. 2002, 2007
|
||
|
* Copyright (c) 2008-2009 QUALCOMM USA, INC.
|
||
|
*
|
||
|
* All source code in this file is licensed under the following license
|
||
|
*
|
||
|
* This program is free software; you can redistribute it and/or
|
||
|
* modify it under the terms of the GNU General Public License
|
||
|
* 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.
|
||
|
*
|
||
|
* You should have received a copy of the GNU General Public License
|
||
|
* along with this program; if not, you can find it at http://www.fsf.org
|
||
|
*/
|
||
|
#include <linux/io.h>
|
||
|
#include <linux/spinlock.h>
|
||
|
#include <linux/genalloc.h>
|
||
|
|
||
|
#include "kgsl_sharedmem.h"
|
||
|
#include "kgsl_device.h"
|
||
|
#include "kgsl.h"
|
||
|
#include "kgsl_log.h"
|
||
|
|
||
|
/* block alignment shift count */
|
||
|
static inline unsigned int
|
||
|
kgsl_memarena_get_order(uint32_t flags)
|
||
|
{
|
||
|
unsigned int alignshift;
|
||
|
alignshift = ((flags & KGSL_MEMFLAGS_ALIGN_MASK)
|
||
|
>> KGSL_MEMFLAGS_ALIGN_SHIFT);
|
||
|
return alignshift;
|
||
|
}
|
||
|
|
||
|
/* block alignment shift count */
|
||
|
static inline unsigned int
|
||
|
kgsl_memarena_align(unsigned int address, unsigned int shift)
|
||
|
{
|
||
|
unsigned int alignedbaseaddr = ((address) >> shift) << shift;
|
||
|
if (alignedbaseaddr < address)
|
||
|
alignedbaseaddr += (1 << shift);
|
||
|
|
||
|
return alignedbaseaddr;
|
||
|
}
|
||
|
|
||
|
int
|
||
|
kgsl_sharedmem_init(struct kgsl_sharedmem *shmem)
|
||
|
{
|
||
|
int result = -EINVAL;
|
||
|
|
||
|
if (!request_mem_region(shmem->physbase, shmem->size, DRIVER_NAME)) {
|
||
|
KGSL_MEM_ERR("request_mem_region failed\n");
|
||
|
goto error;
|
||
|
}
|
||
|
|
||
|
shmem->baseptr = ioremap(shmem->physbase, shmem->size);
|
||
|
KGSL_MEM_INFO("ioremap(shm) = %p\n", shmem->baseptr);
|
||
|
|
||
|
if (shmem->baseptr == NULL) {
|
||
|
KGSL_MEM_ERR("ioremap failed for address %08x size %d\n",
|
||
|
shmem->physbase, shmem->size);
|
||
|
result = -ENODEV;
|
||
|
goto error_release_mem;
|
||
|
}
|
||
|
|
||
|
shmem->pool = gen_pool_create(KGSL_PAGESIZE_SHIFT, -1);
|
||
|
if (shmem->pool == NULL) {
|
||
|
KGSL_MEM_ERR("gen_pool_create failed\n");
|
||
|
result = -ENOMEM;
|
||
|
goto error_iounmap;
|
||
|
}
|
||
|
|
||
|
if (gen_pool_add(shmem->pool, shmem->physbase, shmem->size, -1)) {
|
||
|
KGSL_MEM_ERR("gen_pool_create failed\n");
|
||
|
result = -ENOMEM;
|
||
|
goto error_pool_destroy;
|
||
|
}
|
||
|
result = 0;
|
||
|
KGSL_MEM_INFO("physbase 0x%08x size 0x%08x baseptr 0x%p\n",
|
||
|
shmem->physbase, shmem->size, shmem->baseptr);
|
||
|
return 0;
|
||
|
|
||
|
error_pool_destroy:
|
||
|
gen_pool_destroy(shmem->pool);
|
||
|
error_iounmap:
|
||
|
iounmap(shmem->baseptr);
|
||
|
shmem->baseptr = NULL;
|
||
|
error_release_mem:
|
||
|
release_mem_region(shmem->physbase, shmem->size);
|
||
|
error:
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
int
|
||
|
kgsl_sharedmem_close(struct kgsl_sharedmem *shmem)
|
||
|
{
|
||
|
if (shmem->pool) {
|
||
|
gen_pool_destroy(shmem->pool);
|
||
|
shmem->pool = NULL;
|
||
|
}
|
||
|
|
||
|
if (shmem->baseptr != NULL) {
|
||
|
KGSL_MEM_INFO("iounmap(shm) = %p\n", shmem->baseptr);
|
||
|
iounmap(shmem->baseptr);
|
||
|
shmem->baseptr = NULL;
|
||
|
release_mem_region(shmem->physbase, shmem->size);
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
/*
|
||
|
* get the host mapped address for a hardware device address
|
||
|
*/
|
||
|
static void *kgsl_memarena_gethostptr(struct kgsl_sharedmem *shmem,
|
||
|
uint32_t physaddr)
|
||
|
{
|
||
|
void *result;
|
||
|
|
||
|
KGSL_MEM_VDBG("enter (memarena=%p, physaddr=0x%08x)\n",
|
||
|
shmem, physaddr);
|
||
|
|
||
|
BUG_ON(shmem == NULL);
|
||
|
|
||
|
/* check address range */
|
||
|
if (physaddr < shmem->physbase)
|
||
|
return NULL;
|
||
|
|
||
|
if (physaddr >= shmem->physbase + shmem->size)
|
||
|
return NULL;
|
||
|
|
||
|
if (shmem->baseptr == NULL) {
|
||
|
KGSL_MEM_VDBG("return: %p\n", NULL);
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
result = ((physaddr - shmem->physbase) + shmem->baseptr);
|
||
|
|
||
|
KGSL_MEM_VDBG("return: %p\n", result);
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
|
||
|
int
|
||
|
kgsl_sharedmem_alloc(uint32_t flags, int size,
|
||
|
struct kgsl_memdesc *memdesc)
|
||
|
{
|
||
|
struct kgsl_sharedmem *shmem;
|
||
|
int result = -ENOMEM;
|
||
|
unsigned int blksize;
|
||
|
unsigned int baseaddr;
|
||
|
unsigned int alignshift;
|
||
|
unsigned int alignedbaseaddr;
|
||
|
|
||
|
KGSL_MEM_VDBG("enter (flags=0x%08x, size=%d, memdesc=%p)\n",
|
||
|
flags, size, memdesc);
|
||
|
|
||
|
shmem = &kgsl_driver.shmem;
|
||
|
BUG_ON(memdesc == NULL);
|
||
|
BUG_ON(size <= 0);
|
||
|
|
||
|
alignshift = kgsl_memarena_get_order(flags);
|
||
|
|
||
|
size = ALIGN(size, KGSL_PAGESIZE);
|
||
|
blksize = size;
|
||
|
if (alignshift > KGSL_PAGESIZE_SHIFT)
|
||
|
blksize += (1 << alignshift) - KGSL_PAGESIZE;
|
||
|
|
||
|
baseaddr = gen_pool_alloc(shmem->pool, blksize);
|
||
|
if (baseaddr == 0) {
|
||
|
KGSL_MEM_ERR("gen_pool_alloc failed\n");
|
||
|
result = -ENOMEM;
|
||
|
goto done;
|
||
|
}
|
||
|
result = 0;
|
||
|
|
||
|
if (alignshift > KGSL_PAGESIZE_SHIFT) {
|
||
|
alignedbaseaddr = ALIGN(baseaddr, (1 << alignshift));
|
||
|
|
||
|
KGSL_MEM_VDBG("ba %x al %x as %d m->as %d bs %x s %x\n",
|
||
|
baseaddr, alignedbaseaddr, alignshift,
|
||
|
KGSL_PAGESIZE_SHIFT, blksize, size);
|
||
|
if (alignedbaseaddr > baseaddr) {
|
||
|
KGSL_MEM_VDBG("physaddr %x free before %x size %x\n",
|
||
|
alignedbaseaddr,
|
||
|
baseaddr, alignedbaseaddr - baseaddr);
|
||
|
gen_pool_free(shmem->pool, baseaddr,
|
||
|
alignedbaseaddr - baseaddr);
|
||
|
blksize -= alignedbaseaddr - baseaddr;
|
||
|
}
|
||
|
if (blksize > size) {
|
||
|
KGSL_MEM_VDBG("physaddr %x free after %x size %x\n",
|
||
|
alignedbaseaddr,
|
||
|
alignedbaseaddr + size,
|
||
|
blksize - size);
|
||
|
gen_pool_free(shmem->pool,
|
||
|
alignedbaseaddr + size,
|
||
|
blksize - size);
|
||
|
}
|
||
|
} else {
|
||
|
alignedbaseaddr = baseaddr;
|
||
|
}
|
||
|
|
||
|
memdesc->physaddr = alignedbaseaddr;
|
||
|
memdesc->hostptr = kgsl_memarena_gethostptr(shmem, memdesc->physaddr);
|
||
|
memdesc->size = size;
|
||
|
|
||
|
KGSL_MEM_VDBG("ashift %d m->ashift %d blksize %d base %x abase %x\n",
|
||
|
alignshift, KGSL_PAGESIZE_SHIFT, blksize, baseaddr,
|
||
|
alignedbaseaddr);
|
||
|
|
||
|
done:
|
||
|
if (result)
|
||
|
memset(memdesc, 0, sizeof(*memdesc));
|
||
|
|
||
|
|
||
|
KGSL_MEM_VDBG("return: %d\n", result);
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
void
|
||
|
kgsl_sharedmem_free(struct kgsl_memdesc *memdesc)
|
||
|
{
|
||
|
struct kgsl_sharedmem *shmem = &kgsl_driver.shmem;
|
||
|
|
||
|
KGSL_MEM_VDBG("enter (shmem=%p, memdesc=%p, physaddr=%08x, size=%d)\n",
|
||
|
shmem, memdesc, memdesc->physaddr, memdesc->size);
|
||
|
|
||
|
BUG_ON(memdesc == NULL);
|
||
|
BUG_ON(memdesc->size <= 0);
|
||
|
BUG_ON(shmem->physbase > memdesc->physaddr);
|
||
|
BUG_ON((shmem->physbase + shmem->size)
|
||
|
< (memdesc->physaddr + memdesc->size));
|
||
|
|
||
|
gen_pool_free(shmem->pool, memdesc->physaddr, memdesc->size);
|
||
|
|
||
|
memset(memdesc, 0, sizeof(struct kgsl_memdesc));
|
||
|
KGSL_MEM_VDBG("return\n");
|
||
|
}
|
||
|
|
||
|
int
|
||
|
kgsl_sharedmem_read(const struct kgsl_memdesc *memdesc, void *dst,
|
||
|
unsigned int offsetbytes, unsigned int sizebytes)
|
||
|
{
|
||
|
if (memdesc == NULL || memdesc->hostptr == NULL || dst == NULL) {
|
||
|
KGSL_MEM_ERR("bad ptr memdesc %p hostptr %p dst %p\n",
|
||
|
memdesc,
|
||
|
(memdesc ? memdesc->hostptr : NULL),
|
||
|
dst);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
if (offsetbytes + sizebytes > memdesc->size) {
|
||
|
KGSL_MEM_ERR("bad range: offset %d size %d memdesc %d\n",
|
||
|
offsetbytes, sizebytes, memdesc->size);
|
||
|
return -ERANGE;
|
||
|
}
|
||
|
memcpy(dst, memdesc->hostptr + offsetbytes, sizebytes);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
int
|
||
|
kgsl_sharedmem_write(const struct kgsl_memdesc *memdesc,
|
||
|
unsigned int offsetbytes,
|
||
|
void *value, unsigned int sizebytes)
|
||
|
{
|
||
|
if (memdesc == NULL || memdesc->hostptr == NULL) {
|
||
|
KGSL_MEM_ERR("bad ptr memdesc %p hostptr %p\n", memdesc,
|
||
|
(memdesc ? memdesc->hostptr : NULL));
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
if (offsetbytes + sizebytes > memdesc->size) {
|
||
|
KGSL_MEM_ERR("bad range: offset %d size %d memdesc %d\n",
|
||
|
offsetbytes, sizebytes, memdesc->size);
|
||
|
return -ERANGE;
|
||
|
}
|
||
|
memcpy(memdesc->hostptr + offsetbytes, value, sizebytes);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
int
|
||
|
kgsl_sharedmem_set(const struct kgsl_memdesc *memdesc, unsigned int offsetbytes,
|
||
|
unsigned int value, unsigned int sizebytes)
|
||
|
{
|
||
|
if (memdesc == NULL || memdesc->hostptr == NULL) {
|
||
|
KGSL_MEM_ERR("bad ptr memdesc %p hostptr %p\n", memdesc,
|
||
|
(memdesc ? memdesc->hostptr : NULL));
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
if (offsetbytes + sizebytes > memdesc->size) {
|
||
|
KGSL_MEM_ERR("bad range: offset %d size %d memdesc %d\n",
|
||
|
offsetbytes, sizebytes, memdesc->size);
|
||
|
return -ERANGE;
|
||
|
}
|
||
|
memset(memdesc->hostptr + offsetbytes, value, sizebytes);
|
||
|
return 0;
|
||
|
}
|
||
|
|