// vi:set ft=cpp: -*- Mode: C++ -*-
/**
 * \file
 * C++ Irq interface
 */
/*
 * (c) 2008-2009 Adam Lackorzynski <adam@os.inf.tu-dresden.de>,
 *               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/icu.h>
#include <l4/sys/irq.h>
#include <l4/sys/capability>
#include <l4/sys/rcv_endpoint>
#include <l4/sys/cxx/ipc_iface>
#include <l4/sys/cxx/ipc_types>

namespace L4 {

/**
 * Interface for sending an unmask message to an object.
 *
 * The object is usually an ICU or an IRQ.
 *
 * When the kernel receives an IRQ, it masks the interrupt line at the interrupt
 * controller and immediately acknowledges the interrupt. This interface is used
 * to let the kernel know that userspace has dealt with the IRQ. The kernel will
 * unmask the interrupt line and further IRQs can then be delivered.
 *
 * \see L4::Icu, L4::Irq
 */
class Irq_eoi : public Kobject_0t<Irq_eoi, L4::PROTO_EMPTY>
{
public:
  /**
   * Unmask the given interrupt line. When the object is an IRQ, the given
   * interrupt line is ignored and instead the line which the IRQ is bound to
   * (if any) is unmasked.
   *
   * Its counterpart for explicitly masking an interrupt line is
   * L4::Icu::mask().
   *
   * \param      irqnum  The interrupt line that shall be unmasked. Ignored if
   *                     the object is an IRQ.
   * \param[out] label   If NULL, this is a send-only unmask. If not NULL, this
   *                     operation enters an open wait and the *protected
   *                     label* shall be received here.
   * \param      to      The timeout-pair (send and receive) that shall be used
   *                     for this operation. The receive timeout is used with a
   *                     non-NULL `label` only.
   * \utcb{utcb}
   *
   * \return Syscall return tag. If `label` is NULL, this function performs an
   *         IPC send-only operation and there is no return value except
   *         #L4_MSGTAG_ERROR indicating success or failure of the send
   *         operation. In this case use l4_ipc_error() to check for errors
   *         and **do not** use l4_error().
   */
  l4_msgtag_t unmask(unsigned irqnum, l4_umword_t *label = 0,
                     l4_timeout_t to = L4_IPC_NEVER,
                     l4_utcb_t *utcb = l4_utcb()) noexcept
  {
    return l4_icu_control_u(cap(), irqnum, L4_ICU_CTL_UNMASK, label, to, utcb);
  }
};

/**
 * Interface that allows an object to be triggered by some source. The interface
 * specifies no semantics for the trigger operation, this is defined by derived
 * objects.
 *
 * This interface is usually used in conjunction with L4::Icu.
 */
struct Triggerable : Kobject_t<Triggerable, Irq_eoi, L4_PROTO_IRQ>
{
  /**
   * Trigger the object.
   *
   * \utcb{utcb}
   *
   * \return Syscall return tag for a send-only operation, this means there
   *         is no return value except #L4_MSGTAG_ERROR indicating success or
   *         failure of the send operation. Use l4_ipc_error() to check for
   *         errors and **do not** use l4_error().
   */
  l4_msgtag_t trigger(l4_utcb_t *utcb = l4_utcb()) noexcept
  { return l4_irq_trigger_u(cap(), utcb); }
};

/**
 * C++ Irq interface, see \ref l4_irq_api for the C interface.
 *
 * \note "IRQ" is short for "interrupt request". This is often used
 * interchangeably for "interrupt"
 *
 * The Irq class provides access to abstract interrupts provided by the
 * microkernel. Interrupts may be
 * - hardware interrupts provided by the platform interrupt controller,
 * - virtual device interrupts provided by the microkernel's virtual devices
 *   (virtual serial or trace buffer) or
 * - virtual interrupts that can be triggered by user programs (IRQs) via the
 *   inherited method L4::Triggerable::trigger().
 *
 * For hardware and virtual device interrupts the Irq object must be bound to
 * an interrupt source, see L4::Icu. To receive interrupts, the Irq object must
 * be bound to a thread, see L4::Rcv_endpoint.
 *
 * Irq objects can be created using a factory, see the L4::Factory API
 * (L4::Factory::create()).
 *
 * \includefile{l4/sys/irq}
 *
 * For the C interface refer to the \ref l4_irq_api API for an overview.
 */
class Irq : public Kobject_2t<Irq, Triggerable, Rcv_endpoint, L4_PROTO_IRQ_SENDER>
{
public:
  using Triggerable::unmask;

  /**
   * Bind a thread to this Irq for vCPU interrupt forwarding.
   *
   * If the interrupt is triggered, the kernel will directly inject the
   * interrupt into the guest. This requires that the thread is currently in
   * extended vCPU user mode. Otherwise the interrupt will stay pending and
   * gets injected on the next vCPU user mode transition. Optionally a doorbell
   * Irq can be registered on the thread (see Thread::register_doorbell_irq())
   * that is triggered in this case.
   *
   * If a guest has acknowledged the interrupt but has not yet issued an EOI
   * (i.e. the interrupt is in "active" state), it is not possible to bind the
   * Irq to a new thread object. Either wait for the guest to issue the EOI or
   * detach() from the current thread. In this case the interrupt will stay
   * active in the guest and it is the responsibility of the VMM to handle the
   * eventual EOI of the guest.
   *
   * \param thread  Thread object this Irq shall be bound to.
   * \param cfg     Architecture specific interrupt configuration.
   * \utcb{utcb}
   *
   * \return Syscall return tag
   *
   * \retval -L4_EPERM   Insufficient permissions; see precondition.
   * \retval -L4_EBUSY   Cannot bind to the new thread because interrupt
   *                     is active on previous thread and guest has to issue
   *                     end-of-interrupt first.
   * \retval -L4_ENOSYS  The kernel does not support direct interrupt
   *                     forwarding.
   *
   * \pre The invoked Irq capability and the capability `thread` both must have
   *      the permission #L4_CAP_FPAGE_S.
   */
  l4_msgtag_t bind_vcpu(L4::Cap<Thread> const &thread, l4_umword_t cfg,
                        l4_utcb_t *utcb = l4_utcb()) noexcept
  { return l4_irq_bind_vcpu_u(cap(), thread.cap(), cfg, utcb); }

  /**
   * Detach from this interrupt.
   *
   * \utcb{utcb}
   *
   * \return Syscall return tag
   *
   * \retval 0           Successfully detached, there was no interrupt pending.
   * \retval 1           Successfully detached, there was an interrupt pending.
   * \retval 2           Successfully detached, an active vIRQ was abandoned.
   * \retval -L4_EPERM   Insufficient permissions; see precondition.
   *
   * \pre The invoked Irq capability must have the permission #L4_CAP_FPAGE_S.
   */
  l4_msgtag_t detach(l4_utcb_t *utcb = l4_utcb()) noexcept
  { return l4_irq_detach_u(cap(), utcb); }


  /**
   * Unmask and wait for this IRQ.
   *
   * \param timeout    Timeout.
   * \utcb{utcb}
   *
   * \return Syscall return tag
   *
   * \note If this is the function normally used for your IRQs consider using
   *       L4::Semaphore instead of L4::Irq.
   */
  l4_msgtag_t receive(l4_timeout_t timeout = L4_IPC_NEVER,
                      l4_utcb_t *utcb = l4_utcb()) noexcept
  { return l4_irq_receive_u(cap(), timeout, utcb); }

  /**
   * Unmask IRQ and (open) wait for any message.
   *
   * \param label    The *protected label* shall be received here.
   * \param timeout  Timeout.
   * \utcb{utcb}
   *
   * \return Syscall return tag
   */
  l4_msgtag_t wait(l4_umword_t *label, l4_timeout_t timeout = L4_IPC_NEVER,
                   l4_utcb_t *utcb = l4_utcb()) noexcept
  { return unmask(-1, label, timeout, utcb); }

  /**
   * Unmask this IRQ.
   *
   * \utcb{utcb}
   *
   * \return Syscall return tag for a send-only operation, this means there
   *         is no return value except #L4_MSGTAG_ERROR indicating success or
   *         failure of the send operation. Use l4_ipc_error() to check for
   *         errors and **do not** use l4_error().
   *
   * Irq::wait() and Irq::receive() operations already include an unmask(), do
   * not use an extra unmask() in these cases.
   */
  l4_msgtag_t unmask(l4_utcb_t *utcb = l4_utcb()) noexcept
  { return unmask(-1, 0, L4_IPC_NEVER, utcb); }
};

/**
 * C++ Icu interface, see \ref l4_icu_api for the C interface.
 *
 * \note "ICU" is short for "interrupt control unit".
 *
 * This class defines the interface for interrupt controllers. It defines
 * functions for binding L4::Irq objects to interrupt lines and other interrupt
 * sources, as well as functions for masking and unmasking of interrupts.
 *
 * To setup an interrupt line the following steps are required:
 * 1. set_mode() (optional if interrupt has a default mode)
 * 2. L4::Rcv_endpoint::bind_thread() or
 *    L4::Rcv_endpoint::bind_snd_destination() to attach the L4::Irq object
 *    to a send destination.
 * 3. bind()
 * 4. unmask() to receive the first interrupt
 *
 * For certain interrupt sources only some of these steps are necessary and
 * supported, see L4::Scheduler and L4::Vcon.
 *
 * At most one L4::Irq object can be bound to a certain interrupt source and a
 * certain L4::Irq object can be bound to at most one interrupt source.
 *
 * \includefile{l4/sys/icu}
 */
class Icu :
  public Kobject_t<Icu, Irq_eoi, L4_PROTO_IRQ,
                   Type_info::Demand_t<1> >
{
public:
  enum Mode
  {
    F_none         = L4_IRQ_F_NONE,
    F_level_high   = L4_IRQ_F_LEVEL_HIGH,
    F_level_low    = L4_IRQ_F_LEVEL_LOW,
    F_pos_edge     = L4_IRQ_F_POS_EDGE,
    F_neg_edge     = L4_IRQ_F_NEG_EDGE,
    F_both_edge    = L4_IRQ_F_BOTH_EDGE,
    F_mask         = L4_IRQ_F_MASK,

    F_set_wakeup   = L4_IRQ_F_SET_WAKEUP,
    F_clear_wakeup = L4_IRQ_F_CLEAR_WAKEUP,
  };

  enum Flags
  {
    F_msi = L4_ICU_FLAG_MSI
  };

  /**
   * This class encapsulates information about an ICU.
   */
  class Info : public l4_icu_info_t
  {
  public:
    /// True, if the ICU has support for MSIs.
    bool supports_msi() const noexcept { return features & F_msi; }
  };

  /**
   * Bind an interrupt line of an interrupt controller to an interrupt object.
   *
   * \param irqnum  IRQ line at the ICU.
   * \param irq     IRQ object for the given IRQ line to bind to this ICU.
   * \utcb{utcb}
   *
   * \return Syscall return tag. The caller should check the return value using
   *         l4_error() to check for errors and to identify the correct method
   *         for unmasking the interrupt.
   *         Return values `< 0` indicate an error. A return value of `0` means
   *         a direct unmask via the IRQ object using L4::Irq::unmask. A return
   *         value of `1` means that the interrupt has to be unmasked via the
   *         ICU using L4::Icu::unmask.
   *
   * \retval -L4_EINVAL  `irq` is bound to an interrupt source.
   * \retval -L4_EPERM   Insufficient permissions; see precondition.
   *
   * \pre The capability `irq` must have the permission #L4_CAP_FPAGE_W.
   *
   * In case the `irq` is already bound to an interrupt source, it is unbound
   * first. In case the `irq` is bound and the interrupt source is bound to a
   * different L4::Irq object, only the unbinding happens. An L4::Irq object
   * that is bound to an interrupt source will get unbound if the L4::Irq
   * object is deleted.
   */
  l4_msgtag_t bind(unsigned irqnum, L4::Cap<Triggerable> irq,
                   l4_utcb_t *utcb = l4_utcb()) noexcept
  { return l4_icu_bind_u(cap(), irqnum, irq.cap(), utcb); }

  L4_RPC_NF_OP(
    L4_ICU_OP_BIND,
    l4_msgtag_t, bind, (l4_umword_t irqnum, Ipc::Cap<Irq> irq)
  );

  /**
   * Remove binding of an interrupt line from the interrupt controller object.
   *
   * \param irqnum  IRQ line at the ICU.
   * \param irq     IRQ object to remove from the ICU.
   * \utcb{utcb}
   *
   * \return Syscall return tag
   */
  l4_msgtag_t unbind(unsigned irqnum, L4::Cap<Triggerable> irq,
                     l4_utcb_t *utcb = l4_utcb()) noexcept
  { return l4_icu_unbind_u(cap(), irqnum, irq.cap(), utcb); }

  L4_RPC_NF_OP(
    L4_ICU_OP_UNBIND,
    l4_msgtag_t, unbind, (l4_umword_t irqnum, Ipc::Cap<Irq> irq)
  );

  /**
   * Get information about the ICU features.
   *
   * \param[out] info  Info structure to be filled with information.
   * \utcb{utcb}
   *
   * \return Syscall return tag
   */
  l4_msgtag_t info(l4_icu_info_t *info, l4_utcb_t *utcb = l4_utcb()) noexcept
  { return l4_icu_info_u(cap(), info, utcb); }

  struct _Info { l4_umword_t features, nr_irqs, nr_msis; };
  L4_RPC_NF_OP(L4_ICU_OP_INFO, l4_msgtag_t, info, (_Info *info));

  /**
   * Get MSI info about IRQ.
   *
   * \param      irqnum    IRQ line at the ICU.
   * \param      source    Platform dependent requester ID for MSIs. On IA32 we
   *                       use a 20bit source filter value as described in the
   *                       Intel IRQ remapping specification.
   * \param[out] msi_info  A l4_icu_msi_info_t structure receiving the address
   *                       and the data value to trigger this MSI.
   *
   * \return Syscall return tag
   */
  L4_INLINE_RPC_OP(L4_ICU_OP_MSI_INFO,
      l4_msgtag_t, msi_info, (l4_umword_t irqnum, l4_uint64_t source,
                              l4_icu_msi_info_t *msi_info));

  /**
   * \internal
   */
  l4_msgtag_t control(unsigned irqnum, unsigned op, l4_umword_t *label,
                      l4_timeout_t to, l4_utcb_t *utcb = l4_utcb()) noexcept
  { return l4_icu_control_u(cap(), irqnum, op, label, to, utcb); }

  /**
   * Mask an IRQ line.
   *
   * \param irqnum  IRQ line at the ICU.
   * \param label   If NULL, this function is a send-only message to the ICU.
   *                If not NULL, this function will enter an open wait after
   *                sending the mask message and the received label is returned
   *                here.
   * \param to      The timeout-pair (send and receive) that shall be used for
   *                this operation. The receive timeout is used with a non-NULL
   *                `label` only.
   * \utcb{utcb}
   *
   * \return Syscall return tag. If `label` is NULL, this function performs an
   *         IPC send-only operation and there is no return value except
   *         #L4_MSGTAG_ERROR indicating success or failure of the send
   *         operation. In this case use l4_ipc_error() to check for errors
   *         and **do not** use l4_error().
   */
  l4_msgtag_t mask(unsigned irqnum,
                   l4_umword_t *label = 0,
                   l4_timeout_t to = L4_IPC_NEVER,
                   l4_utcb_t *utcb = l4_utcb()) noexcept
  { return l4_icu_mask_u(cap(), irqnum, label, to, utcb); }

  L4_RPC_NF_OP(
    L4_ICU_OP_MASK,
    l4_msgtag_t, mask, (l4_umword_t irqnum),
    L4::Ipc::Send_only
  );


  L4_RPC_NF_OP(
    L4_ICU_OP_UNMASK,
    l4_msgtag_t, unmask, (l4_umword_t irqnum),
    L4::Ipc::Send_only
  );

  /**
   * Set interrupt mode.
   *
   * \param irqnum  IRQ line at the ICU.
   * \param mode    Mode, see #L4_irq_mode.
   * \utcb{utcb}
   *
   * \return Syscall return tag
   */
  l4_msgtag_t set_mode(unsigned irqnum, l4_umword_t mode,
                       l4_utcb_t *utcb = l4_utcb()) noexcept
  { return l4_icu_set_mode_u(cap(), irqnum, mode, utcb); }

  L4_RPC_NF_OP(
    L4_ICU_OP_SET_MODE,
    l4_msgtag_t, set_mode, (l4_umword_t irqnum, l4_umword_t mode)
  );

  typedef L4::Typeid::Rpcs_sys<
    bind_t, unbind_t, info_t, msi_info_t, unmask_t, mask_t, set_mode_t
  > Rpcs;
};

}
