/**
 * \file
 * Platform control object.
 */
/*
 * (c) 2014 Alexander Warg <alexander.warg@kernkonzept.com>
 *
 * License: see LICENSE.spdx (in this directory or the directories above)
 */

#pragma once

#include <l4/sys/types.h>
#include <l4/sys/utcb.h>

/**
 * \defgroup l4_platform_control_api Platform Control C API
 * \{
 * \ingroup  l4_kernel_object_api
 *
 * C interface for controlling platform-wide properties, see 
 * L4::Platform_control for the C++ interface.
 *
 * \includefile{l4/sys/platform_control.h}
 *
 *  The API allows a client to suspend, reboot or shutdown the system.
 *
 *  For the C++ interface refer to \ref L4::Platform_control
 */


/**
 * Enter suspend to RAM.
 *
 * \pre Must only be invoked on the boot CPU. Furthermore it must be ensured
 *      that the invoking thread is not migrated to a different CPU during the
 *      suspend.
 *
 * \param pfc     Capability selector for the platform-control object.
 * \param extras  Some extra platform-specific information needed to enter
 *                suspend to RAM. On x86 platforms and when using the
 *                Platform_control object provided by Fiasco, the value
 *                defines the sleep state. The sleep states are defined in the
 *                ACPI table. Other platforms as well as Io's Platform_control
 *                object don't make use of this value at the moment.
 *
 * \return Syscall return tag
 */
L4_INLINE l4_msgtag_t
l4_platform_ctl_system_suspend(l4_cap_idx_t pfc,
                               l4_umword_t extras) L4_NOTHROW;

/**
 * \internal
 */
L4_INLINE l4_msgtag_t
l4_platform_ctl_system_suspend_u(l4_cap_idx_t pfc,
                                 l4_umword_t extras,
                                 l4_utcb_t *utcb) L4_NOTHROW;


/**
 * Shutdown or reboot the system.
 *
 * \param pfc     Capability selector for the platform-control object.
 * \param reboot  Shutdown when 0, or reboot when 1.
 *
 * \return Syscall return tag
 */
L4_INLINE l4_msgtag_t
l4_platform_ctl_system_shutdown(l4_cap_idx_t pfc,
                                l4_umword_t reboot) L4_NOTHROW;

/**
 * \internal
 */
L4_INLINE l4_msgtag_t
l4_platform_ctl_system_shutdown_u(l4_cap_idx_t pfc,
                                  l4_umword_t reboot,
                                  l4_utcb_t *utcb) L4_NOTHROW;

/**
 * Allow a CPU to be shut down.
 *
 * \param pfc      Capability selector for the platform-control object.
 * \param phys_id  Physical CPU id of CPU (e.g. local APIC id) to enable.
 * \param enable   Allow shutdown when 1, disallow when 0.
 *
 * \return Syscall return tag
 */
L4_INLINE l4_msgtag_t
l4_platform_ctl_cpu_allow_shutdown(l4_cap_idx_t pfc,
                                   l4_umword_t phys_id,
                                   l4_umword_t enable) L4_NOTHROW;

/**
 * \internal
 */
L4_INLINE l4_msgtag_t
l4_platform_ctl_cpu_allow_shutdown_u(l4_cap_idx_t pfc,
                                     l4_umword_t phys_id,
                                     l4_umword_t enable,
                                     l4_utcb_t *utcb) L4_NOTHROW;
/**
 * Enable an offline CPU.
 *
 * \param pfc      Capability to the platform control object.
 * \param phys_id  Physical CPU id of CPU (e.g. local APIC id) to enable.
 *
 * \return System call message tag
 *
 * This function is currently only supported on the ARM EXYNOS platform.
 */
L4_INLINE l4_msgtag_t
l4_platform_ctl_cpu_enable(l4_cap_idx_t pfc,
                           l4_umword_t phys_id) L4_NOTHROW;

/**
 * \internal
 */
L4_INLINE l4_msgtag_t
l4_platform_ctl_cpu_enable_u(l4_cap_idx_t pfc,
                             l4_umword_t phys_id,
                             l4_utcb_t *utcb) L4_NOTHROW;

/**
 * Disable an online CPU.
 *
 * \param pfc      Capability to the platform control object.
 * \param phys_id  Physical CPU id of CPU (e.g. local APIC id) to disable.
 *
 * \return System call message tag
 *
 * This function is currently only supported on the ARM EXYNOS platform.
 */
L4_INLINE l4_msgtag_t
l4_platform_ctl_cpu_disable(l4_cap_idx_t pfc,
                            l4_umword_t phys_id) L4_NOTHROW;

/**
 * \internal
 */
L4_INLINE l4_msgtag_t
l4_platform_ctl_cpu_disable_u(l4_cap_idx_t pfc,
                              l4_umword_t phys_id,
                              l4_utcb_t *utcb) L4_NOTHROW;

/**\} */ /* ends l4_platform_control_api group */


/**
 * \ingroup l4_protocol_ops
 *
 * Operations on platform-control objects.
 *
 * See #L4_PROTO_PLATFORM_CTL for the protocol type to use for messages to
 * platform-control objects.
 */
enum L4_platform_ctl_ops
{
  L4_PLATFORM_CTL_SYS_SUSPEND_OP        = 0UL, /**< Suspend */
  L4_PLATFORM_CTL_SYS_SHUTDOWN_OP       = 1UL, /**< shutdown/reboot */
  L4_PLATFORM_CTL_CPU_ALLOW_SHUTDOWN_OP = 2UL, /**< allow CPU shutdown */
  L4_PLATFORM_CTL_CPU_ENABLE_OP         = 3UL, /**< enable an offline CPU */
  L4_PLATFORM_CTL_CPU_DISABLE_OP        = 4UL, /**< disable an online CPU */

  L4_PLATFORM_CTL_SET_TASK_ASID_OP      = 0x10UL, /**< Arm: set task ASID */
};

/**
 * Predefined protocol type for messages to platform-control objects.
 * \ingroup l4_msgtag_api
 */
enum L4_platform_ctl_proto
{
  /**
   * Protocol messages to a platform control object.
   *
   * See #L4_platform_ctl_ops for allowed operations.
   */
  L4_PROTO_PLATFORM_CTL = 0
};

/* IMPLEMENTATION -----------------------------------------------------------*/

#include <l4/sys/ipc.h>

L4_INLINE l4_msgtag_t
l4_platform_ctl_system_suspend_u(l4_cap_idx_t pfc,
                                 l4_umword_t extras,
                                 l4_utcb_t *utcb) L4_NOTHROW
{
  l4_msg_regs_t *v = l4_utcb_mr_u(utcb);
  v->mr[0] = L4_PLATFORM_CTL_SYS_SUSPEND_OP;
  v->mr[1] = extras;
  return l4_ipc_call(pfc, utcb, l4_msgtag(L4_PROTO_PLATFORM_CTL, 2, 0, 0),
                                          L4_IPC_NEVER);
}

L4_INLINE l4_msgtag_t
l4_platform_ctl_system_shutdown_u(l4_cap_idx_t pfc,
                                  l4_umword_t reboot,
                                  l4_utcb_t *utcb) L4_NOTHROW
{
  l4_msg_regs_t *v = l4_utcb_mr_u(utcb);
  v->mr[0] = L4_PLATFORM_CTL_SYS_SHUTDOWN_OP;
  v->mr[1] = reboot;
  return l4_ipc_call(pfc, utcb, l4_msgtag(L4_PROTO_PLATFORM_CTL, 2, 0, 0),
                                          L4_IPC_NEVER);
}


L4_INLINE l4_msgtag_t
l4_platform_ctl_system_suspend(l4_cap_idx_t pfc,
                               l4_umword_t extras) L4_NOTHROW
{
  return l4_platform_ctl_system_suspend_u(pfc, extras, l4_utcb());
}

L4_INLINE l4_msgtag_t
l4_platform_ctl_system_shutdown(l4_cap_idx_t pfc,
                                l4_umword_t reboot) L4_NOTHROW
{
  return l4_platform_ctl_system_shutdown_u(pfc, reboot, l4_utcb());
}

L4_INLINE l4_msgtag_t
l4_platform_ctl_cpu_allow_shutdown_u(l4_cap_idx_t pfc,
                                     l4_umword_t phys_id,
                                     l4_umword_t enable,
                                     l4_utcb_t *utcb) L4_NOTHROW
{
  l4_msg_regs_t *v = l4_utcb_mr_u(utcb);
  v->mr[0] = L4_PLATFORM_CTL_CPU_ALLOW_SHUTDOWN_OP;
  v->mr[1] = phys_id;
  v->mr[2] = enable;
  return l4_ipc_call(pfc, utcb, l4_msgtag(L4_PROTO_PLATFORM_CTL, 3, 0, 0),
                                          L4_IPC_NEVER);
}

L4_INLINE l4_msgtag_t
l4_platform_ctl_cpu_allow_shutdown(l4_cap_idx_t pfc,
                                   l4_umword_t phys_id,
                                   l4_umword_t enable) L4_NOTHROW
{
  return l4_platform_ctl_cpu_allow_shutdown_u(pfc, phys_id, enable, l4_utcb());
}

L4_INLINE l4_msgtag_t
l4_platform_ctl_cpu_enable_u(l4_cap_idx_t pfc,
                             l4_umword_t phys_id,
                             l4_utcb_t *utcb) L4_NOTHROW
{
  l4_msg_regs_t *v = l4_utcb_mr_u(utcb);
  v->mr[0] = L4_PLATFORM_CTL_CPU_ENABLE_OP;
  v->mr[1] = phys_id;
  return l4_ipc_call(pfc, utcb, l4_msgtag(L4_PROTO_PLATFORM_CTL, 2, 0, 0),
                                          L4_IPC_NEVER);
}

L4_INLINE l4_msgtag_t
l4_platform_ctl_cpu_disable_u(l4_cap_idx_t pfc,
                              l4_umword_t phys_id,
                              l4_utcb_t *utcb) L4_NOTHROW
{
  l4_msg_regs_t *v = l4_utcb_mr_u(utcb);
  v->mr[0] = L4_PLATFORM_CTL_CPU_DISABLE_OP;
  v->mr[1] = phys_id;
  return l4_ipc_call(pfc, utcb, l4_msgtag(L4_PROTO_PLATFORM_CTL, 2, 0, 0),
                                          L4_IPC_NEVER);
}

L4_INLINE l4_msgtag_t
l4_platform_ctl_cpu_enable(l4_cap_idx_t pfc,
                           l4_umword_t phys_id) L4_NOTHROW
{
  return l4_platform_ctl_cpu_enable_u(pfc, phys_id, l4_utcb());
}

L4_INLINE l4_msgtag_t
l4_platform_ctl_cpu_disable(l4_cap_idx_t pfc,
                            l4_umword_t phys_id) L4_NOTHROW
{
  return l4_platform_ctl_cpu_disable_u(pfc, phys_id, l4_utcb());
}
