/* Copyright (C) 2018 RDA Technologies Limited and/or its affiliates("RDA").
 * All rights reserved.
 *
 * This software is supplied "AS IS" without any warranties.
 * RDA assumes no responsibility or liability for the use of the software,
 * conveys no license or title under any patent, copyright, or mask work
 * right to the product. RDA reserves the right to make changes in the
 * software without notification.  RDA also make no representation or
 * warranty that such application will be suitable for the specified use
 * without further testing or modification.
 */

#ifndef __OSI_MEM_H__
#define __OSI_MEM_H__

#include "kernel_config.h"

#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>

#ifdef __cplusplus
extern "C" {
#endif

/**
 * opaque data structure for memory pool
 */
typedef struct osiMemPool osiMemPool_t;

/**
 * memory pool information
 */
typedef struct
{
    void *start;             ///< memory pool start pointer
    uint32_t size;           ///< memory pool total size
    uint32_t index;          ///< memory pool internal index
    uint32_t avail_size;     ///< available size. The actual allocatable size may be less than this
    uint32_t max_block_size; ///< maximum allocatable block size
} osiMemPoolStat_t;

/**
 * memory allocate/free record
 */
typedef struct
{
    uintptr_t caller; ///< [31] 1: allocate, 0: free, [30:0] caller address[31:1]
    uintptr_t ptr;    ///< address
} osiMemRecord_t;

/**
 * memory block information for error scan
 */
typedef struct
{
    uintptr_t address; ///< usable address
    uintptr_t caller;  ///< caller address
    unsigned flags;    ///< error flags
} osiMemErrorBlock_t;

typedef struct
{
    uint32_t num_allocs; ///< allocs number
    uint32_t num_frees;  ///< frees number
} osiMemStatistics_t;

/**
 * initialize a fixed pool
 *
 * Initialize and register a fixed size block pool. The pool management data
 * structure will be located inside the provided memory, Due to alignment
 * it may exist an offset.
 *
 * @note Due to there are management overhead, the available block count is
 * not `pool_size/block_size`.
 *
 * @param ptr   starting pointer of the memory
 * @param size  memory total size
 * @param block_size    block size
 * @return
 *      - the pool pointer on success
 *      - NULL on failure
 */
osiMemPool_t *osiFixedPoolInit(void *ptr, size_t size, size_t block_size);

/**
 * initialize a block pool
 *
 * Initialize and register a block pool pool. The variadic parameters should
 * be ended with a zero. Each pair of (size_t count, size_t size) indicates
 * to create a fixed pool child for the block pool. When allocating from
 * the block pool, fixed pool children will be checked automatically. It
 * may be helpful to reduce memory fragmentation.
 *
 * The management data structure of block pool and all children will be
 * located inside the provided memory.
 *
 * @note The feature of fixed block pool inside is experimental.
 *
 * @param ptr   starting pointer of the memory pool.
 * @param size  memory pool size
 * @return
 *      - the pool pointer on success
 *      - NULL on failure
 */
osiMemPool_t *osiBlockPoolInit(void *ptr, size_t size, ...);

/**
 * set the default pool
 *
 * Default pool will be used by malloc/calloc, and it must be block pool.
 * The first created block pool will be set to default automatically.
 * And this API will change default pool.
 *
 * When \p pool is NULL or invalid, it will return silently.
 *
 * @param pool  pool to be set as default.
 */
void osiPoolSetDefault(osiMemPool_t *pool);

/**
 * allocate from specified pool
 *
 * When \p pool is NULL or invalid, or \p size is zero, NULL will be
 * returned.
 *
 * Refer to malloc(3).
 *
 * @param pool  the pool
 * @param size  size to be allocated
 * @return
 *      - allocated memory pointer on success
 *      - NULL at failure
 */
void *osiPoolMalloc(osiMemPool_t *pool, size_t size);

/**
 * allocate from specified pool, which is unlikely to be freed
 *
 * Comparing to \p osiPoolMalloc, allocator will try to allocate
 * from location with less fragmentation impact.
 *
 * Usually, it only be called at system initialization. It is a replacement
 * of global variable (either DATA or BSS).
 *
 * @param pool  the pool
 * @param size  size to be allocated
 * @return
 *      - allocated memory pointer on success
 *      - NULL at failure
 */
void *osiPoolMallocUnlikelyFree(osiMemPool_t *pool, size_t size);

/**
 * allocate from specified pool, and clear to zero
 *
 * It is the exact same as `osiPoolMalloc(pool, nmemb*size)` and clear
 * memory to zero.
 *
 * Refer to calloc(3).
 *
 * @param pool  the pool.
 * @param nmemb member count to be allocated.
 * @param size  size of each member.
 * @return
 *      - allocated memory pointer on success
 *      - NULL at failure
 */
void *osiPoolCalloc(osiMemPool_t *pool, size_t nmemb, size_t size);

/**
 * change size of memory block
 *
 * Refer to realloc(3).
 *
 * @param pool  the pool.
 * @param ptr   pointer to be changed.
 * @param size  changed size.
 * @return
 *      - allocated memory pointer on success
 *      - NULL at failure
 */
void *osiPoolRealloc(osiMemPool_t *pool, void *ptr, size_t size);

/**
 * allocate from specified pool with specified alignment
 *
 * The pool must be block pool.
 *
 * \p alignment should be power of 2. When \p alignment is less than
 * default alignment, it will behavior the same as `osiPoolMalloc`.
 *
 * Refer to memalign(3).
 *
 * @param[in] pool The pool
 * @param[in] alignment Requested alignment
 * @param[in] size Size to be allocated
 * @return
 *      - allocated memory pointer on success
 *      - NULL at failure
 */
void *osiPoolMemalign(osiMemPool_t *pool, size_t alignment, size_t size);

/**
 * set memory block caller address
 *
 * Caller address is for debug only. And won't affect behavior. All APIs
 * in this module will set caller automatically. Only for memory management
 * wrappers, and it is wanted to set caller to the caller of wrapper,
 * this function may be called.
 *
 * @param ptr       pointer of memory block.
 * @param caller    caller address
 */
void osiMemSetCaller(void *ptr, void *caller);

/**
 * get the allocated size for the pointer
 *
 * The size is allocated by memory management. The size may be larger than
 * the size requested, due to tail padding.
 *
 * @param ptr       pointer of memory block
 * @return
 *      - allocated memory block size
 *      - 0 if \p ptr is NULL
 *      - -1 if \p ptr is invalid
 */
int osiMemAllocSize(void *ptr);

/**
 * increase reference count of the pointer
 *
 * When the reference count reach the maximum allowed reference count,
 * system will panic.
 *
 * When \p ptr is NULL, nothing will be done.
 *
 * @param ptr       pointer of memory block
 */
void osiMemRef(void *ptr);

/**
 * get reference count of the pointer
 *
 * When \p ptr is NULL, 0 will be returned.
 *
 * @param ptr       pointer of memory block
 * @return  reference count of the pointer
 */
size_t osiMemRefCount(void *ptr);

/**
 * decrease reference if the reference count is not 1
 *
 * At object oriented design with refrence count, it is not enough to
 * consider memory reference count only. Rather, it is needed to
 * consider the object reference count. So, the *delete* function of
 * object should be:
 *
 * \code{.cpp}
 * void objectDelete(void *object)
 * {
 *     if (osiMemUnrefNotLast(object))
 *         return;
 *
 *     objectCleanup(object);
 *     free(object);
 * }
 * \endcode
 *
 * When the reference count of the object, which is stored inside memory
 * management, is not 1, only decrease the reference count. Only when
 * it is the last reference, the cleanup shall be called.
 *
 * When \p ptr is NULL, it will return true, and do nothing. Conceptually,
 * NULL pointer can be regarded as reference count of 0. So, it is not
 * the last reference.
 *
 * When reference count of \p ptr is greater than 1, the reference count
 * will be decreased by 1, and it is the same as \p free.
 *
 * @param ptr       pointer of memory block
 * @return
 *      - true if it is not the last reference, or \p ptr is NULL
 *      - false if it is the last reference
 */
bool osiMemUnrefNotLast(void *ptr);

/**
 * refer to malloc(3).
 */
void *osiMalloc(size_t size);

/**
 * refer to calloc(3).
 */
void *osiCalloc(size_t nmemb, size_t size);

/**
 * refer to realloc(3).
 */
void *osiRealloc(void *ptr, size_t size);

/**
 * refer to memalign(3).
 */
void *osiMemalign(size_t alignment, size_t size);

/**
 * refer to free(3).
 */
void osiFree(void *ptr);

/**
 * get memory pool information
 *
 * \p max_block_size is the maximum allocatable size. \p realloc and
 * \p memalign will use more extra spaces, they may fail with that size.
 *
 * \code{.cpp}
 * malloc(stat->max_block_size);        // will success
 * realloc(ptr, stat->max_block_size);  // may fail
 * memalign(32, stat->max_block_size);  // may fail
 * \endcode
 *
 * @param pool      the memory pool. \p NULL for default memory pool
 * @param stat      output memory pool information
 * @return
 *      - true on success
 *      - false if there are no memory pool, or \p stat is NULL
 */
bool osiMemPoolStat(osiMemPool_t *pool, osiMemPoolStat_t *stat);

/**
 * scan all heaps for memory block error
 *
 * This should only be called after system enter blue screen.
 *
 * \param blocks    output memory block error information
 * \param count     maximum error block count
 * \return
 *      - memory block error count
 */
int osiMemScanError(osiMemErrorBlock_t *blocks, unsigned count);

/**
 * get last memory allocate/free records
 *
 * \param records   output memory allocate/free record
 * \param count     maximum record count
 * \return
 *      - memory allocate/free record count
 */
int osiMemRecordGetLast(osiMemRecord_t *records, unsigned count);

#ifdef CONFIG_KERNEL_MEM_STATISTICS_SUPPORT
/**
 * get the count of alloc and free.
 *
 * \param Statistics   output memory allocate/free count
 * \return
 */
void osiMemStatistic(osiMemStatistics_t *statistics);
#endif

#ifdef __cplusplus
}
#endif
#endif
