// vi:set ft=cpp: -*- Mode: C++ -*-
/*
 * (c) 2008-2009 Adam Lackorzynski <adam@os.inf.tu-dresden.de>,
 *               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 <string.h>
#include <stddef.h>
#include <l4/bid_config.h>
#include <l4/sys/types.h>
#include <l4/cxx/minmax>
#include <l4/re/dataspace>
#include <l4/re/dataspace-sys.h>
#include <l4/sys/cxx/ipc_legacy>

namespace L4Re { namespace Util {

/**
 * Dataspace server class.
 *
 * The default implementation of the interface provides a continuous dataspace
 * with contiguous pages.
 */
class Dataspace_svr
{
public:
  L4_RPC_LEGACY_DISPATCH(L4Re::Dataspace);

  typedef L4::Ipc::Snd_fpage::Map_type Map_type;
  typedef L4::Ipc::Snd_fpage::Cacheopt Cache_type;

  Dataspace_svr() noexcept
  : _ds_start(0), _ds_size(0), _map_flags(L4::Ipc::Snd_fpage::Map),
    _cache_flags(L4::Ipc::Snd_fpage::Cached)
  {}

  virtual ~Dataspace_svr() noexcept {}

  /**
   * Map a region of the dataspace
   *
   * \param      offset      Offset to start within data space
   * \param      local_addr  Local address to map to.
   * \param      flags       Dataspace flags, see #L4Re::Dataspace::F::Flags.
   * \param      min_addr    Defines start of receive window.
   * \param      max_addr    Defines end of receive window.
   * \param[out] memory      Send fpage to map
   *
   * \retval 0   Success
   * \retval <0  Error
   */
  int map(Dataspace::Offset offset,
          Dataspace::Map_addr local_addr,
          Dataspace::Flags flags,
          Dataspace::Map_addr min_addr,
          Dataspace::Map_addr max_addr,
          L4::Ipc::Snd_fpage &memory)
  {
    memory = L4::Ipc::Snd_fpage();

    offset     = l4_trunc_page(offset);
    local_addr = l4_trunc_page(local_addr);

    if (!check_limit(offset))
      {
#if 0
        printf("limit failed: off=%lx sz=%lx\n", offset, size());
#endif
        return -L4_ERANGE;
      }

    min_addr = l4_trunc_page(min_addr);
    max_addr = l4_round_page(max_addr);

    l4_addr_t addr = _ds_start + offset;
    unsigned char order = L4_PAGESHIFT;

    while (order < 30 /* limit to 1GB flexpage */)
      {
        l4_addr_t map_base = l4_trunc_size(addr, order + 1);
        if (map_base < _ds_start)
          break;

        if (map_base + (1UL << (order + 1)) -1 > (_ds_start + round_size() - 1))
          break;

        map_base = l4_trunc_size(local_addr, order + 1);
        if (map_base < min_addr)
          break;

        if (map_base + (1UL << (order + 1)) -1 > max_addr -1)
          break;

        l4_addr_t mask = ~(~0UL << (order + 1));
        if (local_addr == ~0UL || ((addr ^ local_addr) & mask))
          break;

        ++order;
      }

    l4_addr_t map_base = l4_trunc_size(addr, order);

    Dataspace::Map_addr b = map_base;
    unsigned send_order = order;
    int err = map_hook(offset /*map_base - _ds_start*/, order, flags,
                       &b, &send_order);
    if (err < 0)
      return err;

    l4_fpage_t fpage = l4_fpage(b, send_order, flags.fpage_rights());

    memory = L4::Ipc::Snd_fpage(fpage, local_addr, _map_flags, _cache_flags);

    return L4_EOK;
  }

  /**
   * A hook that is called for acquiring the data to be mapped.
   *
   * \param offs        Offset in dataspace to supply
   * \param order       Log2-size of data to supply
   * \param flags       Flags for the mapping
   * \param base        Start address of the flexpage to be mapped
   * \param send_order  Order (log2 of size) of the flexpage to be mapped
   *
   * \retval <0   Error and the map request will be aborted with that error.
   * \retval >=0  Success
   *
   * \see map
   */
  virtual int map_hook([[maybe_unused]] Dataspace::Offset offs,
                       [[maybe_unused]] unsigned order,
                       [[maybe_unused]] Dataspace::Flags flags,
                       [[maybe_unused]] Dataspace::Map_addr *base,
                       [[maybe_unused]] unsigned *send_order)
  {
    return 0;
  }

  /**
   * Take a reference to this dataspace
   *
   * Default does nothing.
   */
  virtual void take() noexcept
  {}

  /**
   * Release a reference to this dataspace
   *
   * \return Number of references to the dataspace
   *
   * Default does nothing and returns always zero.
   */
  virtual unsigned long release() noexcept
  { return 0; }

  /**
   * Copy from src dataspace to this destination dataspace
   *
   * \param dst_offs  Offset into the destination dataspace
   * \param src_id    Local id of the source dataspace
   * \param src_offs  Offset into the source dataspace
   * \param size      Number of bytes to copy
   *
   * \retval >=0  Number of bytes copied
   * \retval <0   An error occured. The error code may depend on the
   *              implementation.
   */
  virtual long copy([[maybe_unused]] l4_addr_t dst_offs,
                    [[maybe_unused]] l4_umword_t src_id,
                    [[maybe_unused]] l4_addr_t src_offs,
                    [[maybe_unused]] unsigned long size) noexcept
  {
    return -L4_ENODEV;
  }

  /**
   * Clear a region in the dataspace
   *
   * \param offs Start of the region
   * \param size Size of the region
   *
   * \retval 0   Success
   * \retval <0  Error
   */
  virtual long clear(unsigned long offs, unsigned long size) const noexcept
  {
    if (!check_limit(offs))
      return -L4_ERANGE;

    unsigned long sz = size = cxx::min(size, round_size() - offs);

    while (sz)
      {
        unsigned long b_addr = _ds_start + offs;
        unsigned long b_sz = cxx::min(size - offs, sz);

        memset(reinterpret_cast<void *>(b_addr), 0, b_sz);

        offs += b_sz;
        sz -= b_sz;
      }

    return 0;
  }

  /**
   * Allocate a region within a dataspace
   *
   * \param offset  Offset in the dataspace, in bytes.
   * \param size    Size of the range, in bytes.
   * \param access  Access mode with which the memory backing the dataspace
   *                region should be allocated.
   *
   * \retval 0   Success
   * \retval <0  Error
   */
  virtual long allocate([[maybe_unused]] l4_addr_t offset,
                        [[maybe_unused]] l4_size_t size,
                        [[maybe_unused]] unsigned access) noexcept
  {
    return -L4_ENODEV;
  }

  /**
   * Define the size of the flexpage to map
   *
   * \return flexpage size
   */
  virtual unsigned long page_shift() const noexcept
  { return L4_LOG2_PAGESIZE; }

  /**
   * Return whether the dataspace is static
   *
   * \return True if dataspace is static
   */
  virtual bool is_static() const noexcept
  { return true; }

  /**
   * Return mapping information for no-MMU systems.
   *
   * The method is only called on no-MMU systems. It should return the address
   * of the underlying backing buffer so that the caller might map the
   * dataspace.
   *
   * The default implementation always returns an error because the derived
   * class must provide the required information.
   *
   * \see L4Re::Dataspace::map_info()
   */
  virtual long map_info([[maybe_unused]] l4_addr_t &start_addr,
                        [[maybe_unused]] l4_addr_t &end_addr) noexcept
  { return -L4_EPERM; }


  long op_map(L4Re::Dataspace::Rights rights,
              L4Re::Dataspace::Offset offset,
              L4Re::Dataspace::Map_addr spot,
              L4Re::Dataspace::Flags flags,
              L4::Ipc::Snd_fpage &fp)
  {
    auto rf = map_flags(rights);

    if (!rf.w() && flags.w())
      return -L4_EPERM;

    return map(offset, spot, flags & rf, 0, ~0, fp);
  }

  long op_allocate(L4Re::Dataspace::Rights rights,
                   L4Re::Dataspace::Offset offset,
                   L4Re::Dataspace::Size size)
  { return allocate(offset, size, rights & 3); }

  long op_copy_in(L4Re::Dataspace::Rights rights,
                  L4Re::Dataspace::Offset dst_offs,
                  L4::Ipc::Snd_fpage const &src_cap,
                  L4Re::Dataspace::Offset src_offs,
                  L4Re::Dataspace::Size sz)
  {
    if (!src_cap.id_received())
      return -L4_EINVAL;

    if (!(rights & L4_CAP_FPAGE_W))
      return -L4_EACCESS;

    if (sz == 0)
      return L4_EOK;

    return copy(dst_offs, src_cap.data(), src_offs, sz);
  }

  long op_info(L4Re::Dataspace::Rights rights, L4Re::Dataspace::Stats &s)
  {
    s.size = size();
    // only return writable if really writable
    s.flags = Dataspace::Flags(0);
    if (map_flags(rights).w())
      s.flags |= Dataspace::F::W;
    return L4_EOK;
  }

  long op_clear(L4Re::Dataspace::Rights rights,
                L4Re::Dataspace::Offset offset,
                L4Re::Dataspace::Size size)
  {
    if (!map_flags(rights).w())
      return -L4_EACCESS;

    return clear(offset, size);
  }

  long op_map_info(L4Re::Dataspace::Rights,
                   [[maybe_unused]] l4_addr_t &start_addr,
                   [[maybe_unused]] l4_addr_t &end_addr)
  {
#ifdef CONFIG_MMU
    return 0;
#else
    return map_info(start_addr, end_addr);
#endif
  }

protected:
  unsigned long size() const noexcept
  { return _ds_size; }
  unsigned long map_flags() const noexcept
  { return _map_flags; }
  unsigned long page_size() const noexcept
  { return 1UL << page_shift(); }
  unsigned long round_size() const noexcept
  { return l4_round_size(size(), page_shift()); }
  bool check_limit(l4_addr_t offset) const noexcept
  { return offset < round_size(); }

  L4Re::Dataspace::Flags
  map_flags(L4Re::Dataspace::Rights rights = L4_CAP_FPAGE_W) const noexcept
  {
    auto f = (_rw_flags & L4Re::Dataspace::Flags(0x0f)) | L4Re::Dataspace::F::Caching_mask;
    if (!(rights & L4_CAP_FPAGE_W))
      f &= ~L4Re::Dataspace::F::W;

    return f;
  }

protected:
  void size(unsigned long size) noexcept { _ds_size = size; }

  l4_addr_t _ds_start;
  l4_size_t _ds_size;
  Map_type _map_flags;
  Cache_type _cache_flags;
  L4Re::Dataspace::Flags _rw_flags;
};

}}
