// 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)
 *
 * 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 <cstring>
#include <cstddef>
#include <l4/sys/types.h>
#include <l4/cxx/list>
#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 continuously
 * mapped dataspace.
 */
class Dataspace_svr
{
private:
  typedef L4::Ipc::Gen_fpage<L4::Ipc::Snd_item> Snd_fpage;
public:
  L4_RPC_LEGACY_DISPATCH(L4Re::Dataspace);

  typedef Snd_fpage::Map_type Map_type;
  typedef Snd_fpage::Cacheopt Cache_type;
  enum Rw_type {
      Read_only = 0,
      Writable = 1,
  };


  Dataspace_svr() throw()
  : _ds_start(0), _ds_size(0), _map_flags(Snd_fpage::Map),
    _cache_flags(Snd_fpage::Cached)
  {}

  virtual ~Dataspace_svr() throw() {}

  /**
   * Map a region of the dataspace
   *
   * \param      offset      Offset to start within data space
   * \param      local_addr  Local address to map to.
   * \param      flags       Map flags, see #L4Re::Dataspace::Map_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(l4_addr_t offset, l4_addr_t local_addr, unsigned long flags,
          l4_addr_t min_addr, l4_addr_t max_addr, L4::Ipc::Snd_fpage &memory);

  /**
   * A hook that is called as the first operation in each map
   *        request.
   * \param offs  Offs param to map
   * \param flags Flags param to map
   * \param min   Min param to map
   * \param max   Max param to map
   * \retval <0   Error and the map request will be aborted with that error.
   * \retval >=0  Success
   *
   * \see map
   */
  virtual int map_hook(l4_addr_t offs, unsigned long flags,
                       l4_addr_t min, l4_addr_t max)
  {
    (void)offs; (void)flags; (void)min; (void)max;
    return 0;
  }

  /**
   * Return physical address for a virtual address
   *
   * \param      offset     Offset into the dataspace
   * \param[out] phys_addr  Physical address
   * \param[out] phys_size  Size of continious physical region
   *
   * \retval 0   Success
   * \retval <0  Error
   */
  virtual int phys(l4_addr_t offset, l4_addr_t &phys_addr, l4_size_t &phys_size) throw();

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

  /**
   * Release a reference to this dataspace
   *
   * \return Number of references to the dataspace
   *
   * Default does nothing and returns always zero.
   */
  virtual unsigned long release() throw()
  { 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(l4_addr_t dst_offs, l4_umword_t src_id,
                    l4_addr_t src_offs, unsigned long size) throw()
  {
    (void)dst_offs; (void)src_id; (void)src_offs; (void)size;
    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 throw();

  /**
   * 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(l4_addr_t offset, l4_size_t size, unsigned access) throw()
  { (void)offset; (void)size; (void)access; return -L4_ENODEV; }

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

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


  long op_map(L4Re::Dataspace::Rights rights,
              unsigned long offset, l4_addr_t spot,
              unsigned long flags, L4::Ipc::Snd_fpage &fp)
  {
    bool read_only = !is_writable() || !(rights & L4_CAP_FPAGE_W);

    if (read_only && (flags & 1))
      return -L4_EPERM;

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

  long op_take(L4Re::Dataspace::Rights)
  { take(); return 0; }

  long op_release(L4Re::Dataspace::Rights)
  {

    if (release() == 0 && !is_static())
      {
        //L4::cout << "MOE: R[" << this << "]: refs=" << ref_cnt() << '\n';
        delete this;
        return 0;
      }
    //L4::cout << "MOE: R[" << this << "]: refs=" << ref_cnt() << '\n';

    return 1;
  }

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

  long op_copy_in(L4Re::Dataspace::Rights rights,
                  l4_addr_t dst_offs, L4::Ipc::Snd_fpage const &src_cap,
                  l4_addr_t src_offs, unsigned long 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_phys(L4Re::Dataspace::Rights, l4_addr_t offset,
               l4_addr_t &phys_addr, l4_size_t &phys_size)
  { return phys(offset, phys_addr, phys_size); }

  long op_info(L4Re::Dataspace::Rights rights, L4Re::Dataspace::Stats &s)
  {
    s.size = size();
    // only return writable if really writable
    s.flags = rw_flags() & ~Writable;
    if ((rights & L4_CAP_FPAGE_W) && is_writable())
      s.flags |= Writable;
    return L4_EOK;
  }

  long op_clear(L4Re::Dataspace::Rights rights,
                l4_addr_t offset, unsigned long size)
  {
    if (   !(rights & L4_CAP_FPAGE_W)
        || !is_writable())
      return -L4_EACCESS;

    return clear(offset, size);
  }


protected:
  unsigned long size() const throw()
  { return _ds_size; }
  unsigned long map_flags() const throw()
  { return _map_flags; }
  unsigned long rw_flags() const throw()
  { return _rw_flags; }
  unsigned long is_writable() const throw()
  { return _rw_flags & Writable; }
  unsigned long page_size() const throw()
  { return 1UL << page_shift(); }
  unsigned long round_size() const throw()
  { return l4_round_size(size(), page_shift()); }
  bool check_limit(l4_addr_t offset) const throw()
  { return offset < round_size(); }

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

  l4_addr_t _ds_start;
  l4_size_t _ds_size;
  Map_type _map_flags;
  Cache_type _cache_flags;
  Rw_type  _rw_flags;
};

}}
