:: commit 165e05ff3dba6280a61bd46ac7e9f97a53a60f6c

Mintsuki <mintsuki@protonmail.com> — 2026-03-19 23:43

parents: 471a34e4ed

protos/limine: Make aarch64 EL2 entry automatic based on firmware state

diff --git a/common/lib/spinup.asm_aarch64 b/common/lib/spinup.asm_aarch64
index bceb8f52..7efc6413 100644
--- a/common/lib/spinup.asm_aarch64
+++ b/common/lib/spinup.asm_aarch64
@@ -5,16 +5,13 @@
 // noreturn void enter_in_el1(uint64_t entry, uint64_t sp, uint64_t sctlr,
 //                            uint64_t mair, uint64_t tcr, uint64_t ttbr0,
 //                            uint64_t ttbr1, uint64_t direct_map_offset)
-// Potentially drop to EL1 from EL2 (and also disable trapping to EL2), then
-// configure EL1 state and jump to kernel.
+// Configure EL1 state and jump to kernel. Must be called at EL1.
 
 .global enter_in_el1
 enter_in_el1:
     msr spsel, #0
     mov sp, x1
 
-    PICK_EL x8, 0f, 2f
-0:
     // Switch to the new page tables
 
     // Point the EL1t handler to the continuation, such that after we page fault,
@@ -59,61 +56,6 @@ enter_in_el1:
 
     eret
 
-2:
-    // Check HCR_EL2.E2H
-    mrs x8, hcr_el2
-    tbnz x8, #34, 3f
-
-    // Configure EL1 state (normal silicon)
-    msr mair_el1, x3
-    msr tcr_el1, x4
-    msr ttbr0_el1, x5
-    msr ttbr1_el1, x6
-    msr sctlr_el1, x2
-    msr cpacr_el1, xzr
-    dsb sy
-    isb
-    b 4f
-
-3:
-    // Configure EL1 state (apple silicon)
-    msr s3_5_c10_c2_0, x3 // MAIR_EL12
-    msr s3_5_c2_c0_2, x4 // TCR_EL12
-    msr s3_5_c2_c0_0, x5 // TTBR0_EL12
-    msr s3_5_c2_c0_1, x6 // TTBR1_EL12
-    msr s3_5_c1_c0_0, x2 // SCTLR_EL12
-    msr s3_5_c1_c0_2, xzr // CPACR_EL12
-    dsb sy
-    isb
-
-4:
-
-    // Configure EL2-specific state for EL1
-
-    // Don't trap counters to EL2
-    mov x8, #3
-    msr cnthctl_el2, x8
-    msr cntvoff_el2, xzr
-
-    // Enable AArch64 in EL1
-    ldr x8, =0x80000002
-    msr hcr_el2, x8
-
-    // Don't trap FP/SIMD to EL2
-    mov x8, #0x33FF
-    msr cptr_el2, x8
-    msr hstr_el2, xzr
-
-    // Enter kernel in EL1
-    mov x8, #0x3c4
-    msr spsr_el2, x8
-    msr elr_el2, x0
-
-    mov x0, xzr
-    ZERO_REGS_EXCEPT_X0
-
-    eret
-
 // noreturn void enter_in_el2(uint64_t entry, uint64_t sp, uint64_t sctlr,
 //                            uint64_t mair, uint64_t tcr, uint64_t ttbr0,
 //                            uint64_t ttbr1, uint64_t direct_map_offset)
diff --git a/common/protos/limine.c b/common/protos/limine.c
index 9d64cd2b..2e59c48c 100644
--- a/common/protos/limine.c
+++ b/common/protos/limine.c
@@ -426,6 +426,17 @@ noreturn void limine_load(char *config, char *cmdline) {
     uint32_t eax, ebx, ecx, edx;
 #endif
 
+#if defined (__aarch64__)
+    // Booting at EL2 without VHE is not supported.
+    if (current_el() == 2) {
+        uint64_t mmfr1;
+        asm volatile ("mrs %0, id_aa64mmfr1_el1" : "=r"(mmfr1));
+        if (!((mmfr1 >> 8) & 0xF)) {
+            panic(true, "limine: Booting at EL2 without VHE support is not supported");
+        }
+    }
+#endif
+
     char *kernel_path = config_get_value(config, 0, "PATH");
     if (kernel_path == NULL) {
         kernel_path = config_get_value(config, 0, "KERNEL_PATH");
@@ -1507,29 +1518,8 @@ FEAT_END
                             physical_base, virtual_base, direct_map_offset);
 
 #if defined (__aarch64__)
-    // aarch64 EL2
-    bool want_el2 = false;
-FEAT_START
-    struct limine_aarch64_el2_request *el2_request =
-        get_request(LIMINE_AARCH64_EL2_REQUEST_ID);
-    if (el2_request == NULL) {
-        break;
-    }
-
-    // Grant EL2 if we are at EL2 and VHE is active (E2H enabled early)
-    if (current_el() == 2) {
-        uint64_t hcr;
-        asm volatile ("mrs %0, hcr_el2" : "=r"(hcr));
-        if (hcr & (1ULL << 34)) {
-            want_el2 = true;
-
-            struct limine_aarch64_el2_response *el2_response =
-                ext_mem_alloc(sizeof(struct limine_aarch64_el2_response));
-
-            el2_request->response = reported_addr(el2_response);
-        }
-    }
-FEAT_END
+    // Enter at EL2 with VHE if we are at EL2 (VHE check done at function entry)
+    bool want_el2 = (current_el() == 2);
 #endif
 
     // MP
@@ -1553,7 +1543,7 @@ FEAT_START
 
     mp_info = init_smp(config, &cpu_count, &bsp_mpidr,
                         pagemap, LIMINE_MAIR(fb_attr), LIMINE_TCR(tsz, pa), LIMINE_SCTLR,
-                        direct_map_offset, want_el2);
+                        direct_map_offset);
 #elif defined (__riscv)
     mp_info = init_smp(&cpu_count, pagemap, direct_map_offset);
 #elif defined (__loongarch64)
diff --git a/common/sys/smp.c b/common/sys/smp.c
index 1ae3ce1d..b2313132 100644
--- a/common/sys/smp.c
+++ b/common/sys/smp.c
@@ -324,7 +324,7 @@ struct limine_mp_info *init_smp(size_t   *cpu_count,
 #elif defined (__aarch64__)
 
 struct trampoline_passed_info {
-    uint64_t smp_tpl_enter_in_el2;
+    uint64_t smp_tpl_ap_el;
 
     uint64_t smp_tpl_booted_flag;
 
@@ -353,7 +353,7 @@ static bool try_start_ap(int boot_method, uint64_t method_ptr,
                          struct limine_mp_info *info_struct,
                          uint64_t ttbr0, uint64_t ttbr1, uint64_t mair,
                          uint64_t tcr, uint64_t sctlr,
-                         uint64_t hhdm_offset, bool enter_in_el2) {
+                         uint64_t hhdm_offset) {
     // Prepare the trampoline
     static void *trampoline = NULL;
     if (trampoline == NULL) {
@@ -370,13 +370,13 @@ static bool try_start_ap(int boot_method, uint64_t method_ptr,
 
     passed_info->smp_tpl_info_struct = (uint64_t)(uintptr_t)info_struct;
     passed_info->smp_tpl_booted_flag = 0;
+    passed_info->smp_tpl_ap_el       = 0;
     passed_info->smp_tpl_ttbr0       = ttbr0;
     passed_info->smp_tpl_ttbr1       = ttbr1;
     passed_info->smp_tpl_mair        = mair;
     passed_info->smp_tpl_tcr         = tcr;
     passed_info->smp_tpl_sctlr       = sctlr;
     passed_info->smp_tpl_hhdm_offset = hhdm_offset;
-    passed_info->smp_tpl_enter_in_el2 = enter_in_el2 ? 1 : 0;
 
     // Cache coherency between the I-Cache and D-Cache is not guaranteed by the
     // architecture and as such we must perform I-Cache invalidation.
@@ -447,6 +447,12 @@ static bool try_start_ap(int boot_method, uint64_t method_ptr,
         // set this flag, it has enabled its caches
 
         if (locked_read(&passed_info->smp_tpl_booted_flag) == 1) {
+            uint64_t ap_el = locked_read(&passed_info->smp_tpl_ap_el);
+            uint64_t bsp_el = current_el();
+            if (ap_el != bsp_el) {
+                panic(false, "smp: AP started at EL%u but BSP is at EL%u",
+                      (uint32_t)ap_el, (uint32_t)bsp_el);
+            }
             return true;
         }
         stall(100);
@@ -461,8 +467,7 @@ static struct limine_mp_info *try_acpi_smp(size_t   *cpu_count,
                                             uint64_t  mair,
                                             uint64_t  tcr,
                                             uint64_t  sctlr,
-                                            uint64_t  hhdm_offset,
-                                            bool      enter_in_el2) {
+                                            uint64_t  hhdm_offset) {
     int boot_method = BOOT_WITH_ACPI_PARK;
 
     // Search for FADT table
@@ -566,8 +571,7 @@ static struct limine_mp_info *try_acpi_smp(size_t   *cpu_count,
                 if (!try_start_ap(boot_method, gicc->parking_addr, info_struct,
                                   (uint64_t)(uintptr_t)pagemap.top_level[0],
                                   (uint64_t)(uintptr_t)pagemap.top_level[1],
-                                  mair, tcr, sctlr, hhdm_offset,
-                                  enter_in_el2)) {
+                                  mair, tcr, sctlr, hhdm_offset)) {
                     print("smp: FAILED to bring-up AP\n");
                     continue;
                 }
@@ -595,8 +599,7 @@ static struct limine_mp_info *try_dtb_smp( void *dtb,
                                            uint64_t  mair,
                                            uint64_t  tcr,
                                            uint64_t  sctlr,
-                                           uint64_t  hhdm_offset,
-                                           bool      enter_in_el2) {
+                                           uint64_t  hhdm_offset) {
     uint64_t bsp_mpidr;
     asm volatile ("mrs %0, mpidr_el1" : "=r"(bsp_mpidr));
 
@@ -761,8 +764,7 @@ static struct limine_mp_info *try_dtb_smp( void *dtb,
         if (!try_start_ap(boot_method, method_ptr, info_struct,
                                         (uint64_t)(uintptr_t)pagemap.top_level[0],
                                         (uint64_t)(uintptr_t)pagemap.top_level[1],
-                                        mair, tcr, sctlr, hhdm_offset,
-                                        enter_in_el2)) {
+                                        mair, tcr, sctlr, hhdm_offset)) {
             print("smp: FAILED to bring-up AP\n");
             continue;
         }
@@ -783,14 +785,12 @@ struct limine_mp_info *init_smp(const char *config,
                                  uint64_t  mair,
                                  uint64_t  tcr,
                                  uint64_t  sctlr,
-                                 uint64_t  hhdm_offset,
-                                 bool      enter_in_el2) {
+                                 uint64_t  hhdm_offset) {
     struct limine_mp_info *info = NULL;
 
     if (acpi_get_rsdp() && (info = try_acpi_smp(
                                     cpu_count, bsp_mpidr, pagemap,
-                                    mair, tcr, sctlr, hhdm_offset,
-                                    enter_in_el2)))
+                                    mair, tcr, sctlr, hhdm_offset)))
         return info;
 
     // No RSDP means no ACPI, try device trees in that case.
@@ -798,8 +798,7 @@ struct limine_mp_info *init_smp(const char *config,
     if (dtb) {
         info = try_dtb_smp(dtb,
                            cpu_count, bsp_mpidr, pagemap,
-                           mair, tcr, sctlr, hhdm_offset,
-                           enter_in_el2);
+                           mair, tcr, sctlr, hhdm_offset);
         pmm_free(dtb, fdt_totalsize(dtb));
         return info;
     }
diff --git a/common/sys/smp.h b/common/sys/smp.h
index c914f720..7e692d76 100644
--- a/common/sys/smp.h
+++ b/common/sys/smp.h
@@ -30,8 +30,7 @@ struct limine_mp_info *init_smp(const char *config,
                                  uint64_t  mair,
                                  uint64_t  tcr,
                                  uint64_t  sctlr,
-                                 uint64_t  hhdm_offset,
-                                 bool      enter_in_el2);
+                                 uint64_t  hhdm_offset);
 
 #elif defined (__riscv)
 
diff --git a/common/sys/smp_trampoline.asm_aarch64 b/common/sys/smp_trampoline.asm_aarch64
index d11db4f4..bb4f3812 100644
--- a/common/sys/smp_trampoline.asm_aarch64
+++ b/common/sys/smp_trampoline.asm_aarch64
@@ -1,6 +1,6 @@
 #include <lib/macros.aarch64_asm.h>
 
-.set tpl_enter_in_el2, -72
+.set tpl_ap_el, -72
 .set tpl_booted_flag, -64
 .set tpl_hhdm_offset, -56
 .set tpl_ttbr0, -48
@@ -32,55 +32,15 @@ smp_trampoline_start:
 
     PICK_EL x8, 1f, 0f
 0:
-    // EL2 path
+    // EL2 path - enable VHE and stay at EL2
 
-    // Check HCR_EL2.E2H
-    mrs x8, hcr_el2
-    tbnz x8, #34, 6f
-
-    // Non-VHE: check if we should stay at EL2 anyway
-    ldr x8, [x1, tpl_enter_in_el2]
-    cbnz x8, 10f
-
-    // Non-VHE drop to EL1: configure real EL1 page tables directly
-    msr mair_el1, x3
-    msr tcr_el1, x4
-    msr ttbr0_el1, x5
-    msr ttbr1_el1, x6
-    msr sctlr_el1, x2
-    msr cpacr_el1, xzr
-    isb
-    dsb sy
-    isb
-    b 7f
-
-10:
-    // Enable E2H on this AP and use VHE stay path
+    // Enable E2H if not already set
     mrs x8, hcr_el2
     orr x8, x8, #(1 << 34)
     msr hcr_el2, x8
     isb
-    b 8f
-
-6:
-    // VHE (E2H=1): check if we should stay at EL2
-    ldr x8, [x1, tpl_enter_in_el2]
-    cbnz x8, 8f
-
-    // VHE drop to EL1: use EL12 aliases for real EL1 registers
-    msr s3_5_c10_c2_0, x3 // MAIR_EL12
-    msr s3_5_c2_c0_2, x4  // TCR_EL12
-    msr s3_5_c2_c0_0, x5  // TTBR0_EL12
-    msr s3_5_c2_c0_1, x6  // TTBR1_EL12
-    msr s3_5_c1_c0_0, x2  // SCTLR_EL12
-    msr s3_5_c1_c0_2, xzr // CPACR_EL12
-    isb
-    dsb sy
-    isb
-    b 7f
 
-8:
-    // VHE stay at EL2: configure EL2 state before enabling MMU
+    // Configure EL2 state for VHE
     msr spsel, #0
 
     mov x8, #3
@@ -121,35 +81,6 @@ smp_trampoline_start:
     // Jump to the higher half mapping in case we didn't immediately crash
     br x8
 
-7:
-    // Common EL2-to-EL1 drop path
-
-    // Don't trap counters to EL2
-    mov x8, #3
-    msr cnthctl_el2, x8
-    msr cntvoff_el2, xzr
-
-    // Enable AArch64 in EL1
-    mov x8, xzr
-    orr x8, x8, #(1 << 31)
-    orr x8, x8, #(1 << 1)
-    msr hcr_el2, x8
-
-    // Don't trap FP/SIMD to EL2
-    mov x8, #0x33FF
-    msr cptr_el2, x8
-    msr hstr_el2, xzr
-
-    // Run rest of trampoline in EL1
-    mov x8, #0x3c4
-    msr spsr_el2, x8
-    adrp x8, 3f
-    add x8, x8, :lo12:3f
-    add x8, x8, x7 // Add HHDM offset
-    msr elr_el2, x8
-
-    eret
-
 1:
     // EL1 path
     msr spsel, #0
@@ -192,6 +123,12 @@ smp_trampoline_start:
     // Add HHDM offset to data pointer
     add x1, x1, x7
 
+    // Report our EL to the BSP
+    mrs x8, currentel
+    lsr x8, x8, #2
+    add x9, x1, tpl_ap_el
+    stlr x8, [x9]
+
     // Notify BSP we are alive
     mov x8, #1
     add x9, x1, tpl_booted_flag
@@ -214,10 +151,11 @@ smp_trampoline_start:
     ldr x8, [x0, #16]
     mov sp, x8
 
-    // Enter kernel
+    // Enter kernel - determine SPSR from current EL
     mov x8, #0x3c4
-    ldr x9, [x1, tpl_enter_in_el2]
-    cbz x9, 9f
+    mrs x9, currentel
+    cmp x9, #0b1000
+    b.ne 9f
     mov x8, #0x3c8
 9:
     msr spsr_el1, x8
tab: 248 wrap: offon