// vim:set ft=cpp: -*- Mode: C++ -*-
/*
 * (c) 2008-2009 Alexander Warg <warg@os.inf.tu-dresden.de>,
 *               Torsten Frenzel <frenzel@os.inf.tu-dresden.de>
 *     economic rights: Technische Universität Dresden (Germany)
 *
 * License: see LICENSE.spdx (in this directory or the directories above)
 */

#pragma once

#include <l4/cxx/arith>
#include <l4/cxx/minmax>
#include <l4/cxx/type_traits>
#include <l4/sys/consts.h>

namespace cxx {

/**
 * Standard list-based allocator.
 */
class List_alloc
{
private:
  friend class List_alloc_sanity_guard;

  struct Mem_block
  {
    Mem_block *next;
    unsigned long size;
  };

  Mem_block *_first;

  inline void check_overlap(void *, unsigned long );
  inline void sanity_check_list(char const *, char const *);
  inline void merge();

public:

  /**
   * Initializes an empty list allocator.
   *
   * \note To initialize the allocator with available memory
   *       use the #free() function.
   */
  List_alloc() : _first(0) {}

  /**
   * Return a free memory block to the allocator.
   *
   * \param block        Pointer to memory block.
   * \param size         Size of memory block.
   * \param initial_free Set to true for putting fresh memory
   *                     to the allocator. This will enforce alignment on that
   *                     memory.
   *
   * \pre `block` must not be NULL.
   * \pre `2 * sizeof(void *)` <= `size` <= `~0UL - 32`.
   */
  inline void free(void *block, unsigned long size, bool initial_free = false);

  /**
   * Allocate a memory block.
   *
   * \param size  Size of the memory block.
   * \param align Alignment constraint.
   * \param lower Lower bound of the physical region the memory block should be
   *              allocated from.
   * \param upper Upper bound of the physical region the memory block should be
   *              allocated from, value is inclusive.
   *
   * \return      Pointer to memory block
   *
   * \pre 0 < `size` <= `~0UL - 32`.
   */
  inline void *alloc(unsigned long size, unsigned long align,
                     unsigned long lower = 0, unsigned long upper = ~0UL);

  /**
   * Allocate a memory block of `min` <= size <= `max`.
   *
   * \param         min          Minimal size to allocate (in bytes).
   * \param[in,out] max          Maximum size to allocate (in bytes). The actual
   *                             allocated size is returned here.
   * \param         align        Alignment constraint.
   * \param         granularity  Granularity to use for the allocation (power
   *                             of 2).
   * \param         lower        Lower bound of the physical region the memory
   *                             block should be allocated from.
   * \param         upper        Upper bound of the physical region the memory
   *                             block should be allocated from, value is
   *                             inclusive.
   *
   * \return  Pointer to memory block
   *
   * \pre 0 < `min` <= `~0UL - 32`.
   * \pre 0 < `max`.
   */
  inline void *alloc_max(unsigned long min, unsigned long *max,
                         unsigned long align, unsigned granularity,
                         unsigned long lower = 0, unsigned long upper = ~0UL);

  /**
   * Get the amount of available memory.
   *
   * \return Available memory in bytes
   */
  inline unsigned long avail();

  template <typename DBG>
  void dump_free_list(DBG &out);
};

#if !defined (CXX_LIST_ALLOC_SANITY)
class List_alloc_sanity_guard
{
public:
  List_alloc_sanity_guard(List_alloc *, char const *)
  {}

};


void
List_alloc::check_overlap(void *, unsigned long )
{}

void
List_alloc::sanity_check_list(char const *, char const *)
{}

#else

class List_alloc_sanity_guard
{
private:
  List_alloc *a;
  char const *func;

public:
  List_alloc_sanity_guard(List_alloc *a, char const *func)
    : a(a), func(func)
  { a->sanity_check_list(func, "entry"); }

  ~List_alloc_sanity_guard()
  { a->sanity_check_list(func, "exit"); }
};

void
List_alloc::check_overlap(void *b, unsigned long s)
{
  unsigned long const mb_align = (1UL << arith::Ld<sizeof(Mem_block)>::value) - 1;
  if ((unsigned long)b & mb_align)
    {
      L4::cerr << "List_alloc(FATAL): trying to free unaligned memory: "
               << b << " align=" << arith::Ld<sizeof(Mem_block)>::value << "\n";
    }

  Mem_block *c = _first;
  for (;c ; c = c->next)
    {
      unsigned long x_s = (unsigned long)b;
      unsigned long x_e = x_s + s;
      unsigned long b_s = (unsigned long)c;
      unsigned long b_e = b_s + c->size;

      if ((x_s >= b_s && x_s < b_e)
          || (x_e > b_s && x_e <= b_e)
          || (b_s >= x_s && b_s < x_e)
          || (b_e > x_s && b_e <= x_e))
      {
        L4::cerr << "List_alloc(FATAL): trying to free memory that "
                    "is already free: \n  ["
                 << (void*)x_s << '-' << (void*)x_e << ") overlaps ["
                 << (void*)b_s << '-' << (void*)b_e << ")\n";
      }
    }
}

void
List_alloc::sanity_check_list(char const *func, char const *info)
{
  Mem_block *c = _first;
  for (;c ; c = c->next)
    {
      if (c->next)
        {
          if (c >= c->next)
            {
              L4::cerr << "List_alloc(FATAL): " << func << '(' << info
                       << "): list order violation\n";
            }

          if (((unsigned long)c) + c->size > (unsigned long)c->next)
            {
              L4::cerr << "List_alloc(FATAL): " << func << '(' << info
                       << "): list order violation\n";
            }
        }
    }
}

#endif

void
List_alloc::merge()
{
  List_alloc_sanity_guard __attribute__((unused)) guard(this, __func__);
  Mem_block *c = _first;
  while (c && c->next)
    {
      unsigned long f_start = reinterpret_cast<unsigned long>(c);
      unsigned long f_end   = f_start + c->size;
      unsigned long n_start = reinterpret_cast<unsigned long>(c->next);

      if (f_end == n_start)
        {
          c->size += c->next->size;
          c->next = c->next->next;
          continue;
        }

      c = c->next;
    }
}

void
List_alloc::free(void *block, unsigned long size, bool initial_free)
{
  List_alloc_sanity_guard __attribute__((unused)) guard(this, __func__);

  unsigned long const mb_align = (1UL << arith::Ld<sizeof(Mem_block)>::value) - 1;

  if (initial_free)
    {
      // enforce alignment constraint on initial memory
      unsigned long nblock = (reinterpret_cast<unsigned long>(block) + mb_align)
                             & ~mb_align;
      size = (size - (nblock - reinterpret_cast<unsigned long>(block)))
             & ~mb_align;
      block = reinterpret_cast<void*>(nblock);
    }
  else
    // blow up size to the minimum aligned size
    size = (size + mb_align) & ~mb_align;

  check_overlap(block, size);

  Mem_block **c = &_first;
  Mem_block *next = 0;

  if (*c)
    {
      while (*c && *c < block)
        c = &(*c)->next;

      next = *c;
    }

  *c = reinterpret_cast<Mem_block*>(block);

  (*c)->next = next;
  (*c)->size = size;

  merge();
}

void *
List_alloc::alloc_max(unsigned long min, unsigned long *max, unsigned long align,
                      unsigned granularity, unsigned long lower,
                      unsigned long upper)
{
  List_alloc_sanity_guard __attribute__((unused)) guard(this, __func__);

  unsigned char const mb_bits = arith::Ld<sizeof(Mem_block)>::value;
  unsigned long const mb_align = (1UL << mb_bits) - 1;

  // blow minimum up to at least the minimum aligned size of a Mem_block
  min = l4_round_size(min, mb_bits);
  // truncate maximum to at least the size of a Mem_block
  *max = l4_trunc_size(*max, mb_bits);
  // truncate maximum size according to granularity
  *max = *max & ~(granularity - 1UL);

  if (min > *max)
    return 0;

  unsigned long almask = align ? (align - 1UL) : 0;

  // minimum alignment is given by the size of a Mem_block
  if (almask < mb_align)
    almask = mb_align;

  Mem_block **c = &_first;
  Mem_block **fit = 0;
  unsigned long max_fit = 0;
  unsigned long a_lower = (lower + almask) & ~almask;

  for (; *c; c = &(*c)->next)
    {
      // address of free memory block
      unsigned long n_start = reinterpret_cast<unsigned long>(*c);

      // block too small, next
      // XXX: maybe we can skip this and just do the test below
      if ((*c)->size < min)
        continue;

      // block outside region, next
      if (upper < n_start || a_lower > n_start + (*c)->size)
        continue;

      // aligned start address within the free block
      unsigned long a_start = (n_start + almask) & ~almask;

      // check if aligned start address is behind the block, next
      if (a_start - n_start >= (*c)->size)
        continue;

      a_start = a_start < a_lower ? a_lower : a_start;

      // end address would overflow, next
      if (min > ~0UL - a_start)
        continue;

      // block outside region, next
      if (a_start + min - 1UL > upper)
        continue;

      // remaining size after subtracting the padding for the alignment
      unsigned long r_size = (*c)->size - a_start + n_start;

      // upper limit can limit maximum size
      if (a_start + r_size - 1UL > upper)
        r_size = upper - a_start + 1UL;

      // round down according to granularity
      r_size &= ~(granularity - 1UL);

      // block too small
      if (r_size < min)
        continue;

      if (r_size >= *max)
        {
          fit = c;
          max_fit = *max;
          break;
        }

      if (r_size > max_fit)
        {
          max_fit = r_size;
          fit = c;
        }
    }

  if (fit)
    {
      unsigned long n_start = reinterpret_cast<unsigned long>(*fit);
      unsigned long a_lower = (lower + almask) & ~almask;
      unsigned long a_start = (n_start + almask) & ~almask;
      a_start = a_start < a_lower ? a_lower : a_start;
      unsigned long r_size = (*fit)->size - a_start + n_start;

      if (a_start > n_start)
        {
          (*fit)->size -= r_size;
          fit = &(*fit)->next;
        }
      else
        *fit = (*fit)->next;

      *max = max_fit;
      if (r_size == max_fit)
        return reinterpret_cast<void *>(a_start);

      Mem_block *m = reinterpret_cast<Mem_block*>(a_start + max_fit);
      m->next = *fit;
      m->size = r_size - max_fit;
      *fit = m;
      return reinterpret_cast<void *>(a_start);
    }

  return 0;
}

void *
List_alloc::alloc(unsigned long size, unsigned long align, unsigned long lower,
                  unsigned long upper)
{
  List_alloc_sanity_guard __attribute__((unused)) guard(this, __func__);

  unsigned long const mb_align
    = (1UL << arith::Ld<sizeof(Mem_block)>::value) - 1;

  // blow up size to the minimum aligned size
  size = (size + mb_align) & ~mb_align;

  unsigned long almask = align ? (align - 1UL) : 0;

  // minimum alignment is given by the size of a Mem_block
  if (almask < mb_align)
    almask = mb_align;

  Mem_block **c = &_first;
  unsigned long a_lower = (lower + almask) & ~almask;

  for (; *c; c=&(*c)->next)
    {
      // address of free memory block
      unsigned long n_start = reinterpret_cast<unsigned long>(*c);

      // block too small, next
      // XXX: maybe we can skip this and just do the test below
      if ((*c)->size < size)
        continue;

      // block outside region, next
      if (upper < n_start || a_lower > n_start + (*c)->size)
        continue;

      // aligned start address within the free block
      unsigned long a_start = (n_start + almask) & ~almask;

      // block too small after alignment, next
      if (a_start - n_start >= (*c)->size)
        continue;

      a_start = a_start < a_lower ? a_lower : a_start;

      // end address would overflow, next
      if (size > ~0UL - a_start)
        continue;

      // block outside region, next
      if (a_start + size - 1UL > upper)
        continue;

      // remaining size after subtracting the padding
      // for the alignment
      unsigned long r_size = (*c)->size - a_start + n_start;

      // block too small
      if (r_size < size)
        continue;

      if (a_start > n_start)
        {
          // have free space before the allocated block
          // shrink the block and set c to the next pointer of that
          // block
          (*c)->size -= r_size;
          c = &(*c)->next;
        }
      else
        // drop the block, c remains the next pointer of the
        // previous block
        *c = (*c)->next;

      // allocated the whole remaining space
      if (r_size == size)
        return reinterpret_cast<void*>(a_start);

      // add a new free block behind the allocated block
      Mem_block *m = reinterpret_cast<Mem_block*>(a_start + size);
      m->next = *c;
      m->size = r_size - size;
      *c = m;
      return reinterpret_cast<void *>(a_start);
    }

  return 0;
}

unsigned long
List_alloc::avail()
{
  List_alloc_sanity_guard __attribute__((unused)) guard(this, __FUNCTION__);
  Mem_block *c = _first;
  unsigned long a = 0;
  while (c)
    {
      a += c->size;
      c = c->next;
    }

  return a;
}

template <typename DBG>
void
List_alloc::dump_free_list(DBG &out)
{
  Mem_block *c = _first;
  while (c)
    {
      static constexpr char const *const unitstr[4] =
      { "Byte", "KiB", "MiB", "GiB" };

      unsigned sz = c->size;
      unsigned i;
      for (i = 0; i < cxx::array_size(unitstr) && sz > 8 << 10; ++i)
        sz >>= 10;

      out.printf("%12p - %12p (%u %s)\n",
                 c, reinterpret_cast<char *>(c) + c->size - 1, sz, unitstr[i]);

      c = c->next;
    }
}

}
