/**
 * \internal
 * \file
 * \brief X86 virtualization interface.
 */
/*
 * (c) 2010-2013 Adam Lackorzynski <adam@os.inf.tu-dresden.de>,
 *               Alexander Warg <warg@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/vcpu.h>

/**
 * \defgroup l4_vm_vmx_api VM API for VMX
 * \brief Virtual machine API for VMX.
 * \ingroup l4_vm_api
 */


/**
 * \brief Exported VMX capability registers.
 * \ingroup l4_vm_vmx_api
 */
enum L4_vm_vmx_caps_regs
{
  L4_VM_VMX_BASIC_REG               = 0,    /**< Basic VMX capabilities */
  L4_VM_VMX_TRUE_PINBASED_CTLS_REG  = 1,    /**< True pin-based control caps */
  L4_VM_VMX_TRUE_PROCBASED_CTLS_REG = 2,    /**< True processor based control caps */
  L4_VM_VMX_TRUE_EXIT_CTLS_REG      = 3,    /**< True exit control caps */
  L4_VM_VMX_TRUE_ENTRY_CTLS_REG     = 4,    /**< True entry control caps */
  L4_VM_VMX_MISC_REG                = 5,    /**< Misc caps */
  L4_VM_VMX_CR0_FIXED0_REG          = 6,    /**< Fixed to 0 bits of CR0 */
  L4_VM_VMX_CR0_FIXED1_REG          = 7,    /**< Fixed to 1 bits of CR0 */
  L4_VM_VMX_CR4_FIXED0_REG          = 8,    /**< Fixed to 0 bits of CR4 */
  L4_VM_VMX_CR4_FIXED1_REG          = 9,    /**< Fixed to 1 bits of CR4 */
  L4_VM_VMX_VMCS_ENUM_REG           = 0xa,  /**< VMCS enumeration info */
  L4_VM_VMX_PROCBASED_CTLS2_REG     = 0xb,  /**< Processor based control 2 caps */
  L4_VM_VMX_EPT_VPID_CAP_REG        = 0xc,  /**< EPT and VPID caps */
  L4_VM_VMX_NUM_CAPS_REGS                   /**< Total number of VMX capability registers */
};


/**
 * \brief Exported VMX capability registers (default to 1 bits).
 * \ingroup l4_vm_vmx_api
 */
enum L4_vm_vmx_dfl1_regs
{
  L4_VM_VMX_PINBASED_CTLS_DFL1_REG  = 0x1, /**< Default 1 bits in pin-based controls */
  L4_VM_VMX_PROCBASED_CTLS_DFL1_REG = 0x2, /**< Default 1 bits in processor-based controls */
  L4_VM_VMX_EXIT_CTLS_DFL1_REG      = 0x3, /**< Default 1 bits in exit controls */
  L4_VM_VMX_ENTRY_CTLS_DFL1_REG     = 0x4, /**< Default 1 bits in entry controls */
  L4_VM_VMX_NUM_DFL1_REGS                  /**< Total number of default on registers */
};

/**
 * \brief Get a capability register for VMX.
 * \ingroup l4_vm_vmx_api
 *
 * \param vcpu_state  Pointer to the VCPU state of the VCPU.
 * \param cap_msr     Caps register index (\see L4_vm_vmx_caps_regs).
 * \return The value of the capability register.
 */
L4_INLINE
l4_uint64_t
l4_vm_vmx_get_caps(void const *vcpu_state, unsigned cap_msr) L4_NOTHROW;

/**
 * \brief Get a default to one capability register for VMX.
 * \ingroup l4_vm_vmx_api
 *
 * \param vcpu_state  Pointer to the VCPU state of the VCPU.
 * \param cap_msr     Default 1 caps register index (\see L4_vm_vmx_dfl1_regs).
 * \return The value of the capability register.
 */
L4_INLINE
l4_uint32_t
l4_vm_vmx_get_caps_default1(void const *vcpu_state, unsigned cap_msr) L4_NOTHROW;


/**
 * \brief Additional VMCS fields.
 * \ingroup l4_vm_vmx_api
 */
enum
{
  /**
   * \brief (virtual) VMCS offset for CR2.
   *
   * The CR2 register is actually not in the hardware VMCS, however our VMMs
   * run in user mode and need to have access to this register so we put it
   * into our version of the VMCS.
   *
   * \note You usually need to check this value against the value you get from
   *       l4_vm_vmx_get_cr2_index() to make sure you are running on a
   *       compatible kernel.
   */
  L4_VM_VMX_VMCS_CR2              = 0x683e,
};

/**
 * \brief Return length in bytes of a VMCS field.
 * \ingroup l4_vm_vmx_api
 *
 * \param field  Field number.
 * \return Width of field in bytes.
 */
L4_INLINE
unsigned
l4_vm_vmx_field_len(unsigned field) L4_NOTHROW;

/**
 * \brief Return length in power of two (bytes) of a VMCS field.
 * \ingroup l4_vm_vmx_api
 *
 * \param field  Field number.
 * \return Width of field in power of two (bytes).
 */
L4_INLINE
unsigned
l4_vm_vmx_field_order(unsigned field) L4_NOTHROW;

/**
 * \brief Get pointer into VMCS.
 * \internal
 * \ingroup l4_vm_vmx_api
 *
 * \param vmcs   Pointer to VMCS buffer.
 * \param field  Field number.
 *
 * \param Pointer to field in the VMCS.
 */
L4_INLINE
void *
l4_vm_vmx_field_ptr(void *vmcs, unsigned field) L4_NOTHROW;

/**
 * \brief Saves cached state from the kernel VMCS to the user VMCS.
 * \ingroup l4_vm_vmx_api
 * \param vmcs Pointer to the kernel VMCS.
 * \param user_vmcs Pointer to the user VMCS.
 *
 * This function is comparable to VMX vmclear.
 */
L4_INLINE
void
l4_vm_vmx_clear(void *vmcs, void *user_vmcs) L4_NOTHROW;

/**
 * \brief Loades the user_vmcs as the current VMCS.
 * \ingroup l4_vm_vmx_api
 * \param vmcs Pointer to the kernel VMCS.
 * \param user_vmcs Pointer to the user VMCS.
 *
 * This function is comparable to VMX vmptrld.
 */
L4_INLINE
void
l4_vm_vmx_ptr_load(void *vmcs, void *user_vmcs) L4_NOTHROW;


/**
 * \brief Get the VMCS field index of the virtual CR2 register.
 * \ingroup l4_vm_vmx_api
 *
 * \param vmcs  Pointer to the software VMCS.
 * \return The field index used for the virtual CR2 register as used by the
 *         current Fiasco.OC interface.
 *
 * The CR2 register is actually not in the hardware VMCS, however our VMMs run
 * in user mode and need to have access to this register so we put it into our
 * software version of the VMCS.
 *
 * \see #L4_VM_VMX_VMCS_CR2
 */
L4_INLINE
l4_uint32_t
l4_vm_vmx_get_cr2_index(void const *vmcs) L4_NOTHROW;

/**
 * \brief Read a natural width VMCS field.
 * \ingroup l4_vm_vmx_api
 *
 * \param vmcs Pointer to the software VMCS.
 * \param field The VMCS field index as used on VMX hardware.
 * \return The value of the VMCS field with the given index.
 */
L4_INLINE
l4_umword_t
l4_vm_vmx_read_nat(void *vmcs, unsigned field) L4_NOTHROW;

/**
 * \brief Read a 16bit VMCS field.
 * \ingroup l4_vm_vmx_api
 *
 * \param vmcs Pointer to the software VMCS.
 * \param field The VMCS field index as used on VMX hardware.
 * \return The value of the VMCS field with the given index.
 */
L4_INLINE
l4_uint16_t
l4_vm_vmx_read_16(void *vmcs, unsigned field) L4_NOTHROW;

/**
 * \brief Read a 32bit VMCS field.
 * \ingroup l4_vm_vmx_api
 *
 * \param vmcs Pointer to the software VMCS.
 * \param field The VMCS field index as used on VMX hardware.
 * \return The value of the VMCS field with the given index.
 */
L4_INLINE
l4_uint32_t
l4_vm_vmx_read_32(void *vmcs, unsigned field) L4_NOTHROW;

/**
 * \brief Read a 64bit VMCS field.
 * \ingroup l4_vm_vmx_api
 *
 * \param vmcs Pointer to the software VMCS.
 * \param field The VMCS field index as used on VMX hardware.
 * \return The value of the VMCS field with the given index.
 */
L4_INLINE
l4_uint64_t
l4_vm_vmx_read_64(void *vmcs, unsigned field) L4_NOTHROW;

/**
 * \brief Read any VMCS field.
 * \ingroup l4_vm_vmx_api
 *
 * \param vmcs Pointer to the software VMCS.
 * \param field The VMCS field index as used on VMX hardware.
 * \return The value of the VMCS field with the given index.
 */
L4_INLINE
l4_uint64_t
L4_vm_vmx_read(void *vmcs, unsigned field) L4_NOTHROW;

/**
 * \brief Write to a natural width VMCS field.
 * \ingroup l4_vm_vmx_api
 *
 * \param vmcs   Pointer to the software VMCS.
 * \param field  The VMCS field index as used on VMX hardware.
 * \param val    The value that shall be written to the given field.
 */
L4_INLINE
void
l4_vm_vmx_write_nat(void *vmcs, unsigned field, l4_umword_t val) L4_NOTHROW;

/**
 * \brief Write to a 16bit VMCS field.
 * \ingroup l4_vm_vmx_api
 *
 * \param vmcs   Pointer to the software VMCS.
 * \param field  The VMCS field index as used on VMX hardware.
 * \param val    The value that shall be written to the given field.
 */
L4_INLINE
void
l4_vm_vmx_write_16(void *vmcs, unsigned field, l4_uint16_t val) L4_NOTHROW;

/**
 * \brief Write to a 32bit VMCS field.
 * \ingroup l4_vm_vmx_api
 *
 * \param vmcs   Pointer to the software VMCS.
 * \param field  The VMCS field index as used on VMX hardware.
 * \param val    The value that shall be written to the given field.
 */
L4_INLINE
void
l4_vm_vmx_write_32(void *vmcs, unsigned field, l4_uint32_t val) L4_NOTHROW;

/**
 * \brief Write to a 64bit VMCS field.
 * \ingroup l4_vm_vmx_api
 *
 * \param vmcs   Pointer to the software VMCS.
 * \param field  The VMCS field index as used on VMX hardware.
 * \param val    The value that shall be written to the given field.
 */
L4_INLINE
void
l4_vm_vmx_write_64(void *vmcs, unsigned field, l4_uint64_t val) L4_NOTHROW;

/**
 * \brief Write to an arbitrary VMCS field.
 * \ingroup l4_vm_vmx_api
 *
 * \param vmcs   Pointer to the software VMCS.
 * \param field  The VMCS field index as used on VMX hardware.
 * \param val    The value that shall be written to the given field.
 */
L4_INLINE
void
l4_vm_vmx_write(void *vmcs, unsigned field, l4_uint64_t val) L4_NOTHROW;


/* Implementations */

L4_INLINE
unsigned
l4_vm_vmx_field_order(unsigned field) L4_NOTHROW
{
  switch (field >> 13)
    {
    case 0: return 1;
    case 1: return 3;
    case 2: return 2;
    case 3: if (sizeof(l4_umword_t) == 8) return 3; else return 2;
    default: return 0;
    }
}


L4_INLINE
unsigned
l4_vm_vmx_field_len(unsigned field) L4_NOTHROW
{
  return 1 << l4_vm_vmx_field_order(field);
}


/* Internal VCPU state layout:
 *
 * VCPU State:
 * 0 - xxx: normal IA32 VCPU state (l4_vcpu_state_t)
 *    200h: VMX capabilities (see l4_vm_vmx_get_caps)
 *    400h: Fiasco.OC VMCS
 *
 * Fiasco.OC VMCS:
 *  0h -   7h: Reserved
 *  8h -   Fh: ignored by kernel, stores current VMCS for l4_vm_vmx_clear...
 * 10h -  13h: L4_VM_VMX_VMCS_CR2 value used by the kernel
 * 14h -  1Fh: Reserved
 * 20h -  3Fh: VMCS field offset table
 * 40h - BFFh: Data (VMCS field data)
 *
 * VMCS field offset table:
 *  0h -  2h: 3 offsets for 16bit fields:
 *            0: Control fields, 1: read-only fields, 2: guest state
 *            all offsets in 64byte granules relative to the start of the VMCS
 *        3h: Reserved
 *  4h -  7h: Index shift values for 16bit, 64bit, 32bit, and natural width fields
 *  8h -  Ah: 3 offsets for 64bit fields
 *  Bh -  Fh: Reserved
 * 10h - 12h: 3 offsets for 32bit fields
 * 13h - 17h: Reserved
 * 18h - 1Ah: 3 offsets for natural width fields
 *       1Bh: Reserved
 *       1Ch: Offset of first VMCS field
 *       1Dh: Full size of VMCS fields
 * 1Eh - 1Fh: Reserved
 *
 */

L4_INLINE
unsigned
l4_vm_vmx_field_offset(void const *vmcs, unsigned field) L4_NOTHROW
{
  // the offset table is at 0x20 offset
  enum { Si = 4 };
  l4_uint8_t const *offsets = (l4_uint8_t const *)vmcs;
  offsets += 0x20;
  return (unsigned)offsets[field >> 10] * 64 + ((field & 0x3ff) << offsets[Si + (field >> 13)]);
}

L4_INLINE
void *
l4_vm_vmx_field_ptr(void *vmcs, unsigned field) L4_NOTHROW
{
  return (void *)((char *)vmcs + l4_vm_vmx_field_offset(vmcs, field));
}

/**
 * \brief Copy full vmcs state
 * \internal
 */
L4_INLINE
void
l4_vm_vmx_copy_state(void const *vmcs, void *_dst, void const *_src) L4_NOTHROW
{
  l4_uint8_t const *offsets = (l4_uint8_t const *)vmcs + 0x20;

  unsigned offs = offsets[28] * 64;
  unsigned size = offsets[29] * 64;
  char *const dst = (char*)_dst + offs;
  char const *const src = (char const *)_src + offs;
  __builtin_memcpy(dst, src, size);
}

L4_INLINE
void
l4_vm_vmx_clear(void *vmcs, void *user_vmcs) L4_NOTHROW
{
  void **current_vmcs = (void **)((char *)vmcs + 8);
  if (*current_vmcs != user_vmcs)
    return;

  l4_vm_vmx_copy_state(vmcs, user_vmcs, vmcs);
  *current_vmcs = 0;
}

L4_INLINE
void
l4_vm_vmx_ptr_load(void *vmcs, void *user_vmcs) L4_NOTHROW
{
  void **current_vmcs = (void **)((char *)vmcs + 8);
  if (*current_vmcs == user_vmcs)
    return;

  if (*current_vmcs && *current_vmcs != user_vmcs)
    l4_vm_vmx_clear(vmcs, *current_vmcs);

  *current_vmcs = user_vmcs;
  l4_vm_vmx_copy_state(vmcs, vmcs, user_vmcs);
}


L4_INLINE
l4_umword_t
l4_vm_vmx_read_nat(void *vmcs, unsigned field) L4_NOTHROW
{ return *(l4_umword_t*)(l4_vm_vmx_field_ptr(vmcs, field)); }

L4_INLINE
l4_uint16_t
l4_vm_vmx_read_16(void *vmcs, unsigned field) L4_NOTHROW
{ return *(l4_uint16_t*)(l4_vm_vmx_field_ptr(vmcs, field)); }

L4_INLINE
l4_uint32_t
l4_vm_vmx_read_32(void *vmcs, unsigned field) L4_NOTHROW
{ return *(l4_uint32_t*)(l4_vm_vmx_field_ptr(vmcs, field)); }

L4_INLINE
l4_uint64_t
l4_vm_vmx_read_64(void *vmcs, unsigned field) L4_NOTHROW
{ return *(l4_uint64_t*)(l4_vm_vmx_field_ptr(vmcs, field)); }

L4_INLINE
l4_uint64_t
L4_vm_vmx_read(void *vmcs, unsigned field) L4_NOTHROW
{
  switch(field >> 13)
    {
    case 0: return l4_vm_vmx_read_16(vmcs, field);
    case 1: return l4_vm_vmx_read_64(vmcs, field);
    case 2: return l4_vm_vmx_read_32(vmcs, field);
    case 3: return l4_vm_vmx_read_nat(vmcs, field);
    }
}

L4_INLINE
void
l4_vm_vmx_write_nat(void *vmcs, unsigned field, l4_umword_t val) L4_NOTHROW
{ *(l4_umword_t*)(l4_vm_vmx_field_ptr(vmcs, field)) = val; }

L4_INLINE
void
l4_vm_vmx_write_16(void *vmcs, unsigned field, l4_uint16_t val) L4_NOTHROW
{ *(l4_uint16_t*)(l4_vm_vmx_field_ptr(vmcs, field)) = val; }

L4_INLINE
void
l4_vm_vmx_write_32(void *vmcs, unsigned field, l4_uint32_t val) L4_NOTHROW
{ *(l4_uint32_t*)(l4_vm_vmx_field_ptr(vmcs, field)) = val; }

L4_INLINE
void
l4_vm_vmx_write_64(void *vmcs, unsigned field, l4_uint64_t val) L4_NOTHROW
{ *(l4_uint64_t*)(l4_vm_vmx_field_ptr(vmcs, field)) = val; }


L4_INLINE
void
l4_vm_vmx_write(void *vmcs, unsigned field, l4_uint64_t val) L4_NOTHROW
{
  switch(field >> 13)
    {
    case 0: l4_vm_vmx_write_16(vmcs, field, val); break;
    case 1: l4_vm_vmx_write_64(vmcs, field, val); break;
    case 2: l4_vm_vmx_write_32(vmcs, field, val); break;
    case 3: l4_vm_vmx_write_nat(vmcs, field, val); break;
    }
}

L4_INLINE
l4_uint64_t
l4_vm_vmx_get_caps(void const *vcpu_state, unsigned cap_msr) L4_NOTHROW
{
  l4_uint64_t const *caps = (l4_uint64_t const *)((char const *)(vcpu_state) + L4_VCPU_OFFSET_EXT_INFOS);
  return caps[cap_msr & 0xf];
}

L4_INLINE
l4_uint32_t
l4_vm_vmx_get_caps_default1(void const *vcpu_state, unsigned cap_msr) L4_NOTHROW
{
  l4_uint32_t const *caps = (l4_uint32_t const *)((char const *)(vcpu_state) + L4_VCPU_OFFSET_EXT_INFOS);
  return caps[L4_VM_VMX_NUM_CAPS_REGS * 2 + ((cap_msr & 0xf) - L4_VM_VMX_PINBASED_CTLS_DFL1_REG)];
}

L4_INLINE
l4_uint32_t
l4_vm_vmx_get_cr2_index(void const *vmcs) L4_NOTHROW
{
  l4_uint32_t const *infos = (l4_uint32_t const *)vmcs;
  return infos[3];
}
