// vim:set ft=cpp: -*- Mode: C++ -*-
/**
 * \file
 * Reference-counting capability allocator
 */
/*
 * (c) 2008-2010 Alexander Warg <warg@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/sys/task>
#include <l4/sys/assert.h>
#include <l4/re/consts>

namespace L4Re { namespace Util {

/**
 * Counter for Counting_cap_alloc with variable data width.
 *
 * This version is not thread safe.
 */
template< typename COUNTER = unsigned char >
struct Counter
{
  typedef COUNTER Type;
  Type _cnt;

  static Type nil() { return 0; }
  static Type unused() { return 0; }

  void free() { _cnt = 0; }
  bool is_free() const { return _cnt == 0; }
  bool is_saturated() const { return static_cast<Type>(_cnt + 1) == 0; }

  /**
   * Increment counter if not yet saturated.
   *
   * Once the counter reached the saturated state, the counter value isn't
   * changed.
   *
   * \retval false  The counter just went saturated after it was increased.
   * \retval true   Either the counter was already saturated -- in that case the
   *                counter value was not changed, or the counter was not
   *                saturated -- in that case the counter was increased.
   */
  bool inc()
  {
    if (is_saturated())
      return true; // no change and no warning
    ++_cnt;
    if (is_saturated())
      return false; // warn caller that counter is now saturated
    else
      return true; // success
  }

  /**
   * Decrement counter if not saturated.
   *
   * Once the counter reached the saturated state, the counter value isn't
   * changed.
   */
  Type dec()
  {
    if (is_saturated())
      return _cnt; // no change
    else
      return --_cnt; // success
  }

  bool try_alloc()
  {
    if (_cnt == 0)
      {
        _cnt = 1;
        return true;
      }
    return false;
  }
};

/**
 * Thread safe version of counter for Counting_cap_alloc.
 *
 * Despite using atomic instructions, this version has to make sure that
 * capability slots are not reused too early. If the last reference is gone,
 * the capability slot has to be unmapped. The slot must only be allocated
 * again when the unmap has completed. This is accomplished by starting with an
 * initial count of 2. The last reference will decrease the counter to 1. Only
 * then, after the slot was unmapped, will the counter be set to 0. This will
 * allow other threads to reallocate the slot.
 */
template< typename COUNTER = unsigned char >
struct Counter_atomic
{
  typedef COUNTER Type;
  Type _cnt;

  static Type nil() { return 0; }
  static Type unused() { return 1; }

  bool is_free() const { return __atomic_load_n(&_cnt, __ATOMIC_RELAXED) == 0; }
  static bool is_saturated(Type cnt) { return static_cast<Type>(cnt + 1) == 0; }

  bool try_alloc()
  {
    Type expected = nil();
    // Use "acquire" memory ordering. Any operations tied to the capability slot
    // must only be observable after the slot has been occupied.
    return __atomic_compare_exchange_n(&_cnt, &expected, 2, false,
                                       __ATOMIC_ACQUIRE, __ATOMIC_RELAXED);
  }

  /**
   * \copydoc Counter::inc
   */
  bool inc()
  {
    Type old_cnt = __atomic_load_n(&_cnt, __ATOMIC_RELAXED);
    Type new_cnt;
    do
      {
        if (is_saturated(old_cnt))
          return true; // no change and no warning
        new_cnt = old_cnt + 1;
      }
    while (!__atomic_compare_exchange_n(&_cnt, &old_cnt, new_cnt, false,
                                        __ATOMIC_RELAXED, __ATOMIC_RELAXED));
    if (is_saturated(new_cnt))
      return false; // warn caller that counter is now saturated
    else
      return true; // success
  }

  /**
   * \copydoc Counter::dec
   */
  Type dec()
  {
    Type old_cnt = __atomic_load_n(&_cnt, __ATOMIC_RELAXED);
    Type new_cnt;
    do
      {
        if (is_saturated(old_cnt))
          return old_cnt; // no change
        new_cnt = old_cnt - 1;
      }
    while (!__atomic_compare_exchange_n(&_cnt, &old_cnt, new_cnt, false,
                                        __ATOMIC_RELAXED, __ATOMIC_RELAXED));
    return new_cnt; // success
  }

  void free()
  {
    // Use "release" memory ordering to make sure that any operations tied to
    // the capability slot are observable by other threads before the slot can
    // be reused.
    __atomic_store_n(&_cnt, 0, __ATOMIC_RELEASE);
  }
};

/**
 * Internal reference-counting cap allocator
 *
 * This is intended for internal use only. L4Re applications should
 * use L4Re::Util::cap_alloc().
 *
 * Allocator for capability slots that automatically frees the slot
 * and optionally unmaps the capability when the reference count goes
 * down to zero. Reference counting must be done manually via take()
 * and release(). The backing store for the reference counters must be
 * provided in the setup() method. The allocator can recognize
 * capability slots that are not managed by itself and does nothing on
 * such slots.
 *
 * \note The user must ensure that the backing store is
 * zero-initialized.
 *
 * \note The user must ensure that the capability slots managed by
 * this allocator are not used by a different allocator, see setup().
 *
 * \note The operations in this class are not thread-safe.
 *
 * \ingroup api_l4re_util
 */
template <typename COUNTERTYPE, typename Dbg>
class Counting_cap_alloc
{
private:
  void operator = (Counting_cap_alloc const &) { }
  typedef COUNTERTYPE Counter;

  COUNTERTYPE *_items;
  long _free_hint;
  long _bias;
  long _capacity;
  Dbg *_dbg;

public:

  template <unsigned COUNT>
  struct Storage
  {
    COUNTERTYPE _buf[COUNT];
    typedef COUNTERTYPE Buf_type[COUNT];
    enum { Size = COUNT };
  };

  Counting_cap_alloc(long capacity, void *m, long bias, Dbg *dbg) noexcept
  : _items((Counter*)m), _free_hint(0), _bias(bias), _capacity(capacity),
    _dbg(dbg)
  {}

protected:
  /**
   * Create a new, empty allocator.
   *
   * Needs to be initialized with setup() before it can be used.
   */
  Counting_cap_alloc() noexcept
  : _items(0), _free_hint(0), _bias(0), _capacity(0)
  {}

  /**
   * Set up the backing memory for the allocator and the area of
   * managed capability slots.
   *
   * \param m        Pointer to backing memory.
   * \param capacity Number of capabilities that can be stored.
   * \param bias     First capability id to use by this allocator.
   * \param dbg      Logger for warnings if counter got saturated.
   *
   * The allocator will manage the capability slots between `bias`
   * and `bias` + `capacity` - 1 (inclusive). It is the
   * responsibility of the user to ensure that these slots are not
   * used otherwise.
   */
  void setup(void *m, long capacity, long bias, Dbg *dbg) noexcept
  {
    _items = static_cast<Counter*>(m);
    _capacity = capacity;
    _bias = bias;
    _dbg = dbg;
  }

public:
  /**
   * Allocate a new capability slot.
   *
   * \return The newly allocated capability slot, invalid if the allocator
   *         was exhausted.
   */
  L4::Cap<void> alloc() noexcept
  {
    long free_hint = __atomic_load_n(&_free_hint, __ATOMIC_RELAXED);

    for (long i = free_hint; i < _capacity; ++i)
      if (_items[i].try_alloc())
        {
          _free_hint = i + 1;
          return L4::Cap<void>((i + _bias) << L4_CAP_SHIFT);
        }

    // _free_hint is not necessarily correct in case of multi-threading! Make
    // sure we don't miss any potentially free slots.
    for (long i = 0; i < free_hint && i < _capacity; ++i)
      if (_items[i].try_alloc())
        {
          _free_hint = i + 1;
          return L4::Cap<void>((i + _bias) << L4_CAP_SHIFT);
        }

    return L4::Cap<void>::Invalid;
  }

  /// \copydoc alloc()
  template <typename T>
  L4::Cap<T> alloc() noexcept
  {
    return L4::cap_cast<T>(alloc());
  }


  /**
   * Increase the reference counter for the capability.
   *
   * \param cap Capability, whose reference counter should be increased.
   *
   * If the capability was still free, it will be automatically allocated.
   * Silently does nothing if the capability is not managed by this allocator.
   */
  void take(L4::Cap<void> cap) noexcept
  {
    long c;
    if (!range_check_and_get_idx(cap, &c))
      return;

    if (!L4_UNLIKELY(_items[c].inc()))
      _dbg->printf("Warning: Reference counter of cap 0x%lx now saturated!\n",
                   cap.cap() >> L4_CAP_SHIFT);
  }


  /**
   * Free the capability.
   *
   * \param cap  Capability to free.
   * \param task If set, task to unmap the capability from.
   * \param unmap_flags  Flags for unmap, see l4_unmap_flags_t.
   *
   * \pre The capability has been allocated. Calling free twice on a
   *      capability managed by this allocator results in undefined
   *      behaviour.
   *
   * \return True, if the capability was managed by this allocator.
   */
  bool free(L4::Cap<void> cap, l4_cap_idx_t task = L4_INVALID_CAP,
            unsigned unmap_flags = L4_FP_ALL_SPACES) noexcept
  {
    long c;
    if (!range_check_and_get_idx(cap, &c))
      return false;

    l4_assert(!_items[c].is_free());

    if (l4_is_valid_cap(task))
      l4_task_unmap(task, cap.fpage(), unmap_flags);

    if (c < _free_hint)
      _free_hint = c;

    _items[c].free();

    return true;
  }

  /**
   * Decrease the reference counter for a capability.
   *
   * \param cap  Capability to release.
   * \param task If set, task to unmap the capability from.
   * \param unmap_flags  Flags for unmap, see l4_unmap_flags_t.
   *
   * \pre The capability has been allocated. Calling release on a free
   *      capability results in undefined behaviour.
   *
   * \return True, if the capability was freed as a result of
   *         this operation. If false is returned the capability
   *         is either still in use or is not managed by this
   *         allocator.
   *
   * Does nothing apart from returning false if the capability is not
   * managed by this allocator.
   */
  bool release(L4::Cap<void> cap, l4_cap_idx_t task = L4_INVALID_CAP,
               unsigned unmap_flags = L4_FP_ALL_SPACES) noexcept
  {
    long c;
    if (!range_check_and_get_idx(cap, &c))
      return false;

    l4_assert(!_items[c].is_free());

    if (_items[c].dec() == Counter::unused())
      {
        if (task != L4_INVALID_CAP)
          l4_task_unmap(task, cap.fpage(), unmap_flags);

        if (c < _free_hint)
          _free_hint = c;

        // Let others allocate this slot only after the l4_task_unmap() has
        // finished.
        _items[c].free();

        return true;
      }
    return false;
  }

  /**
   * Return highest capability id managed by this allocator.
   */
  long last() noexcept
  {
    return _capacity + _bias - 1;
  }

private:
  bool range_check_and_get_idx(L4::Cap<void> cap, long *c)
  {
    *c = cap.cap() >> L4_CAP_SHIFT;
    if (*c < _bias)
      return false;

    *c -= _bias;

    return *c < _capacity;
  }
};

}}
