x86/sev-es: Setup an early #VC handler
[linux-2.6-microblaze.git] / arch / x86 / kernel / head64.c
index cbb71c1..fc55cc9 100644 (file)
 #include <asm/microcode.h>
 #include <asm/kasan.h>
 #include <asm/fixmap.h>
+#include <asm/realmode.h>
+#include <asm/desc.h>
+#include <asm/extable.h>
+#include <asm/trapnr.h>
+#include <asm/sev-es.h>
 
 /*
  * Manage page tables very early on.
@@ -61,6 +66,24 @@ unsigned long vmemmap_base __ro_after_init = __VMEMMAP_BASE_L4;
 EXPORT_SYMBOL(vmemmap_base);
 #endif
 
+/*
+ * GDT used on the boot CPU before switching to virtual addresses.
+ */
+static struct desc_struct startup_gdt[GDT_ENTRIES] = {
+       [GDT_ENTRY_KERNEL32_CS]         = GDT_ENTRY_INIT(0xc09b, 0, 0xfffff),
+       [GDT_ENTRY_KERNEL_CS]           = GDT_ENTRY_INIT(0xa09b, 0, 0xfffff),
+       [GDT_ENTRY_KERNEL_DS]           = GDT_ENTRY_INIT(0xc093, 0, 0xfffff),
+};
+
+/*
+ * Address needs to be set at runtime because it references the startup_gdt
+ * while the kernel still uses a direct mapping.
+ */
+static struct desc_ptr startup_gdt_descr = {
+       .size = sizeof(startup_gdt),
+       .address = 0,
+};
+
 #define __head __section(.head.text)
 
 static void __head *fixup_pointer(void *ptr, unsigned long physaddr)
@@ -297,7 +320,7 @@ static void __init reset_early_page_tables(void)
 }
 
 /* Create a new PMD entry */
-int __init __early_make_pgtable(unsigned long address, pmdval_t pmd)
+bool __init __early_make_pgtable(unsigned long address, pmdval_t pmd)
 {
        unsigned long physaddr = address - __PAGE_OFFSET;
        pgdval_t pgd, *pgd_p;
@@ -307,7 +330,7 @@ int __init __early_make_pgtable(unsigned long address, pmdval_t pmd)
 
        /* Invalid address or early pgt is done ?  */
        if (physaddr >= MAXMEM || read_cr3_pa() != __pa_nodebug(early_top_pgt))
-               return -1;
+               return false;
 
 again:
        pgd_p = &early_top_pgt[pgd_index(address)].pgd;
@@ -364,10 +387,10 @@ again:
        }
        pmd_p[pmd_index(address)] = pmd;
 
-       return 0;
+       return true;
 }
 
-int __init early_make_pgtable(unsigned long address)
+static bool __init early_make_pgtable(unsigned long address)
 {
        unsigned long physaddr = address - __PAGE_OFFSET;
        pmdval_t pmd;
@@ -377,6 +400,15 @@ int __init early_make_pgtable(unsigned long address)
        return __early_make_pgtable(address, pmd);
 }
 
+void __init do_early_exception(struct pt_regs *regs, int trapnr)
+{
+       if (trapnr == X86_TRAP_PF &&
+           early_make_pgtable(native_read_cr2()))
+               return;
+
+       early_fixup_exception(regs, trapnr);
+}
+
 /* Don't add a printk in there. printk relies on the PDA which is not initialized 
    yet. */
 static void __init clear_bss(void)
@@ -489,3 +521,77 @@ void __init x86_64_start_reservations(char *real_mode_data)
 
        start_kernel();
 }
+
+/*
+ * Data structures and code used for IDT setup in head_64.S. The bringup-IDT is
+ * used until the idt_table takes over. On the boot CPU this happens in
+ * x86_64_start_kernel(), on secondary CPUs in start_secondary(). In both cases
+ * this happens in the functions called from head_64.S.
+ *
+ * The idt_table can't be used that early because all the code modifying it is
+ * in idt.c and can be instrumented by tracing or KASAN, which both don't work
+ * during early CPU bringup. Also the idt_table has the runtime vectors
+ * configured which require certain CPU state to be setup already (like TSS),
+ * which also hasn't happened yet in early CPU bringup.
+ */
+static gate_desc bringup_idt_table[NUM_EXCEPTION_VECTORS] __page_aligned_data;
+
+static struct desc_ptr bringup_idt_descr = {
+       .size           = (NUM_EXCEPTION_VECTORS * sizeof(gate_desc)) - 1,
+       .address        = 0, /* Set at runtime */
+};
+
+static void set_bringup_idt_handler(gate_desc *idt, int n, void *handler)
+{
+#ifdef CONFIG_AMD_MEM_ENCRYPT
+       struct idt_data data;
+       gate_desc desc;
+
+       init_idt_data(&data, n, handler);
+       idt_init_desc(&desc, &data);
+       native_write_idt_entry(idt, n, &desc);
+#endif
+}
+
+/* This runs while still in the direct mapping */
+static void startup_64_load_idt(unsigned long physbase)
+{
+       struct desc_ptr *desc = fixup_pointer(&bringup_idt_descr, physbase);
+       gate_desc *idt = fixup_pointer(bringup_idt_table, physbase);
+
+
+       if (IS_ENABLED(CONFIG_AMD_MEM_ENCRYPT)) {
+               void *handler;
+
+               /* VMM Communication Exception */
+               handler = fixup_pointer(vc_no_ghcb, physbase);
+               set_bringup_idt_handler(idt, X86_TRAP_VC, handler);
+       }
+
+       desc->address = (unsigned long)idt;
+       native_load_idt(desc);
+}
+
+/* This is used when running on kernel addresses */
+void early_setup_idt(void)
+{
+       bringup_idt_descr.address = (unsigned long)bringup_idt_table;
+       native_load_idt(&bringup_idt_descr);
+}
+
+/*
+ * Setup boot CPU state needed before kernel switches to virtual addresses.
+ */
+void __head startup_64_setup_env(unsigned long physbase)
+{
+       /* Load GDT */
+       startup_gdt_descr.address = (unsigned long)fixup_pointer(startup_gdt, physbase);
+       native_load_gdt(&startup_gdt_descr);
+
+       /* New GDT is live - reload data segment registers */
+       asm volatile("movl %%eax, %%ds\n"
+                    "movl %%eax, %%ss\n"
+                    "movl %%eax, %%es\n" : : "a"(__KERNEL_DS) : "memory");
+
+       startup_64_load_idt(physbase);
+}