/**
 * @file
 *
 * @brief Red-Black Tree Heap API
 */

/*
 * Copyright (c) 2012 embedded brains GmbH.  All rights reserved.
 *
 *  embedded brains GmbH
 *  Obere Lagerstr. 30
 *  82178 Puchheim
 *  Germany
 *  <rtems@embedded-brains.de>
 *
 * The license and distribution terms for this file may be
 * found in the file LICENSE in this distribution or at
 * http://www.rtems.org/license/LICENSE.
 */

#ifndef _RTEMS_RBHEAP_H
#define _RTEMS_RBHEAP_H

#include <rtems.h>
#include <rtems/chain.h>
#include <rtems/rbtree.h>

#ifdef __cplusplus
extern "C" {
#endif

/**
 * @defgroup RBHeap Red-Black Tree Heap
 *
 * @ingroup ClassicRTEMS
 *
 * @brief Red-Black Tree Heap API.
 *
 * The red-black tree heap provides a memory allocator suitable to implement
 * the malloc() and free() interface.  It uses a first-fit allocation strategy.
 * In the red-black tree heap the administration data structures are not
 * contained in the managed memory area.  Thus writing beyond the boundaries of
 * a chunk does not damage the data to maintain the heap.  This can be used for
 * example in a task stack allocator which protects the task stacks from access
 * by other tasks.  The allocated and free memory parts of the managed area are
 * called chunks.  Each chunk needs a descriptor which is stored outside of the
 * managed area.
 */
/**@{*/

/**
 * @brief Red-black heap chunk descriptor.
 */
typedef struct {
  /**
   * This chain node can be used in two chains
   *  - the chain of spare chunk descriptors and
   *  - the chain of free chunks in the managed memory area.
   *
   * In case this chain node is not part of a chain, the chunk represents a
   * used chunk in the managed memory area.
   */
  rtems_chain_node chain_node;

  /**
   * Tree node for chunks that represent a part of the managed memory area.
   * These chunks are either free or used.
   */
  rtems_rbtree_node tree_node;

  /**
   * Begin address of the chunk.  The address alignment it specified in the
   * @ref rtems_rbheap_control.
   */
  uintptr_t begin;

  /**
   * Size of the chunk in bytes.
   */
  uintptr_t size;
} rtems_rbheap_chunk;

typedef struct rtems_rbheap_control rtems_rbheap_control;

/**
 * @brief Handler to extend the available chunk descriptors.
 *
 * This handler is called when no more chunk descriptors are available.  An
 * example implementation is this:
 *
 * @code
 * void extend_descriptors_with_malloc(rtems_rbheap_control *control)
 * {
 *   rtems_rbheap_chunk *chunk = malloc(sizeof(*chunk));
 *
 *   if (chunk != NULL) {
 *     rtems_rbheap_add_to_spare_descriptor_chain(control, chunk);
 *   }
 * }
 * @endcode
 *
 * @see rtems_rbheap_extend_descriptors_never() and
 * rtems_rbheap_extend_descriptors_with_malloc().
 */
typedef void (*rtems_rbheap_extend_descriptors)(rtems_rbheap_control *control);

/**
 * @brief Red-black heap control.
 */
struct rtems_rbheap_control {
  /**
   * Chain of free chunks in the managed memory area.
   */
  rtems_chain_control free_chunk_chain;

  /**
   * Chain of free chunk descriptors.  Descriptors are consumed during
   * allocation and may be produced during free if contiguous chunks can be
   * coalesced.  In case of descriptor starvation the @ref extend_descriptors
   * handler will be called.
   */
  rtems_chain_control spare_descriptor_chain;

  /**
   * Tree of chunks representing the state of the managed memory area.
   */
  rtems_rbtree_control chunk_tree;

  /**
   * Minimum chunk begin alignment in bytes.
   */
  uintptr_t alignment;

  /**
   * Handler to extend the available chunk descriptors.
   */
  rtems_rbheap_extend_descriptors extend_descriptors;

  /**
   * User specified argument handler for private handler data.
   */
  void *handler_arg;
};

/**
 * @brief Initializes the red-black tree heap @a control.
 *
 * @param[in, out] control The red-black tree heap.
 * @param[in] area_begin The managed memory area begin.
 * @param[in] area_size The managed memory area size.
 * @param[in] alignment The minimum chunk alignment.
 * @param[in] extend_descriptors The handler to extend the available chunk
 * descriptors.
 * @param[in] handler_arg The handler argument.
 *
 * @retval RTEMS_SUCCESSFUL Successful operation.
 * @retval RTEMS_INVALID_ADDRESS The memory area is invalid.
 * @retval RTEMS_NO_MEMORY Not enough chunk descriptors.
 */
rtems_status_code rtems_rbheap_initialize(
  rtems_rbheap_control *control,
  void *area_begin,
  uintptr_t area_size,
  uintptr_t alignment,
  rtems_rbheap_extend_descriptors extend_descriptors,
  void *handler_arg
);

/**
 * @brief Allocates a chunk of memory of at least @a size bytes from the
 * red-black tree heap @a control.
 *
 * The chunk begin is aligned by the value specified in
 * rtems_rbheap_initialize().
 *
 * @param[in, out] control The red-black tree heap.
 * @param[in] size The requested chunk size in bytes.
 *
 * @retval NULL Not enough free space in the heap.
 * @retval otherwise Pointer to allocated chunk of memory.
 */
void *rtems_rbheap_allocate(rtems_rbheap_control *control, size_t size);

/**
 * @brief Frees a chunk of memory @a ptr allocated from the red-black tree heap
 * @a control.
 *
 * @param[in, out] control The red-black tree heap.
 * @param[in] ptr The pointer to the chunk of memory.
 *
 * @retval RTEMS_SUCCESSFUL Successful operation.
 * @retval RTEMS_INVALID_ID The chunk of memory is not a valid chunk in the
 * red-black tree heap.
 * @retval RTEMS_INCORRECT_STATE The chunk of memory is not in the right state.
 */
rtems_status_code rtems_rbheap_free(rtems_rbheap_control *control, void *ptr);

static inline rtems_chain_control *rtems_rbheap_get_spare_descriptor_chain(
  rtems_rbheap_control *control
)
{
  return &control->spare_descriptor_chain;
}

static inline void rtems_rbheap_add_to_spare_descriptor_chain(
  rtems_rbheap_control *control,
  rtems_rbheap_chunk *chunk
)
{
  rtems_chain_control *chain =
    rtems_rbheap_get_spare_descriptor_chain(control);

  rtems_chain_initialize_node(&chunk->chain_node);
  rtems_chain_prepend_unprotected(chain, &chunk->chain_node);
}

static inline void rtems_rbheap_set_extend_descriptors(
  rtems_rbheap_control *control,
  rtems_rbheap_extend_descriptors extend_descriptors
)
{
  control->extend_descriptors = extend_descriptors;
}

static inline void *rtems_rbheap_get_handler_arg(
  const rtems_rbheap_control *control
)
{
  return control->handler_arg;
}

static inline void rtems_rbheap_set_handler_arg(
  rtems_rbheap_control *control,
  void *handler_arg
)
{
  control->handler_arg = handler_arg;
}

/**
 * @brief Chunk descriptor extend handler that does nothing.
 */
void rtems_rbheap_extend_descriptors_never(rtems_rbheap_control *control);

/**
 * @brief Chunk descriptor extend handler that uses malloc().
 */
void rtems_rbheap_extend_descriptors_with_malloc(
  rtems_rbheap_control *control
);

/** @} */

/* Private API */

#define rtems_rbheap_chunk_of_node(node) \
  RTEMS_CONTAINER_OF(node, rtems_rbheap_chunk, tree_node)

static inline bool rtems_rbheap_is_chunk_free(const rtems_rbheap_chunk *chunk)
{
  return !rtems_chain_is_node_off_chain(&chunk->chain_node);
}

#ifdef __cplusplus
}
#endif

#endif /* _RTEMS_RBHEAP_H */
