// -*- Mode: C++ -*-
// vim:ft=cpp
/**
 * \file
 * \brief   Region mapper interface
 */
/*
 * (c) 2008-2009 Adam Lackorzynski <adam@os.inf.tu-dresden.de>,
 *               Alexander Warg <warg@os.inf.tu-dresden.de>,
 *               Björn Döbel <doebel@os.inf.tu-dresden.de>,
 *               Torsten Frenzel <frenzel@os.inf.tu-dresden.de>
 *     economic rights: Technische Universität Dresden (Germany)
 *
 * This file is part of TUD:OS and distributed under the terms of the
 * GNU General Public License 2.
 * Please see the COPYING-GPL-2 file for details.
 *
 * As a special exception, you may use this file as part of a free software
 * library without restriction.  Specifically, if other files instantiate
 * templates or use macros or inline functions from this file, or you compile
 * this file and link it with other files to produce an executable, this
 * file does not by itself cause the resulting executable to be covered by
 * the GNU General Public License.  This exception does not however
 * invalidate any other reasons why the executable file might be covered by
 * the GNU General Public License.
 */
#pragma once

#include <l4/sys/types.h>
#include <l4/sys/l4int.h>
#include <l4/sys/capability>
#include <l4/re/protocols.h>
#include <l4/sys/pager>
#include <l4/sys/cxx/ipc_iface>
#include <l4/sys/cxx/ipc_ret_array>
#include <l4/sys/cxx/types>
#include <l4/re/consts>
#include <l4/re/dataspace>

namespace L4Re {

/**
 * \defgroup api_l4re_rm Region map API
 * \ingroup api_l4re
 * \brief Virtual address-space management.
 *
 * A region map object implements two protocols. The first protocol is the
 * kernel page-fault protocol, to resolve page faults for threads running in an
 * L4 task. The second protocol is the region map protocol itself, which allows
 * managing the virtual memory address space of an L4 task.
 *
 * There are two basic concepts provided by the region map abstraction:
 * - **Areas** are reserved ranges in the virtual memory address space.
 * - **Regions** are areas that are backed by (part of) a dataspace, i.e.
 *   accessing them results in access to the physical memory the dataspace
 *   manages.
 *
 * Areas can be reserved for special use or for attaching a dataspace at a later
 * time. When attaching a dataspace, the user can instruct the region map to
 * search for an appropriate range to attach to. Regions are skipped in this
 * search since they already have dataspaces attached to them, and areas are
 * skipped because they are reserved.
 *
 * When a region map receives a page fault IPC, the region map will check if the
 * faulting virtual address lies in a region. If yes, it will answer the page
 * fault IPC with a mapping from the backing dataspace. If not, an error is
 * returned.
 *
 * \see L4Re::Dataspace, L4Re::Rm,
 * \see \ref l4re_concepts_ds_rm
 */

/**
 * \brief Region map
 * \headerfile rm l4/re/rm
 * \ingroup api_l4re_rm
 *
 * \see \link api_l4re_rm Region map API \endlink.
 */
class L4_EXPORT Rm :
  public L4::Kobject_t<Rm, L4::Pager, L4RE_PROTO_RM,
                       L4::Type_info::Demand_t<1> >
{
public:
  typedef L4Re::Dataspace::Offset Offset;

  /// Result values for detach operation.
  enum Detach_result
  {
    Detached_ds  = 0,      ///< Detached data sapce.
    Kept_ds      = 1,      ///< Kept data space.
    Split_ds     = 2,      ///< Splitted data space, and done.
    Detach_result_mask = 3,

    Detach_again = 4,      ///< Detached data space, more to do.
  };


  enum Region_flag_shifts
  {
    /// Start of Rm cache bits
    Caching_shift    = Dataspace::F::Caching_shift,
  };

  /** Rm flags definitions. */
  struct F
  {
    /// Flags for attach operation.
    enum Attach_flags : l4_uint32_t
    {
      /// Search for a suitable address range.
      Search_addr  = 0x20000,
      /// Search only in area, or map into area.
      In_area      = 0x40000,
      /// Eagerly map the attached data space in.
      Eager_map    = 0x80000,
      /// Mask of all attach flags.
      Attach_mask  = 0xf0000,
    };

    L4_TYPES_FLAGS_OPS_DEF(Attach_flags);

    enum Region_flags : l4_uint16_t
    {
      /// Region rights
      Rights_mask = 0x0f,
      R         = Dataspace::F::R,
      W         = Dataspace::F::W,
      X         = Dataspace::F::X,
      RW        = Dataspace::F::RW,
      RX        = Dataspace::F::RX,
      RWX       = Dataspace::F::RWX,

      /// Free the portion of the data space after detach
      Detach_free = 0x200,
      /// Region has a pager
      Pager       = 0x400,
      /// Region is reserved (blocked)
      Reserved    = 0x800,


      /// Mask of all Rm cache bits
      Caching_mask   = Dataspace::F::Caching_mask,
      /// Cache bits for normal cacheable memory
      Cache_normal   = Dataspace::F::Normal,
      /// Cache bits for buffered (write combining) memory
      Cache_buffered = Dataspace::F::Bufferable,
      /// Cache bits for uncached memory
      Cache_uncached = Dataspace::F::Uncacheable,

      /// Mask for all bits for cache options and rights.
      Ds_map_mask    = 0xff,

      /// Mask of all region flags
      Region_flags_mask = 0xffff,
    };

    L4_TYPES_FLAGS_OPS_DEF(Region_flags);

    friend constexpr Dataspace::Flags map_flags(Region_flags rf)
    {
      return Dataspace::Flags((l4_uint16_t)rf & Ds_map_mask);
    }

    struct Flags : L4::Types::Flags_ops_t<Flags>
    {
      l4_uint32_t raw;
      Flags() = default;
      explicit constexpr Flags(l4_uint32_t f) : raw(f) {}
      constexpr Flags(Attach_flags rf) : raw((l4_uint32_t)rf) {}
      constexpr Flags(Region_flags rf) : raw((l4_uint32_t)rf) {}

      friend constexpr Dataspace::Flags map_flags(Flags f)
      {
        return Dataspace::Flags(f.raw & Ds_map_mask);
      }

      constexpr Region_flags region_flags() const
      {
        return Region_flags(raw & Region_flags_mask);
      }

      constexpr Attach_flags attach_flags() const
      {
        return Attach_flags(raw & Attach_mask);
      }

      constexpr bool r() const { return raw & L4_FPAGE_RO; }
      constexpr bool w() const { return raw & L4_FPAGE_W; }
      constexpr bool x() const { return raw & L4_FPAGE_X; }
      constexpr unsigned cap_rights() const
      { return w() ? L4_CAP_FPAGE_RW : L4_CAP_FPAGE_RO; }
    };

    friend constexpr Flags operator | (Region_flags l, Attach_flags r)
    { return Flags(l) | Flags(r); }

    friend constexpr Flags operator | (Attach_flags l, Region_flags r)
    { return Flags(l) | Flags(r); }
  };

  using Attach_flags = F::Attach_flags;
  using Region_flags = F::Region_flags;
  using Flags = F::Flags;

  /// Flags for detach operation
  enum Detach_flags
  {
    /**
     * \brief Do an unmap of the exact region given.
     * \internal
     *
     * This flag is useful for _detach().
     *
     * Using this mode for detach, unmaps the exact region given.
     * This has the effect that parts of regions may stay in the address space.
     */
    Detach_exact   = 1,
    /**
     * \brief Do an unmap of all overlapping regions.
     * \internal
     *
     * This flag is useful for _detach().
     *
     * Using this mode for detach, unmaps all regions that overlap with
     * the given region.
     */
    Detach_overlap = 2,

    /**
     * \brief Do not free the detached data space, ignore the F::Detach_free
     * \internal
     *
     * This flag is useful for _detach().
     *
     */
    Detach_keep = 4,
  };

  /**
   * \brief Reserve the given area in the region map.
   * \param[in,out] start  The virtual start address of the area to reserve.
   *                       Returns the start address of the area.
   * \param         size   The size of the area to reserve (in bytes).
   * \param         flags  Flags for the reserved area (see
   *                       #L4Re::Rm::F::Region_flags and
   *                       #L4Re::Rm::F::Attach_flags).
   * \param         align  Alignment of area if searched as bits (log2 value).
   * \retval 0                  Success
   * \retval -L4_EADDRNOTAVAIL  The given area cannot be reserved.
   * \retval <0                 IPC errors
   *
   * This function reserves an area within the virtual address space managed
   * by the region map. There are two kinds of areas available:
   * - Reserved areas (\a flags = F::Reserved), where no data spaces can be
   *   attached
   * - Special purpose areas (\a flags = 0), where data spaces can be attached
   *   to the area via the F::In_area flag and a start address within the area
   *   itself.
   *
   * \note When searching for a free place in the virtual address space
   * (with \a flags = F::Search_addr), the space between \a start and the end of
   * the virtual address space is searched.
   */
  long reserve_area(l4_addr_t *start, unsigned long size,
                    Flags flags = Flags(0),
                    unsigned char align = L4_PAGESHIFT) const noexcept
  { return reserve_area_t::call(c(), start, size, flags, align); }

  L4_RPC_NF(long, reserve_area, (L4::Ipc::In_out<l4_addr_t *> start,
                                 unsigned long size,
                                 Flags flags,
                                 unsigned char align));

  /**
   * \brief Reserve the given area in the region map.
   * \param[in,out] start  The virtual start address of the area to reserve.
   *                       Returns the start address of the area.
   * \param         size   The size of the area to reserve (in bytes).
   * \param         flags  Flags for the reserved area (see F::Region_flags and
   *                       F::Attach_flags).
   * \param         align  Alignment of area if searched as bits (log2 value).
   * \retval 0                  Success
   * \retval -L4_EADDRNOTAVAIL  The given area cannot be reserved.
   * \retval <0                 IPC errors
   *
   * For more information, please refer to the analogous function
   * \see L4Re::Rm::reserve_area.
   */
  template< typename T >
  long reserve_area(T **start, unsigned long size,
                    Flags flags = Flags(0),
                    unsigned char align = L4_PAGESHIFT) const noexcept
  { return reserve_area_t::call(c(), (l4_addr_t*)start, size, flags, align); }

  /**
   * \brief Free an area from the region map.
   *
   * \param addr  An address within the area to free.
   * \retval 0           Success
   * \retval -L4_ENOENT  No area found.
   * \retval <0          IPC errors
   *
   * \note The data spaces that are attached to that area are not detached by
   *       this operation.
   * \see reserve_area() for more information about areas.
   */
  L4_RPC(long, free_area, (l4_addr_t addr));

  L4_RPC_NF(long, attach, (L4::Ipc::In_out<l4_addr_t *> start,
                           unsigned long size, Flags flags,
                           L4::Ipc::Opt<L4::Ipc::Cap<Dataspace> > mem,
                           Offset offs, unsigned char align,
                           L4::Ipc::Opt<l4_cap_idx_t> client_cap));

  L4_RPC_NF(long, detach, (l4_addr_t addr, unsigned long size, unsigned flags,
                           l4_addr_t &start, l4_addr_t &rsize,
                           l4_cap_idx_t &mem_cap));

  /**
   * \brief Attach a data space to a region.
   *
   * \param[in,out]  start  Virtual start address where the region manager
   *                        shall attach the data space. Will be rounded down to
   *                        the nearest start of a page.
   *                        If #L4Re::Rm::F::Search_addr is given this value is
   *                        used as the start address to search for a free
   *                        virtual memory region and the resulting address is
   *                        returned here.
   *                        If #L4Re::Rm::F::In_area is given the value is used
   *                        as a selector for the area (see
   *                        #L4Re::Rm::reserve_area) to attach the data space
   *                        to.
   * \param          size   Size of the data space to attach (in bytes). Will be
   *                        rounded up to the nearest multiple of the page size.
   * \param          flags  The flags control how and with which rights the
   *                        dataspace is attached to the region. See
   *                        #L4Re::Rm::F::Attach_flags and
   *                        #L4Re::Rm::F::Region_flags. The caller must specify
   *                        the desired rights of the attached region
   *                        explicitly. The default set of rights is empty. If
   *                        the `F::Eager_map` flag is set this function may
   *                        also return L4Re::Dataspace::map error codes if the
   *                        mapping fails.
   * \param          mem    Data space.
   * \param          offs   Offset into the data space to use.
   * \param          align  Alignment of the virtual region, log2-size, default:
   *                        a page (#L4_PAGESHIFT). This is only meaningful if
   *                        the #L4Re::Rm::F::Search_addr flag is used.
   *
   * \retval 0                  Success
   * \retval -L4_ENOENT         No area could be found (see
   *                            #L4Re::Rm::F::In_area)
   * \retval -L4_EPERM          Operation not allowed.
   * \retval -L4_EINVAL
   * \retval -L4_EADDRNOTAVAIL  The given address is not available.
   * \retval <0                 IPC errors
   *
   * Makes the whole or parts of a data space visible in the virtual memory
   * of the corresponding task. The corresponding region in the virtual
   * address space is backed with the contents of the dataspace.
   *
   * \note When searching for a free place in the virtual address space,
   * the space between \a start and the end of the virtual address space is
   * searched.
   *
   * \note There is no region object created, instead the region is
   *       defined by a virtual address within this range (see #L4Re::Rm::find).
   */
  long attach(l4_addr_t *start, unsigned long size, Flags flags,
              L4::Ipc::Cap<Dataspace> mem, Offset offs = 0,
              unsigned char align = L4_PAGESHIFT) const noexcept;

  /**
   * \copydoc L4Re::Rm::attach
   */
  template< typename T >
  long attach(T **start, unsigned long size, Flags flags,
              L4::Ipc::Cap<Dataspace> mem, Offset offs = 0,
              unsigned char align = L4_PAGESHIFT) const noexcept
  {
    union X { l4_addr_t a; T* t; };
    X *x = reinterpret_cast<X*>(start);
    return attach(&x->a, size, flags, mem, offs, align);
  }

#if __cplusplus >= 201103L
  template< typename T >
  class Unique_region
  {
  private:
    T _addr;
    L4::Cap<Rm> _rm;

  public:
    Unique_region(Unique_region const &) = delete;
    Unique_region &operator = (Unique_region const &) = delete;

    Unique_region() noexcept
    : _addr(0), _rm(L4::Cap<Rm>::Invalid) {}

    explicit Unique_region(T addr) noexcept
    : _addr(addr), _rm(L4::Cap<Rm>::Invalid) {}

    Unique_region(T addr, L4::Cap<Rm> const &rm) noexcept
    : _addr(addr), _rm(rm) {}

    Unique_region(Unique_region &&o) noexcept : _addr(o.get()), _rm(o._rm)
    { o.release(); }

    Unique_region &operator = (Unique_region &&o) noexcept
    {
      if (&o != this)
        {
          if (_rm.is_valid())
            _rm->detach(l4_addr_t(_addr), 0);
          _rm = o._rm;
          _addr = o.release();
        }
      return *this;
    }

    ~Unique_region() noexcept
    {
      if (_rm.is_valid())
        _rm->detach(l4_addr_t(_addr), 0);
    }

    T get() const noexcept
    { return _addr; }

    T release() noexcept
    {
      _rm = L4::Cap<Rm>::Invalid;
      return _addr;
    }

    void reset(T addr, L4::Cap<Rm> const &rm) noexcept
    {
      if (_rm.is_valid())
        _rm->detach(l4_addr_t(_addr), 0);

      _rm = rm;
      _addr = addr;
    }

    void reset() noexcept
    { reset(0, L4::Cap<Rm>::Invalid); }

    bool is_valid() const noexcept
    { return _rm.is_valid(); }

    /** \brief Dereference the pointer. */
    T operator * () const noexcept { return _addr; }

    /** \brief Member access for the object. */
    T operator -> () const noexcept { return _addr; }
  };

  template< typename T >
  long attach(Unique_region<T> *start, unsigned long size, Flags flags,
              L4::Ipc::Cap<Dataspace> mem, Offset offs = 0,
              unsigned char align = L4_PAGESHIFT) const noexcept
  {
    l4_addr_t addr = (l4_addr_t)start->get();

    long res = attach(&addr, size, flags, mem, offs, align);
    if (res < 0)
      return res;

    start->reset((T)addr, L4::Cap<Rm>(cap()));
    return res;
  }
#endif

  /**
   * \brief Detach a region from the address space.
   *
   * \param      addr  Virtual address of region, any address within the
   *                   region is valid.
   * \param[out] mem   Dataspace that is affected. Give 0 if not interested.
   * \param      task  This argument specifies the task where the pages are
   *                   unmapped. Provide L4::Cap<L4::Task>::Invalid for none.
   *                   The default is the current task.
   *
   * \retval #Detach_result  On success.
   * \retval -L4_ENOENT      No region found.
   * \retval <0              IPC errors
   *
   * Frees a region in the virtual address space given by addr (address type).
   * The corresponding part of the address space is now available again.
   */
  int detach(l4_addr_t addr, L4::Cap<Dataspace> *mem,
             L4::Cap<L4::Task> const &task = This_task) const noexcept;

  /**
   * \copydoc L4Re::Rm::detach
   */
  int detach(void *addr, L4::Cap<Dataspace> *mem,
             L4::Cap<L4::Task> const &task = This_task) const noexcept;

  /**
   * \brief Detach all regions of the specified interval.
   *
   * \param      start  Start of area to detach, must be within region.
   * \param      size   Size of of area to detach (in bytes).
   * \param[out] mem    Dataspace that is affected. Give 0 if not interested.
   * \param      task   This argument specifies the task where the pages are
   *                    unmapped. Provide L4::Cap<L4::Task>::Invalid for none.
   *                    The default is the current task.
   *
   * \retval #Detach_result  On success.
   * \retval -L4_ENOENT      No region found.
   * \retval <0              IPC errors
   *
   * Frees all regions within the interval given by start and size. If a
   * region overlaps the start or the end of the interval this region is only
   * detached partly. If the interval is within one region the original region
   * is split up into two separate regions.
   */
  int detach(l4_addr_t start, unsigned long size, L4::Cap<Dataspace> *mem,
             L4::Cap<L4::Task> const &task) const noexcept;

  /**
   * \brief Find a region given an address and size.
   *
   * \param[in,out] addr  Address to look for. Returns the start address of the
   *                      found region.
   * \param[in,out] size  Size of the area to look for (in bytes). Returns the
   *                      size of the found region (in bytes).
   * \param[out] offset  Offset at the beginning of the region within the
   *                     associated dataspace.
   * \param[out] flags   Region flags, see F::Region_flags (and F::In_area).
   * \param[out] m       Associated dataspace or paging service.
   *
   * \retval 0           Success
   * \retval -L4_EPERM   Operation not allowed.
   * \retval -L4_ENOENT  No region found.
   * \retval <0          IPC errors
   *
   * This function returns the properties of the region that contains the area
   * described by the addr and size parameter. If no such region is found but
   * a reserved area, the area is returned and F::In_area is set in `flags`.
   * Note, in the case of an area the `offset` and `m` return values are
   * invalid.
   *
   * \verbatim
                     size-out
                   /           \
                  /             \
              addr-out           \
                 ^________________\
     ------------|----------------|------------------
     |           | Region         |       Dataspace |
     |           |_______|___|____|                 |
     ------------|-------|---|-----------------------
      \         /        |\ /
       \       /         | |> size-in
       offset-out        |
                         |> addr-in
     \endverbatim
   *
   *
   * \note The value of the size input parameter should be 1 to assure that a
   *       region can be determined unambiguously.
   *
   */
  int find(l4_addr_t *addr, unsigned long *size, Offset *offset,
           L4Re::Rm::Flags *flags, L4::Cap<Dataspace> *m) noexcept
  { return find_t::call(c(), addr, size, flags, offset, m); }

  L4_RPC_NF(int, find, (L4::Ipc::In_out<l4_addr_t *> addr,
                        L4::Ipc::In_out<unsigned long *> size,
                        L4Re::Rm::Flags *flags, Offset *offset,
                        L4::Ipc::As_value<L4::Cap<Dataspace> > *m));

  /**
   * A range of virtual addresses.
   */
  struct Range
  {
    /// First address of the range.
    l4_addr_t start;
    /// Last address of the range.
    l4_addr_t end;
  };

  /**
   * A region is a range of virtual addresses which is backed by a dataspace.
   *
   * \see api_l4re_rm
   */
  using Region = Range;

  /**
   * An area is a range of virtual addresses which is reserved,
   * see L4Re::Rm::reserve_area().
   *
   * \see api_l4re_rm
   */
  using Area = Range;

  /**
   * Return the list of regions whose starting addresses are higher or equal to
   * `start` in the address space managed by this region map.
   *
   * \param       start   Virtual address from where to start searching.
   * \param[out]  regions List of regions found in this region map.
   *
   * \retval >=0  Number of returned regions in the `regions` array.
   * \retval <0   IPC errors
   *
   * \note The returned list of regions might not be complete and the caller
   *       shall use the function repeatedly with a start address one larger
   *       that the end address of the last region from the previous call.
   */
  L4_RPC(long, get_regions, (l4_addr_t start, L4::Ipc::Ret_array<Range> regions));

  /**
   * Return the list of areas whose starting addresses are higher or equal to
   * `start` in the address space managed by this region map.
   *
   * \param       start Virtual address from where to start searching.
   * \param[out]  areas List of areas found in this region map.
   *
   * \retval >=0  Number of returned areas in the `areas` array.
   * \retval <0   IPC errors
   *
   * \note The returned list of areas might not be complete and the caller
   *       shall use the function repeatedly with a start address one larger
   *       than the end address of the last area from the previous call.
   */

  L4_RPC(long, get_areas, (l4_addr_t start, L4::Ipc::Ret_array<Range> areas));

  int detach(l4_addr_t start, unsigned long size, L4::Cap<Dataspace> *mem,
             L4::Cap<L4::Task> task, unsigned flags) const noexcept;

  typedef L4::Typeid::Rpcs<attach_t, detach_t, find_t,
                           reserve_area_t, free_area_t,
                           get_regions_t, get_areas_t> Rpcs;
};


inline int
Rm::detach(l4_addr_t addr, L4::Cap<Dataspace> *mem,
           L4::Cap<L4::Task> const &task) const noexcept
{  return detach(addr, 1, mem, task, Detach_overlap); }

inline int
Rm::detach(void *addr, L4::Cap<Dataspace> *mem,
           L4::Cap<L4::Task> const &task) const noexcept
{  return detach((l4_addr_t)addr, 1, mem, task, Detach_overlap); }

inline int
Rm::detach(l4_addr_t addr, unsigned long size, L4::Cap<Dataspace> *mem,
           L4::Cap<L4::Task> const &task) const noexcept
{  return detach(addr, size, mem, task, Detach_exact); }

};