/*
|
* Copyright (c) 2014, STMicroelectronics International N.V.
|
*
|
* 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.
|
*/
|
/**
|
* \file tee_mem.c
|
* \brief Functions to manage a pool of memory chunks.
|
*
|
* This module provides basic functions to manage dynamically a fixed amount
|
* of memory (memory region). For this current implementation the provided
|
* memory region used for the allocations should be physically AND logically
|
* contiguous (only one region is supported for a given allocator).
|
*
|
* Principle of the allocator algorithm: "best fit"
|
*
|
*/
|
|
#include <linux/slab.h>
|
|
#include "tee_mem.h"
|
|
#define _DUMP_INFO_ALLOCATOR 0
|
#define USE_DEVM_ALLOC 1
|
|
#ifndef USE_DEVM_ALLOC
|
#define _KMALLOC(s, f) kmalloc(s, f)
|
#define _KFREE(a) kfree(a)
|
#else
|
#define _KMALLOC(s, f) devm_kzalloc(dev, s, f)
|
#define _KFREE(a) devm_kfree(dev, a)
|
#endif
|
|
/**
|
* \struct mem_chunk
|
* \brief Elementary descriptor of an allocated memory block
|
*
|
* \param node Node for linked list
|
* \param counter Reference counter
|
* (0 indicates that the block is not used/freed)
|
* \param size Total size in bytes
|
* \param paddr Physical base address
|
*
|
* Elementary memory block definition
|
*/
|
struct mem_chunk {
|
struct list_head node;
|
uint32_t counter;
|
size_t size;
|
unsigned long paddr;
|
};
|
|
/**
|
* \struct shm_pool
|
* \brief Main structure to describe a shared memory pool
|
*
|
* \param size Total size in bytes of the associated memory region
|
* \param vaddr Logical base address
|
* \param paddr Physical base address
|
* \param used Total size in bytes of the used memory
|
* \param mchunks List head for handle the elementary memory blocks
|
*
|
* Shared memory pool structure definition
|
*/
|
struct shm_pool {
|
struct mutex lock;
|
size_t size; /* Size of pool/heap memory segment */
|
size_t used; /* Number of bytes allocated */
|
void *vaddr; /* Associated Virtual address */
|
unsigned long paddr; /* Associated Physical address */
|
bool cached; /* true if pool is cacheable */
|
struct list_head mchunks; /* Head of memory chunk/block list */
|
};
|
|
#define __CALCULATE_RATIO_MEM_USED(a) (((a->used)*100)/(a->size))
|
|
/**
|
* \brief Dumps the information of the shared memory pool
|
*
|
* \param pool Pointer on the pool
|
* \param detailforced Flag to force the log for the detailed informations
|
*
|
* Dump/log the meta data of the shared memory pool on the standard output.
|
*
|
*/
|
void tee_shm_pool_dump(struct device *dev, struct shm_pool *pool, bool forced)
|
{
|
struct mem_chunk *chunk;
|
|
if (WARN_ON(!dev || !pool))
|
return;
|
|
dev_info(dev,
|
"tee_shm_pool_dump() poolH(0x%p) pAddr=0x%p vAddr=0x%p size=%zu used=%zu(%zu%%)\n",
|
(void *)pool,
|
(void *)pool->paddr,
|
(void *)pool->vaddr,
|
pool->size, pool->used, __CALCULATE_RATIO_MEM_USED(pool));
|
|
if ((pool->used != 0) || (forced == true)) {
|
dev_info(dev, " \\ HEAD next:[0x%p] prev:[0x%p]\n",
|
(void *)pool->mchunks.next,
|
(void *)pool->mchunks.prev);
|
|
dev_info(dev,
|
" |-[@] next prev pAddr size refCount\n");
|
|
list_for_each_entry(chunk, &pool->mchunks, node) {
|
dev_info(dev, " | [0x%p] 0x%p 0x%p 0x%p %08zu %d\n",
|
(void *)chunk,
|
(void *)chunk->node.next,
|
(void *)chunk->node.prev,
|
(void *)chunk->paddr,
|
chunk->size, chunk->counter);
|
}
|
}
|
}
|
|
bool tee_shm_pool_is_cached(struct shm_pool *pool)
|
{
|
return pool->cached;
|
}
|
|
void tee_shm_pool_set_cached(struct shm_pool *pool)
|
{
|
pool->cached = true;
|
}
|
|
/**
|
* \brief Creates and returns a new shared memory pool manager structure
|
*
|
* \param shm_size Size of the associated memory chunk
|
* \param shm_vaddr Virtual/logical base address
|
* \param shm_paddr Physical base address
|
*
|
* \return Reference of the created shared memory pool manager
|
*
|
* Create and initialize a shared memory pool manager.
|
* The description of the memory region (shm_size, shm_vaddr, shm_paddr)
|
* which is passed should be a physically AND virtually contiguous
|
* (no check is performed by the function).
|
* If a error is detected returned pool is NULL.
|
*/
|
struct shm_pool *tee_shm_pool_create(struct device *dev, size_t shm_size,
|
void *shm_vaddr, unsigned long shm_paddr)
|
{
|
struct mem_chunk *chunk = NULL;
|
struct shm_pool *pool = NULL;
|
|
if (WARN_ON(!dev))
|
goto alloc_failed;
|
|
dev_dbg(dev, "> vaddr=0x%p, paddr=0x%p, size=%zuKiB\n",
|
shm_vaddr, (void *)shm_paddr, shm_size / 1024);
|
|
/* Alloc and initialize the shm_pool structure */
|
pool = _KMALLOC(sizeof(struct shm_pool), GFP_KERNEL);
|
if (!pool) {
|
dev_err(dev, "kmalloc <struct shm_pool> failed\n");
|
goto alloc_failed;
|
}
|
memset(pool, 0, sizeof(*pool));
|
mutex_init(&pool->lock);
|
mutex_lock(&pool->lock);
|
|
INIT_LIST_HEAD(&(pool->mchunks));
|
pool->size = shm_size;
|
pool->vaddr = shm_vaddr;
|
pool->paddr = shm_paddr;
|
|
/* Create the initial elementary memory chunk */
|
/* which handles the whole memory region */
|
chunk = _KMALLOC(sizeof(struct mem_chunk), GFP_KERNEL);
|
if (!chunk) {
|
dev_err(dev, "kmalloc <struct MemChunk> failed\n");
|
mutex_unlock(&pool->lock);
|
goto alloc_failed;
|
}
|
memset(chunk, 0, sizeof(*chunk));
|
chunk->paddr = shm_paddr;
|
chunk->size = shm_size;
|
|
/* Adds the new entry immediately after the list head */
|
list_add(&(chunk->node), &(pool->mchunks));
|
|
#if defined(_DUMP_INFO_ALLOCATOR) && (_DUMP_INFO_ALLOCATOR > 0)
|
tee_shm_pool_dump(dev, pool, true);
|
#endif
|
dev_dbg(dev, "< poolH(0x%p) chunkH=0x%p\n",
|
(void *)(pool), (void *)chunk);
|
|
mutex_unlock(&pool->lock);
|
return pool;
|
|
alloc_failed:
|
if (chunk)
|
_KFREE(chunk);
|
|
if (pool)
|
_KFREE(pool);
|
|
return NULL;
|
}
|
|
/**
|
* Local helper function to check that the physical address is valid
|
*/
|
static inline int is_valid_paddr(struct shm_pool *pool, unsigned long paddr)
|
{
|
return (paddr >= pool->paddr && paddr < (pool->paddr + pool->size));
|
}
|
|
/**
|
* Local helper function to check that the virtual address is valid
|
*/
|
static inline int is_valid_vaddr(struct shm_pool *pool, void *vaddr)
|
{
|
return (vaddr >= pool->vaddr && vaddr < (pool->vaddr + pool->size));
|
}
|
|
/**
|
* \brief Destroy the shared memory pool manager
|
*
|
* \param pool Pointer on the pool
|
*
|
* Destroy a memory pool manager
|
*
|
*/
|
void tee_shm_pool_destroy(struct device *dev, struct shm_pool *pool)
|
{
|
struct mem_chunk *chunk;
|
|
if (WARN_ON(!dev || !pool))
|
return;
|
|
dev_dbg(dev, "> poolH(0x%p)\n", (void *)pool);
|
|
#if defined(_DUMP_INFO_ALLOCATOR) && (_DUMP_INFO_ALLOCATOR > 0)
|
tee_shm_pool_dump(dev, pool, true);
|
#endif
|
|
tee_shm_pool_reset(dev, pool);
|
|
chunk = list_first_entry(&pool->mchunks, struct mem_chunk, node);
|
dev_dbg(dev, "free chunkH=0x%p\n", (void *)chunk);
|
_KFREE(chunk);
|
_KFREE(pool);
|
|
dev_dbg(dev, "<\n");
|
}
|
|
/**
|
* \brief Free all reserved chunk if any, and set pool at it initial state
|
*
|
* \param pool Pointer on the pool
|
*
|
*/
|
void tee_shm_pool_reset(struct device *dev, struct shm_pool *pool)
|
{
|
struct mem_chunk *chunk;
|
struct mem_chunk *tmp;
|
struct mem_chunk *first = NULL;
|
|
if (WARN_ON(!dev || !pool))
|
return;
|
|
mutex_lock(&pool->lock);
|
|
list_for_each_entry_safe(chunk, tmp, &pool->mchunks, node) {
|
if (first != NULL) {
|
dev_err(dev, "Free lost chunkH=0x%p\n", (void *)chunk);
|
list_del(&chunk->node);
|
_KFREE(chunk);
|
} else {
|
first = chunk;
|
}
|
}
|
|
first->counter = 0;
|
first->paddr = pool->paddr;
|
first->size = pool->size;
|
pool->used = 0;
|
|
mutex_unlock(&pool->lock);
|
}
|
|
/**
|
* \brief Return the logical address
|
*
|
* \param pool Pointer on the pool
|
* \param paddr Physical address
|
*
|
* \return Virtual/logical address
|
*
|
* Return the associated virtual/logical address. The address should be inside
|
* the range of addresses managed by the shm pool.
|
*
|
*/
|
void *tee_shm_pool_p2v(struct device *dev, struct shm_pool *pool,
|
unsigned long paddr)
|
{
|
if (WARN_ON(!dev || !pool))
|
return NULL;
|
|
mutex_lock(&pool->lock);
|
if (!is_valid_paddr(pool, paddr)) {
|
mutex_unlock(&pool->lock);
|
dev_err(dev,
|
"tee_shm_pool_p2v() paddr=0x%p not in the shm pool\n",
|
(void *)paddr);
|
return NULL;
|
} else {
|
unsigned long offset = paddr - pool->paddr;
|
void *p = (void *)((unsigned long)pool->vaddr + offset);
|
|
mutex_unlock(&pool->lock);
|
return p;
|
}
|
}
|
|
/**
|
* \brief Return the physical address
|
*
|
* \param pool Pointer on the pool
|
* \param vaddr Logical/Virtual address
|
*
|
* \return Physical address
|
*
|
* Return the associated physical address. The address should be inside
|
* the range of addresses managed by the pool.
|
*
|
*/
|
unsigned long tee_shm_pool_v2p(struct device *dev, struct shm_pool *pool,
|
void *vaddr)
|
{
|
if (WARN_ON(!dev || !pool))
|
return 0UL;
|
|
mutex_lock(&pool->lock);
|
if (!is_valid_vaddr(pool, vaddr)) {
|
dev_err(dev,
|
"tee_shm_pool_v2p() vaddr=0x%p not in shm pool\n",
|
(void *)vaddr);
|
mutex_unlock(&pool->lock);
|
return 0UL;
|
} else {
|
unsigned long offset = vaddr - pool->vaddr;
|
unsigned long p = pool->paddr + offset;
|
|
mutex_unlock(&pool->lock);
|
return p;
|
}
|
}
|
|
/**
|
* \brief Allocate a new block of memory
|
*
|
* \param pool Pointer on the pool
|
* \param size Expected size (in byte)
|
* \param alignment Alignment constraint (in byte)
|
*
|
* \return Physical base address of the allocated block
|
*
|
* Allocate a memory chunk inside the memory region managed by the pool.
|
*
|
*/
|
unsigned long rk_tee_shm_pool_alloc(struct device *dev,
|
struct shm_pool *pool,
|
size_t size, size_t alignment)
|
{
|
struct mem_chunk *chunk;
|
struct mem_chunk *betterchunk = NULL;
|
struct mem_chunk *prev_chunk = NULL;
|
struct mem_chunk *next_chunk = NULL;
|
unsigned long begAddr;
|
unsigned long endAddr;
|
|
if (WARN_ON(!dev || !pool))
|
return 0UL;
|
|
dev_dbg(dev, "> poolH(%p:%p:%x) size=0x%zx align=0x%zx\n",
|
pool, (void *)pool->paddr, (unsigned int)pool->size, size,
|
alignment);
|
|
/* Align on cache line of the target */
|
/* \todo(jmd) Should be defined by a global target specific parameter */
|
/* size = (size + (32-1)) & ~(32-1) */
|
|
if (ALIGN(size, 0x20) < size)
|
goto failed_out;
|
|
if (alignment == 0)
|
alignment = 1;
|
|
size = ALIGN(size, 0x20);
|
|
alignment = ALIGN(alignment, 0x20);
|
|
if (size > (pool->size - pool->used))
|
goto failed_out;
|
|
mutex_lock(&pool->lock);
|
|
/**
|
* Algorithm: Smallest waste (best fit): We choose the block that has the
|
* smallest waste. In other words we choose the block so that
|
* size(b) - size is as small as possible.
|
*/
|
list_for_each_entry(chunk, &pool->mchunks, node) {
|
if (chunk->counter == 0) { /* Free chunk */
|
begAddr = ALIGN(chunk->paddr, alignment);
|
endAddr = begAddr + size;
|
|
if (begAddr >= chunk->paddr
|
&& endAddr <= (chunk->paddr + chunk->size)
|
&& (betterchunk == NULL
|
/* Always split smaller block */
|
|| chunk->size < betterchunk->size))
|
betterchunk = chunk;
|
}
|
}
|
|
/**
|
* Update the linked list
|
*/
|
if (betterchunk != NULL) {
|
prev_chunk = _KMALLOC(sizeof(struct mem_chunk), GFP_KERNEL);
|
next_chunk = _KMALLOC(sizeof(struct mem_chunk), GFP_KERNEL);
|
|
if ((!prev_chunk) || (!next_chunk))
|
goto failed_out_unlock;
|
|
begAddr = ALIGN(betterchunk->paddr, alignment);
|
endAddr = begAddr + size;
|
|
if (betterchunk->paddr < begAddr) {
|
/* memory between begin of chunk and begin
|
* of created memory => create a free chunk */
|
prev_chunk->counter = 0;
|
prev_chunk->paddr = betterchunk->paddr;
|
prev_chunk->size = begAddr - betterchunk->paddr;
|
|
betterchunk->paddr = begAddr;
|
betterchunk->size -= prev_chunk->size;
|
|
dev_dbg(dev,
|
"create p_chunkH=0x%p paddr=0x%p (s=%zu)\n",
|
(void *)prev_chunk,
|
(void *)prev_chunk->paddr, prev_chunk->size);
|
|
list_add_tail(&(prev_chunk->node),
|
&(betterchunk->node));
|
prev_chunk = NULL;
|
} else {
|
_KFREE(prev_chunk);
|
}
|
|
if (betterchunk->paddr + betterchunk->size > endAddr) {
|
/* memory between end of chunk and end of
|
* created memory => create a free chunk */
|
next_chunk->counter = 0;
|
next_chunk->paddr = endAddr;
|
next_chunk->size = betterchunk->size - size;
|
|
dev_dbg(dev,
|
"create n_chunkH=0x%p paddr=0x%p (s=%zu)\n",
|
(void *)next_chunk,
|
(void *)next_chunk->paddr, next_chunk->size);
|
|
betterchunk->size = size;
|
|
list_add(&(next_chunk->node), &(betterchunk->node));
|
next_chunk = NULL;
|
} else {
|
_KFREE(next_chunk);
|
}
|
|
betterchunk->counter = 1;
|
pool->used += size;
|
|
mutex_unlock(&pool->lock);
|
|
#if defined(_DUMP_INFO_ALLOCATOR) && (_DUMP_INFO_ALLOCATOR > 1)
|
tee_shm_pool_dump(dev, pool, false);
|
#endif
|
|
dev_dbg(dev,
|
"< chunkH=0x%p paddr=%p (s=%zu) align=0x%zx\n",
|
(void *)betterchunk,
|
(void *)betterchunk->paddr,
|
betterchunk->size, alignment);
|
|
return betterchunk->paddr;
|
}
|
|
failed_out_unlock:
|
mutex_unlock(&pool->lock);
|
failed_out:
|
if (prev_chunk)
|
_KFREE(prev_chunk);
|
if (next_chunk)
|
_KFREE(next_chunk);
|
|
dev_err(dev,
|
"rk_tee_shm_pool_alloc() FAILED, size=0x%zx, align=0x%zx free=%zu\n",
|
size, alignment, pool->size - pool->used);
|
|
#if defined(_DUMP_INFO_ALLOCATOR) && (_DUMP_INFO_ALLOCATOR > 1)
|
tee_shm_pool_dump(dev, pool, true);
|
#endif
|
|
return 0UL;
|
}
|
|
/**
|
* \brief Release a allocated block of memory
|
*
|
* \param pool Pointer on the pool
|
* \param paddr Physical @ of the block which must be released
|
* \param size Reference to return the size of the block
|
*
|
* Free a allocated memory block inside
|
* the memory region managed by the pool.
|
*
|
*/
|
int rk_tee_shm_pool_free(struct device *dev, struct shm_pool *pool,
|
unsigned long paddr, size_t *size)
|
{
|
struct mem_chunk *chunk;
|
|
if (WARN_ON(!dev || !pool))
|
return -EINVAL;
|
|
dev_dbg(dev, "> Try to free ... poolH(0x%p) paddr=0x%p\n",
|
(void *)pool, (void *)paddr);
|
|
#if defined(_DUMP_INFO_ALLOCATOR) && (_DUMP_INFO_ALLOCATOR > 1)
|
tee_shm_pool_dump(dev, pool, false);
|
#endif
|
|
mutex_lock(&pool->lock);
|
|
if (!is_valid_paddr(pool, paddr))
|
goto out_failed;
|
|
list_for_each_entry(chunk, &pool->mchunks, node) {
|
if (chunk->paddr == paddr) {
|
if (size != NULL)
|
*size = chunk->size;
|
|
if (chunk->counter == 0) {
|
dev_warn(dev,
|
"< tee_shm_pool_free() WARNING, paddr=0x%p already released\n",
|
(void *)paddr);
|
mutex_unlock(&pool->lock);
|
return -EINVAL;
|
} else if (--chunk->counter == 0) {
|
dev_dbg(dev, "paddr=%p\n", (void *)paddr);
|
|
pool->used -= chunk->size;
|
|
/* Merge with previous */
|
if (chunk->node.prev != &pool->mchunks) {
|
struct mem_chunk *prev =
|
list_entry(chunk->node.prev,
|
struct mem_chunk, node);
|
if (prev->counter == 0) {
|
dev_dbg(dev,
|
"chunkH=0x%p paddr=0x%p free ok\n",
|
(void *)chunk,
|
(void *)paddr);
|
prev->size += chunk->size;
|
list_del(&chunk->node);
|
_KFREE(chunk);
|
chunk = prev;
|
}
|
}
|
/* Merge with next */
|
if (chunk->node.next != &pool->mchunks) {
|
struct mem_chunk *next =
|
list_entry(chunk->node.next,
|
struct mem_chunk, node);
|
if (next->counter == 0) {
|
dev_dbg(dev,
|
"chunkH=0x%p paddr=0x%p free ok\n",
|
(void *)chunk,
|
(void *)paddr);
|
chunk->size += next->size;
|
list_del(&next->node);
|
_KFREE(next);
|
}
|
}
|
mutex_unlock(&pool->lock);
|
|
#if defined(_DUMP_INFO_ALLOCATOR) && (_DUMP_INFO_ALLOCATOR > 1)
|
tee_shm_pool_dump(dev, pool, false);
|
#endif
|
dev_dbg(dev, "< freed\n");
|
return 0;
|
|
} else {
|
mutex_unlock(&pool->lock);
|
dev_dbg(dev,
|
"< paddr=0x%p (--) refcounter is decremented ret=1\n",
|
(void *)paddr);
|
return 1;
|
}
|
}
|
}
|
|
out_failed:
|
mutex_unlock(&pool->lock);
|
#if defined(_DUMP_INFO_ALLOCATOR) && (_DUMP_INFO_ALLOCATOR > 1)
|
tee_shm_pool_dump(dev, pool, false);
|
#endif
|
dev_err(dev,
|
"< tee_shm_pool_free() FAILED, pAddr=0x%p not found\n",
|
(void *)paddr);
|
return -EINVAL;
|
}
|
|
/**
|
* \brief Increase the reference count of the memory chunk
|
*
|
* \param pool Pointer on the pool
|
* \param paddr Physical address
|
*
|
* \return true if successful (false otherwise)
|
*
|
* Increment the reference count of the allocated block of memory.
|
* paddr should a valid address returned by the tee_shm_pool_alloc().
|
*
|
*/
|
bool tee_shm_pool_incref(struct device *dev, struct shm_pool *pool,
|
unsigned long paddr)
|
{
|
struct mem_chunk *chunk;
|
|
if (WARN_ON(!dev || !pool))
|
return false;
|
|
mutex_lock(&pool->lock);
|
|
if (!is_valid_paddr(pool, paddr))
|
goto out_failed;
|
|
list_for_each_entry(chunk, &pool->mchunks, node) {
|
if (chunk->paddr == paddr) {
|
dev_dbg(dev,
|
"pAddr=%p (++) refcounter is incremented\n",
|
(void *)paddr);
|
chunk->counter++;
|
|
#if defined(_DUMP_INFO_ALLOCATOR) && (_DUMP_INFO_ALLOCATOR > 0)
|
tee_shm_pool_dump(dev, pool, false);
|
#endif
|
mutex_unlock(&pool->lock);
|
return true;
|
}
|
}
|
|
out_failed:
|
mutex_unlock(&pool->lock);
|
|
dev_err(dev,
|
"tee_shm_pool_incref() FAILED, pAddr=%p is not a valid @\n",
|
(void *)paddr);
|
|
return false;
|
}
|