/**
 * \file
 * \brief Interrupt controller.
 * \ingroup l4_api
 */
/*
 * (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 <l4/sys/kernel_object.h>
#include <l4/sys/ipc.h>

/**
 * \defgroup l4_icu_api Interrupt controller
 * \ingroup  l4_kernel_object_api
 *
 * \brief The ICU class.
 *
 * <c>\#include <l4/sys/icu.h></c>
 */


/**
 * \brief Flags for IRQ numbers used for the ICU.
 * \ingroup l4_icu_api
 */
enum L4_icu_flags
{
  /**
   * \brief Flag to denote that the IRQ is actually an MSI.
   * \hideinitializer
   *
   * This flag may be used for l4_icu_bind() and l4_icu_unbind() functions to
   * denote that the IRQ number is meant to be an MSI.
   */
  L4_ICU_FLAG_MSI = 0x80000000,
};


/**
 * \brief Interrupt flow types.
 * \ingroup l4_irq_api
 */
enum L4_irq_flow_type
{
  L4_IRQ_F_NONE       = 0,     /**< None */
  L4_IRQ_F_LEVEL      = 0x2,   /**< Level triggered */
  L4_IRQ_F_EDGE       = 0x0,   /**< Edge triggered */
  L4_IRQ_F_POS        = 0x0,   /**< Positive trigger */
  L4_IRQ_F_NEG        = 0x4,   /**< Negative trigger */
  L4_IRQ_F_BOTH       = 0x8,   /**< Both edges trigger */
  L4_IRQ_F_LEVEL_HIGH = 0x3,   /**< Level high trigger */
  L4_IRQ_F_LEVEL_LOW  = 0x7,   /**< Level low trigger */
  L4_IRQ_F_POS_EDGE   = 0x1,   /**< Positive edge trigger */
  L4_IRQ_F_NEG_EDGE   = 0x5,   /**< Negative edge trigger */
  L4_IRQ_F_BOTH_EDGE  = 0x9,   /**< Both edges trigger */
  L4_IRQ_F_MASK       = 0xf,   /**< Mask */
};


/**
 * \brief Opcodes to the ICU interface.
 * \ingroup l4_api_icu
 */
enum L4_icu_opcode
{
  /**
   * \brief Bind opcode.
   * \hideinitializer
   * \see l4_icu_bind()
   */
  L4_ICU_OP_BIND = 0,

  /**
   * \brief Unbind opcode.
   * \hideinitializer
   * \see l4_icu_unbind()
   */
  L4_ICU_OP_UNBIND = 1,

  /**
   * \brief Info opcode.
   * \hideinitializer
   * \see l4_icu_info()
   */
  L4_ICU_OP_INFO = 2,

  /**
   * \brief Msi-info opcode.
   * \hideinitializer
   * \see l4_icu_msi_info()
   */
  L4_ICU_OP_MSI_INFO = 3,

  /**
   * \brief Unmask opcode.
   * \hideinitializer
   * \see l4_icu_unmask()
   */
  L4_ICU_OP_UNMASK   = 4,

  /**
   * \brief Mask opcode.
   * \hideinitializer
   * \see l4_icu_mask()
   */
  L4_ICU_OP_MASK     = 5,

  /**
   * \brief Set-mode opcode.
   * \hideinitializer
   * \see l4_icu_set_mode()
   */
  L4_ICU_OP_SET_MODE = 6,
};

enum L4_icu_ctl_op
{
  L4_ICU_CTL_UNMASK = 0,
  L4_ICU_CTL_MASK   = 1
};


/**
 * \brief Info structure for an ICU.
 * \ingroup l4_icu_api
 *
 * This structure contains information about the features of an ICU.
 * \see l4_icu_info().
 */
typedef struct l4_icu_info_t
{
  /**
   * \brief Feature flags.
   *
   * If #L4_ICU_FLAG_MSI is set the ICU supports MSIs.
   */
  unsigned features;

  /**
   * \brief The number of IRQ lines supported by the ICU,
   */
  unsigned nr_irqs;

  /**
   * \brief The number of MSI vectors supported by the ICU,
   */
  unsigned nr_msis;
} l4_icu_info_t;

/**
 * \brief Bind an interrupt vector of an interrupt controller to an interrupt object.
 * \ingroup l4_icu_api
 *
 * \param icu    ICU to use.
 * \param irqnum IRQ vector at the ICU.
 * \param irq    IRQ capability to bind the IRQ to.
 * \return Syscall return tag
 */
L4_INLINE l4_msgtag_t
l4_icu_bind(l4_cap_idx_t icu, unsigned irqnum, l4_cap_idx_t irq) L4_NOTHROW;

/**
 * \internal
 */
L4_INLINE l4_msgtag_t
l4_icu_bind_u(l4_cap_idx_t icu, unsigned irqnum, l4_cap_idx_t irq,
              l4_utcb_t *utcb) L4_NOTHROW;

/**
 * \brief Remove binding of an interrupt vector from the interrupt controller object.
 * \ingroup l4_icu_api
 *
 * \param icu    ICU to use.
 * \param irqnum IRQ vector at the ICU.
 * \param irq    IRQ object to remove from the ICU.
 * \return Syscall return tag
 */
L4_INLINE l4_msgtag_t
l4_icu_unbind(l4_cap_idx_t icu, unsigned irqnum, l4_cap_idx_t irq) L4_NOTHROW;

/**
 * \internal
 */
L4_INLINE l4_msgtag_t
l4_icu_unbind_u(l4_cap_idx_t icu, unsigned irqnum, l4_cap_idx_t irq,
                l4_utcb_t *utcb) L4_NOTHROW;

/**
 * \brief Set mode of interrupt.
 * \ingroup l4_icu_api
 *
 * \param  icu     ICU to use.
 * \param  irqnum  IRQ vector at the ICU.
 * \param  mode    Mode, see L4_irq_flow_type.
 * \return Syscall return tag
 *
 * \ingroup l4_icu_api
 */
L4_INLINE l4_msgtag_t
l4_icu_set_mode(l4_cap_idx_t icu, unsigned irqnum, l4_umword_t mode) L4_NOTHROW;

/**
 * \internal
 */
L4_INLINE l4_msgtag_t
l4_icu_set_mode_u(l4_cap_idx_t icu, unsigned irqnum, l4_umword_t mode,
                  l4_utcb_t *utcb) L4_NOTHROW;

/**
 * \brief Get info about capabilites of ICU.
 * \ingroup l4_icu_api
 *
 * \param icu    ICU to use.
 * \param info   Pointer to an info structure to be filled with information.
 * \return Syscall return tag
 */
L4_INLINE l4_msgtag_t
l4_icu_info(l4_cap_idx_t icu, l4_icu_info_t *info) L4_NOTHROW;

/**
 * \internal
 */
L4_INLINE l4_msgtag_t
l4_icu_info_u(l4_cap_idx_t icu, l4_icu_info_t *info,
              l4_utcb_t *utcb) L4_NOTHROW;

/**
 * \brief Get MSI info about IRQ.
 * \ingroup l4_icu_api
 *
 * \param icu    ICU to use.
 * \param irqnum IRQ vector at the ICU.
 * \param msg    Pointer to a word to receive the message that must be used
 *               for the PCI devices MSI message.
 * \return Syscall return tag
 */
L4_INLINE l4_msgtag_t
l4_icu_msi_info(l4_cap_idx_t icu, unsigned irqnum, l4_umword_t *msg) L4_NOTHROW;

/**
 * \internal
 */
L4_INLINE l4_msgtag_t
l4_icu_msi_info_u(l4_cap_idx_t icu, unsigned irqnum, l4_umword_t *msg,
                  l4_utcb_t *utcb) L4_NOTHROW;


/**
 * \brief Unmask an IRQ vector.
 * \ingroup l4_icu_api
 *
 * \param icu    ICU to use.
 * \param irqnum IRQ vector at the ICU.
 * \param label  If non-NULL the function also waits for the next message.
 * \param to     Timeout for message to ICU, if unsure use L4_IPC_NEVER.
 * \return Syscall return tag
 */
L4_INLINE l4_msgtag_t
l4_icu_unmask(l4_cap_idx_t icu, unsigned irqnum, l4_umword_t *label,
              l4_timeout_t to) L4_NOTHROW;

/**
 * \internal
 */
L4_INLINE l4_msgtag_t
l4_icu_unmask_u(l4_cap_idx_t icu, unsigned irqnum, l4_umword_t *label,
                l4_timeout_t to, l4_utcb_t *utcb) L4_NOTHROW;

/**
 * \brief Mask an IRQ vector.
 * \ingroup l4_icu_api
 *
 * \param icu    ICU to use.
 * \param irqnum IRQ vector at the ICU.
 * \param label  If non-NULL the function also waits for the next message.
 * \param to     Timeout for message to ICU, if unsure use L4_IPC_NEVER.
 * \return Syscall return tag
 */
L4_INLINE l4_msgtag_t
l4_icu_mask(l4_cap_idx_t icu, unsigned irqnum, l4_umword_t *label,
            l4_timeout_t to) L4_NOTHROW;

/**
 * \internal
 */
L4_INLINE l4_msgtag_t
l4_icu_mask_u(l4_cap_idx_t icu, unsigned irqnum, l4_umword_t *label,
              l4_timeout_t to, l4_utcb_t *utcb) L4_NOTHROW;

/**
 * \internal
 */
L4_INLINE l4_msgtag_t
l4_icu_control_u(l4_cap_idx_t icu, unsigned irqnum, unsigned op,
                 l4_umword_t *label,l4_timeout_t to,
                 l4_utcb_t *utcb) L4_NOTHROW;


/**************************************************************************
 * Implementations
 */

L4_INLINE l4_msgtag_t
l4_icu_bind_u(l4_cap_idx_t icu, unsigned irqnum, l4_cap_idx_t irq,
              l4_utcb_t *utcb) L4_NOTHROW
{
  l4_msg_regs_t *m = l4_utcb_mr_u(utcb);
  m->mr[0] = L4_ICU_OP_BIND;
  m->mr[1] = irqnum;
  m->mr[2] = l4_map_obj_control(0, 0);
  m->mr[3] = l4_obj_fpage(irq, 0, L4_FPAGE_RWX).raw;
  return l4_ipc_call(icu, utcb, l4_msgtag(L4_PROTO_IRQ, 2, 1, 0), L4_IPC_NEVER);
}

L4_INLINE l4_msgtag_t
l4_icu_unbind_u(l4_cap_idx_t icu, unsigned irqnum, l4_cap_idx_t irq,
                l4_utcb_t *utcb) L4_NOTHROW
{
  l4_msg_regs_t *m = l4_utcb_mr_u(utcb);
  m->mr[0] = L4_ICU_OP_UNBIND;
  m->mr[1] = irqnum;
  m->mr[2] = l4_map_obj_control(0, 0);
  m->mr[3] = l4_obj_fpage(irq, 0, L4_FPAGE_RWX).raw;
  return l4_ipc_call(icu, utcb, l4_msgtag(L4_PROTO_IRQ, 2, 1, 0), L4_IPC_NEVER);
}

L4_INLINE l4_msgtag_t
l4_icu_info_u(l4_cap_idx_t icu, l4_icu_info_t *info,
              l4_utcb_t *utcb) L4_NOTHROW
{
  l4_msgtag_t res;
  l4_msg_regs_t *m = l4_utcb_mr_u(utcb);
  m->mr[0] = L4_ICU_OP_INFO;
  res = l4_ipc_call(icu, utcb, l4_msgtag(L4_PROTO_IRQ, 1, 0, 0), L4_IPC_NEVER);
  if (!l4_msgtag_has_error(res) && l4_msgtag_label(res) >= 0
      && l4_msgtag_words(res) >= 3)
    {
      info->features = m->mr[0];
      info->nr_irqs  = m->mr[1];
      info->nr_msis  = m->mr[2];
    }
  return res;
}

L4_INLINE l4_msgtag_t
l4_icu_msi_info_u(l4_cap_idx_t icu, unsigned irqnum, l4_umword_t *msg,
                  l4_utcb_t *utcb) L4_NOTHROW
{
  l4_msgtag_t res;
  l4_msg_regs_t *m = l4_utcb_mr_u(utcb);
  m->mr[0] = L4_ICU_OP_MSI_INFO;
  m->mr[1] = irqnum;
  res = l4_ipc_call(icu, utcb, l4_msgtag(L4_PROTO_IRQ, 2, 0, 0), L4_IPC_NEVER);
  if (!l4_msgtag_has_error(res) && l4_msgtag_label(res) >= 0
      && l4_msgtag_words(res) >= 1)
    *msg = m->mr[0];
  return res;
}

L4_INLINE l4_msgtag_t
l4_icu_set_mode_u(l4_cap_idx_t icu, unsigned irqnum, l4_umword_t mode,
                  l4_utcb_t *utcb) L4_NOTHROW
{
  l4_msg_regs_t *mr = l4_utcb_mr_u(utcb);
  mr->mr[0] = L4_ICU_OP_SET_MODE;
  mr->mr[1] = irqnum;
  mr->mr[2] = mode;
  return l4_ipc_call(icu, utcb, l4_msgtag(L4_PROTO_IRQ, 3, 0, 0), L4_IPC_NEVER);
}

L4_INLINE l4_msgtag_t
l4_icu_control_u(l4_cap_idx_t icu, unsigned irqnum, unsigned op,
                 l4_umword_t *label, l4_timeout_t to,
                 l4_utcb_t *utcb) L4_NOTHROW
{
  l4_msg_regs_t *m = l4_utcb_mr_u(utcb);
  m->mr[0] = L4_ICU_OP_UNMASK + op;
  m->mr[1] = irqnum;
  if (label)
    return l4_ipc_send_and_wait(icu, utcb, l4_msgtag(L4_PROTO_IRQ, 2, 0, 0),
                                label, to);
  else
    return l4_ipc_send(icu, utcb, l4_msgtag(L4_PROTO_IRQ, 2, 0, 0), to);
}

L4_INLINE l4_msgtag_t
l4_icu_mask_u(l4_cap_idx_t icu, unsigned irqnum, l4_umword_t *label,
              l4_timeout_t to, l4_utcb_t *utcb) L4_NOTHROW
{ return l4_icu_control_u(icu, irqnum, L4_ICU_CTL_MASK, label, to, utcb); }

L4_INLINE l4_msgtag_t
l4_icu_unmask_u(l4_cap_idx_t icu, unsigned irqnum, l4_umword_t *label,
                l4_timeout_t to, l4_utcb_t *utcb) L4_NOTHROW
{ return l4_icu_control_u(icu, irqnum, L4_ICU_CTL_UNMASK, label, to, utcb); }




L4_INLINE l4_msgtag_t
l4_icu_bind(l4_cap_idx_t icu, unsigned irqnum, l4_cap_idx_t irq) L4_NOTHROW
{ return l4_icu_bind_u(icu, irqnum, irq, l4_utcb()); }

L4_INLINE l4_msgtag_t
l4_icu_unbind(l4_cap_idx_t icu, unsigned irqnum, l4_cap_idx_t irq) L4_NOTHROW
{ return l4_icu_unbind_u(icu, irqnum, irq, l4_utcb()); }

L4_INLINE l4_msgtag_t
l4_icu_info(l4_cap_idx_t icu, l4_icu_info_t *info) L4_NOTHROW
{ return l4_icu_info_u(icu, info, l4_utcb()); }

L4_INLINE l4_msgtag_t
l4_icu_msi_info(l4_cap_idx_t icu, unsigned irqnum, l4_umword_t *msg) L4_NOTHROW
{ return l4_icu_msi_info_u(icu, irqnum, msg, l4_utcb()); }

L4_INLINE l4_msgtag_t
l4_icu_unmask(l4_cap_idx_t icu, unsigned irqnum, l4_umword_t *label,
              l4_timeout_t to) L4_NOTHROW
{ return l4_icu_control_u(icu, irqnum, L4_ICU_CTL_UNMASK, label, to, l4_utcb()); }

L4_INLINE l4_msgtag_t
l4_icu_mask(l4_cap_idx_t icu, unsigned irqnum, l4_umword_t *label,
            l4_timeout_t to) L4_NOTHROW
{ return l4_icu_control_u(icu, irqnum, L4_ICU_CTL_MASK, label, to, l4_utcb()); }

L4_INLINE l4_msgtag_t
l4_icu_set_mode(l4_cap_idx_t icu, unsigned irqnum, l4_umword_t mode) L4_NOTHROW
{
  return l4_icu_set_mode_u(icu, irqnum, mode, l4_utcb());
}
