text: /*
   * Copyright (c) 2015-2019 Contributors as noted in the AUTHORS file
   *
   * This file is part of Solo5, a sandboxed execution environment.
   *
   * Permission to use, copy, modify, and/or distribute this software
   * for any purpose with or without fee is hereby granted, provided
   * that the above copyright notice and this permission notice appear
   * in all copies.
   *
   * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
   * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
   * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
   * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
   * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
   * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
   * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
   * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
   */
  
  #include "bindings.h"
  
  static uint64_t cpu_gdt64[GDT_NUM_ENTRIES] ALIGN_64_BIT;
  
  /*
   * The tender (hvt) or bootloader + bootstrap (virtio) starts us up with a
   * bootstrap GDT which is "invisible" to the guest, init and switch to our own
   * GDT, reloading all segment selectors in the process.
   *
   * This is done primarily since we need to do LTR later in a predictable
   * fashion.
   */
  static void gdt_init(void)
  {
      memset(cpu_gdt64, 0, sizeof(cpu_gdt64));
      cpu_gdt64[GDT_DESC_CODE] = GDT_DESC_CODE_VAL;
      cpu_gdt64[GDT_DESC_DATA] = GDT_DESC_DATA_VAL;
  
      volatile struct gdtptr gdtptr;
      gdtptr.limit = sizeof(cpu_gdt64) - 1;
      gdtptr.base = (uint64_t)&cpu_gdt64;
      __asm__ __volatile__(
              "lgdt (%0);\n"
              "pushq %1;\n"
              "pushq $1f;\n"
              "lretq;\n"
              "1:\n"
              "movq %2, %%rax;\n"
              "movl %%eax, %%ss;\n"
              "movl %%eax, %%ds;\n"
              "movl %%eax, %%es;\n"
              :
              : "r" (&gdtptr),
                "n" (GDT_DESC_OFFSET(GDT_DESC_CODE)),
                "n" (GDT_DESC_OFFSET(GDT_DESC_DATA))
              : "%rax");
  }
  
  static struct idt_gate_desc cpu_idt[IDT_NUM_ENTRIES] ALIGN_64_BIT;
  
  static void idt_fillgate(unsigned num, void *fun, unsigned ist)
  {
      struct idt_gate_desc *desc = &cpu_idt[num];
  
      /*
       * All gates are interrupt gates, all handlers run with interrupts off.
       */
      desc->offset_hi = (uint64_t)fun >> 16;
      desc->offset_lo = (uint64_t)fun & 0xffff;
      desc->selector = GDT_DESC_OFFSET(GDT_DESC_CODE);
      desc->ist = ist;
      desc->type = 0b1110;
      desc->dpl = 0;
      desc->p = 1;
  }
  
  static void idt_init(void)
  {
      /*
       * Load trap vectors. All traps run on IST2 (cpu_trap_stack), except for
       * the exceptions.
       */
  #define FILL_TRAP_GATE(num, ist) extern void cpu_trap_##num(void); \
      idt_fillgate(num, cpu_trap_##num, ist)
      FILL_TRAP_GATE(0, 2);
      FILL_TRAP_GATE(1, 2);
      FILL_TRAP_GATE(2, 3); /* #NMI runs on IST3 (cpu_nmi_stack) */
      FILL_TRAP_GATE(3, 2);
      FILL_TRAP_GATE(4, 2);
      FILL_TRAP_GATE(5, 2);
      FILL_TRAP_GATE(6, 2);
      FILL_TRAP_GATE(7, 2);
      FILL_TRAP_GATE(8, 3); /* #DF runs on IST3 (cpu_nmi_stack) */
      FILL_TRAP_GATE(10, 2);
      FILL_TRAP_GATE(11, 2);
      FILL_TRAP_GATE(12, 2);
      FILL_TRAP_GATE(13, 2);
      FILL_TRAP_GATE(14, 2);
      FILL_TRAP_GATE(16, 2);
      FILL_TRAP_GATE(17, 2);
      FILL_TRAP_GATE(18, 2);
      FILL_TRAP_GATE(19, 2);
      FILL_TRAP_GATE(20, 2);
  
      /*
       * Load irq vectors. All irqs run on IST1 (cpu_intr_stack).
       */
  #define FILL_IRQ_GATE(num, ist) extern void cpu_irq_##num(void); \
      idt_fillgate(32 + num, cpu_irq_##num, ist)
      FILL_IRQ_GATE(0, 1);
      FILL_IRQ_GATE(1, 1);
      FILL_IRQ_GATE(2, 1);
      FILL_IRQ_GATE(3, 1);
      FILL_IRQ_GATE(4, 1);
      FILL_IRQ_GATE(5, 1);
      FILL_IRQ_GATE(6, 1);
      FILL_IRQ_GATE(7, 1);
      FILL_IRQ_GATE(8, 1);
      FILL_IRQ_GATE(9, 1);
      FILL_IRQ_GATE(10, 1);
      FILL_IRQ_GATE(11, 1);
      FILL_IRQ_GATE(12, 1);
      FILL_IRQ_GATE(13, 1);
      FILL_IRQ_GATE(14, 1);
      FILL_IRQ_GATE(15, 1);
  
      volatile struct idtptr idtptr;
      idtptr.limit = sizeof(cpu_idt) - 1;
      idtptr.base = (uint64_t) &cpu_idt;
      __asm__ __volatile__("lidt (%0)" :: "r" (&idtptr));
  }
  
  static struct tss cpu_tss;
  
  static char cpu_intr_stack[4096]; /* IST1 */
  static char cpu_trap_stack[4096]; /* IST2 */
  static char cpu_nmi_stack[4096];  /* IST3 */
  
  static void tss_init(void)
  {
      extern uint64_t cpu_gdt64[];
      struct tss_desc *td = (void *)&cpu_gdt64[GDT_DESC_TSS_LO];
  
      cpu_tss.ist[0] = (uint64_t)&cpu_intr_stack[sizeof cpu_intr_stack];
      cpu_tss.ist[1] = (uint64_t)&cpu_trap_stack[sizeof cpu_trap_stack];
      cpu_tss.ist[2] = (uint64_t)&cpu_nmi_stack[sizeof cpu_nmi_stack];
      td->limit_lo = sizeof(cpu_tss);
      td->base_lo = (uint64_t)&cpu_tss;
      td->type = 0x9;
      td->dpl = 0;
      td->p = 1;
      td->limit_hi = 0;
      td->gran = 0;
      td->base_hi = (uint64_t)&cpu_tss >> 24;
      td->zero = 0;
  
      cc_barrier();
      __asm__ __volatile__("ltr %0" :: "r" ((unsigned short)(GDT_DESC_TSS_LO * 8)));
  }
  
  void cpu_init(void)
  {
      gdt_init();
      tss_init();
      idt_init();
  }
  
  static char *traps[32] = {
      "#DE", "#DB", "#NMI", "#BP", "#OF", "#BR", "#UD", "#NM", "#DF", "#9", "#TS",
      "#NP", "#SS", "#GP", "#PF", "#15", "#MF", "#AC", "#MC", "#XM", "#VE", "#21",
      "#22", "#23", "#24", "#25", "#26", "#27", "#28", "#29", "#30", "#31"
  };
  
  void cpu_trap_handler(uint64_t num, struct trap_regs *regs)
  {
      log(INFO, "Solo5: trap: type=%s ec=0x%llx rip=0x%llx rsp=0x%llx rflags=0x%llx\n",
          traps[num], (unsigned long long)regs->ec, (unsigned long long)regs->rip,
          (unsigned long long)regs->rsp, (unsigned long long)regs->rflags);
      if (num == 14)
          log(INFO, "Solo5: trap: cr2=0x%llx\n", (unsigned long long)regs->cr2);
      PANIC("Fatal trap", regs);
  }
  
  /* keeps track of cpu_intr_disable() depth */
  int cpu_intr_depth = 1;
  
  void cpu_intr_disable(void)
  {
      __asm__ __volatile__("cli");
      cpu_intr_depth++;
  }
  
  void cpu_intr_enable(void)
  {
      assert(cpu_intr_depth > 0);
  
      if (--cpu_intr_depth == 0)
          __asm__ __volatile__("sti");
  }
  
  void cpu_halt(void)
  {
      __asm__ __volatile__("cli; hlt");
      for(;;);
  }
  