/*
 * Copyright (C) 2015, Intel Corporation. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * 3. Neither the name of the copyright holder nor the names of its
 *    contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
 * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "gdt.h"
#include "helpers.h"
#include "prot-domains.h"
#include "segmentation.h"
#include "stacks.h"

/*---------------------------------------------------------------------------*/
static uint32_t
segment_desc_compute_base(segment_desc_t desc)
{
  return (desc.base_hi << 24) | (desc.base_mid << 16) | desc.base_lo;
}
/*---------------------------------------------------------------------------*/
void
prot_domains_reg_multi_seg(volatile struct dom_kern_data ATTR_KERN_ADDR_SPACE *dkd,
                           uintptr_t mmio, size_t mmio_sz,
                           uintptr_t meta, size_t meta_sz)
{
  segment_desc_t desc;
  dom_id_t dom_id = PROT_DOMAINS_GET_DOM_ID(dkd);
  uint32_t kern_data_len;
  uint32_t tmp;

  if((dkd < prot_domains_kern_data) ||
     (prot_domains_kern_data_end <= dkd) ||
     (((((uintptr_t)dkd) - (uintptr_t)prot_domains_kern_data) %
         sizeof(dom_kern_data_t)) != 0)) {
    halt();
  }

  KERN_READL(tmp, dkd->ldt[DT_SEL_GET_IDX(LDT_SEL_KERN)].raw_hi);
  if(tmp != 0) {
    /* This PDCS was previously initialized, which is disallowed. */
    halt();
  }

  /* Initialize descriptors */

  if(dom_id == DOM_ID_kern) {
    kern_data_len = (uint32_t)&_ebss_kern_addr;
  } else {
    /* Non-kernel protection domains do not need to access the protection
     * domain control structures, and they may contain saved register values
     * that are private to each domain.
     */
    kern_data_len = (uint32_t)&_ebss_syscall_addr;
  }
  kern_data_len -= (uint32_t)&_sbss_kern_addr;

  segment_desc_init(&desc, (uint32_t)&_sbss_kern_addr, kern_data_len,
    /* Every protection domain requires at least read-only access to kernel
       data to read dom_client_data structures and to support the system call
       dispatcher, if applicable.  Only the kernel protection domain is granted
       read/write access to the kernel data. */
                    ((dom_id == DOM_ID_kern) ?
                        SEG_TYPE_DATA_RDWR :
                        SEG_TYPE_DATA_RDONLY) |
                    SEG_FLAG(DPL, PRIV_LVL_USER) |
                    SEG_GRAN_BYTE | SEG_DESCTYPE_NSYS);

  KERN_WRITEL(dkd->ldt[LDT_IDX_KERN].raw_lo, desc.raw_lo);
  KERN_WRITEL(dkd->ldt[LDT_IDX_KERN].raw_hi, desc.raw_hi);

  if(mmio_sz != 0) {
    if(SEG_MAX_BYTE_GRAN_LEN < mmio_sz) {
      halt();
    }

    segment_desc_init(&desc, mmio, mmio_sz,
                      SEG_FLAG(DPL, PRIV_LVL_USER) | SEG_GRAN_BYTE |
                      SEG_DESCTYPE_NSYS | SEG_TYPE_DATA_RDWR);
  } else {
    desc.raw = SEG_DESC_NOT_PRESENT;
  }

  KERN_WRITEL(dkd->ldt[LDT_IDX_MMIO].raw_lo, desc.raw_lo);
  KERN_WRITEL(dkd->ldt[LDT_IDX_MMIO].raw_hi, desc.raw_hi);

  if(meta_sz != 0) {
    if(SEG_MAX_BYTE_GRAN_LEN < meta_sz) {
      halt();
    }

    segment_desc_init(&desc, meta, meta_sz,
                      SEG_FLAG(DPL, PRIV_LVL_USER) | SEG_GRAN_BYTE |
                      SEG_DESCTYPE_NSYS | SEG_TYPE_DATA_RDWR);
  } else {
    desc.raw = SEG_DESC_NOT_PRESENT;
  }

  KERN_WRITEL(dkd->ldt[LDT_IDX_META].raw_lo, desc.raw_lo);
  KERN_WRITEL(dkd->ldt[LDT_IDX_META].raw_hi, desc.raw_hi);

  segment_desc_init(&desc,
                    KERN_DATA_OFF_TO_PHYS_ADDR(dkd->ldt),
                    sizeof(dkd->ldt),
                    SEG_FLAG(DPL, PRIV_LVL_USER) | SEG_GRAN_BYTE |
                    SEG_DESCTYPE_SYS | SEG_TYPE_LDT);
  gdt_insert(GDT_IDX_LDT(dom_id), desc);
}
/*---------------------------------------------------------------------------*/
void
prot_domains_gdt_init()
{
  int i;
  segment_desc_t desc;

  segment_desc_init(&desc,
                    (uint32_t)&_stext_addr,
                    ((uint32_t)&_etext_addr) - (uint32_t)&_stext_addr,
                    SEG_FLAG(DPL, PRIV_LVL_EXC) | SEG_GRAN_BYTE |
                    SEG_DESCTYPE_NSYS |
#if X86_CONF_PROT_DOMAINS == X86_CONF_PROT_DOMAINS__SWSEG
  /* The general protection fault handler requires read access to CS */
                    SEG_TYPE_CODE_EXRD
#else
                    SEG_TYPE_CODE_EX
#endif
                    );
  gdt_insert_boot(GDT_IDX_CODE_EXC, desc);

  segment_desc_init(&desc,
                    (uint32_t)&_sdata_addr,
                    ((uint32_t)&_edata_addr) - (uint32_t)&_sdata_addr,
                    SEG_FLAG(DPL, PRIV_LVL_USER) | SEG_GRAN_BYTE |
                    SEG_DESCTYPE_NSYS | SEG_TYPE_DATA_RDWR);
  gdt_insert_boot(GDT_IDX_DATA, desc);

  segment_desc_init(&desc,
                    (uint32_t)&_sbss_kern_addr,
                    ((uint32_t)&_ebss_kern_addr) -
                    (uint32_t)&_sbss_kern_addr,
                    SEG_FLAG(DPL, PRIV_LVL_EXC) | SEG_GRAN_BYTE |
                    SEG_DESCTYPE_NSYS | SEG_TYPE_DATA_RDWR);
  gdt_insert_boot(GDT_IDX_DATA_KERN_EXC, desc);

  segment_desc_init(&desc,
                    (uint32_t)DATA_OFF_TO_PHYS_ADDR(stacks_main),
                    STACKS_SIZE_MAIN,
                    SEG_FLAG(DPL, PRIV_LVL_USER) | SEG_GRAN_BYTE |
                    SEG_DESCTYPE_NSYS | SEG_TYPE_DATA_RDWR);
  gdt_insert_boot(GDT_IDX_STK, desc);

  segment_desc_set_limit(&desc, STACKS_SIZE_MAIN + STACKS_SIZE_INT);
  SEG_SET_FLAG(desc, DPL, PRIV_LVL_INT);
  gdt_insert_boot(GDT_IDX_STK_INT, desc);

  segment_desc_set_limit(&desc,
                         STACKS_SIZE_MAIN +
                         STACKS_SIZE_INT +
                         STACKS_SIZE_EXC);
  SEG_SET_FLAG(desc, DPL, PRIV_LVL_EXC);
  gdt_insert_boot(GDT_IDX_STK_EXC, desc);

  /* Not all domains will necessarily be initialized, so this initially marks
   * all per-domain descriptors not-present.
   */
  desc.raw = SEG_DESC_NOT_PRESENT;
  for(i = 0; i < PROT_DOMAINS_ACTUAL_CNT; i++) {
#if X86_CONF_PROT_DOMAINS == X86_CONF_PROT_DOMAINS__TSS
    gdt_insert_boot(GDT_IDX_TSS(i), desc);
#endif
    gdt_insert_boot(GDT_IDX_LDT(i), desc);
  }

  __asm__ __volatile__ (
      "mov %[_default_data_], %%ds\n\t"
      "mov %[_default_data_], %%es\n\t"
      "mov %[_kern_data_], %%" SEG_KERN "s\n\t"
      :
      : [_default_data_] "r"(GDT_SEL_DATA),
        [_kern_data_] "r"(GDT_SEL_DATA_KERN_EXC));
}
/*---------------------------------------------------------------------------*/
void
multi_segment_launch_kernel(void)
{
  /* Update segment registers. */
  __asm__ __volatile__ (
    "mov %[_data_seg_], %%ds\n\t"
    "mov %[_data_seg_], %%es\n\t"
    "mov %[_kern_seg_], %%" SEG_KERN "s\n\t"
    "mov %[_data_seg_], %%" SEG_META "s\n\t"
    :
    : [_data_seg_] "r" (GDT_SEL_DATA),
      [_kern_seg_] "r" (LDT_SEL_KERN)
    );
}
/*---------------------------------------------------------------------------*/
void
prot_domains_enable_mmio(void)
{
  __asm__ __volatile__ ("mov %0, %%" SEG_MMIO "s" :: "r" (LDT_SEL_MMIO));
}
/*---------------------------------------------------------------------------*/
void
prot_domains_disable_mmio(void)
{
  __asm__ __volatile__ ("mov %0, %%" SEG_KERN "s" :: "r" (LDT_SEL_KERN));
}
/*---------------------------------------------------------------------------*/
uintptr_t
prot_domains_lookup_meta_phys_base(dom_client_data_t ATTR_KERN_ADDR_SPACE *drv)
{
  dom_id_t dom_id;
  segment_desc_t desc;
  volatile dom_kern_data_t ATTR_KERN_ADDR_SPACE *dkd;

  KERN_READL(dom_id, drv->dom_id);

  dkd = prot_domains_kern_data + dom_id;

  KERN_READL(desc.raw_lo, dkd->ldt[DT_SEL_GET_IDX(LDT_SEL_META)].raw_lo);
  KERN_READL(desc.raw_hi, dkd->ldt[DT_SEL_GET_IDX(LDT_SEL_META)].raw_hi);

  return segment_desc_compute_base(desc);
}
/*---------------------------------------------------------------------------*/