mirror of
https://github.com/SerenityOS/serenity
synced 2026-04-25 17:15:42 +02:00
269 lines
12 KiB
C++
269 lines
12 KiB
C++
/*
|
|
* Copyright (c) 2024, Sönke Holz <soenke.holz@serenityos.org>
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*/
|
|
|
|
#include <AK/Types.h>
|
|
|
|
#include <Kernel/Arch/aarch64/ASM_wrapper.h>
|
|
#include <Kernel/Arch/aarch64/Registers.h>
|
|
#include <Kernel/Sections.h>
|
|
|
|
#include <Kernel/EFIPrekernel/Arch/Boot.h>
|
|
#include <Kernel/EFIPrekernel/Arch/MMU.h>
|
|
#include <Kernel/EFIPrekernel/Panic.h>
|
|
#include <Kernel/EFIPrekernel/Runtime.h>
|
|
#include <Kernel/EFIPrekernel/VirtualMemoryLayout.h>
|
|
|
|
namespace Kernel {
|
|
|
|
// This function has to fit into one page as it will be identity mapped.
|
|
[[gnu::aligned(PAGE_SIZE)]] [[noreturn]] NEVER_INLINE static void enter_kernel_helper(FlatPtr sctlr_el1, FlatPtr kernel_entry, FlatPtr kernel_sp, FlatPtr boot_info_vaddr) asm("enter_kernel_helper");
|
|
[[gnu::aligned(PAGE_SIZE)]] [[noreturn]] NEVER_INLINE static void enter_kernel_helper(FlatPtr sctlr_el1, FlatPtr kernel_entry, FlatPtr kernel_sp, FlatPtr boot_info_vaddr)
|
|
{
|
|
register FlatPtr x0 asm("x0") = boot_info_vaddr;
|
|
register FlatPtr sp asm("sp") = kernel_sp;
|
|
asm volatile(R"(
|
|
// Invalidate the TLBs before enabling the MMU, as the TLBs might still contain old values.
|
|
tlbi vmalle1
|
|
dsb ish
|
|
isb
|
|
|
|
msr sctlr_el1, %[sctlr_el1]
|
|
isb
|
|
|
|
mov lr, xzr
|
|
mov fp, xzr
|
|
|
|
br %[kernel_entry]
|
|
)"
|
|
:
|
|
: "r"(x0), "r"(sp), [sctlr_el1] "r"(sctlr_el1), [kernel_sp] "r"(kernel_sp), [kernel_entry] "r"(kernel_entry)
|
|
: "memory");
|
|
|
|
__builtin_unreachable();
|
|
}
|
|
|
|
static void map_bootstrap_page(void* root_page_table, BootInfo& boot_info)
|
|
{
|
|
// FIXME: This leaks < (page table levels) pages, since all active allocations after ExitBootServices are currently eternal.
|
|
// We could theoretically reclaim them in the kernel.
|
|
// NOTE: If this map_pages ever fails, the kernel vaddr range is inside our (physical) prekernel range.
|
|
if (auto result = map_pages(root_page_table, bit_cast<FlatPtr>(&enter_kernel_helper), bit_cast<PhysicalPtr>(&enter_kernel_helper), 1, Access::Read | Access::Execute); result.is_error())
|
|
PANIC("Failed to identity map the enter_kernel_helper function: {}", result.release_error());
|
|
|
|
auto maybe_bootstrap_page_page_directory = get_or_insert_page_table(root_page_table, bit_cast<PhysicalPtr>(&enter_kernel_helper), 1);
|
|
if (maybe_bootstrap_page_page_directory.is_error())
|
|
PANIC("Could not find the bootstrap page page directory: {}", maybe_bootstrap_page_page_directory.release_error());
|
|
|
|
boot_info.boot_method_specific.efi.bootstrap_page_vaddr = VirtualAddress { bit_cast<FlatPtr>(&enter_kernel_helper) };
|
|
boot_info.boot_method_specific.efi.bootstrap_page_page_directory_paddr = PhysicalAddress { bit_cast<PhysicalPtr>(maybe_bootstrap_page_page_directory.value()) };
|
|
}
|
|
|
|
static void set_up_quickmap_page_table(void* root_page_table, BootInfo& boot_info)
|
|
{
|
|
auto kernel_pt1024_base = boot_info.kernel_mapping_base + KERNEL_PT1024_OFFSET;
|
|
|
|
auto maybe_quickmap_page_table_paddr = get_or_insert_page_table(root_page_table, kernel_pt1024_base, 0, true);
|
|
if (maybe_quickmap_page_table_paddr.is_error())
|
|
PANIC("Failed to insert the quickmap page table: {}", maybe_quickmap_page_table_paddr.release_error());
|
|
|
|
boot_info.boot_pd_kernel_pt1023 = bit_cast<Memory::PageTableEntry*>(QUICKMAP_PAGE_TABLE_VADDR);
|
|
|
|
if (auto result = map_pages(root_page_table, bit_cast<FlatPtr>(boot_info.boot_pd_kernel_pt1023), bit_cast<PhysicalPtr>(maybe_quickmap_page_table_paddr.value()), 1, Access::Read | Access::Write); result.is_error())
|
|
PANIC("Failed to map the quickmap page table: {}", result.release_error());
|
|
}
|
|
|
|
void arch_prepare_boot(void* root_page_table, BootInfo& boot_info)
|
|
{
|
|
if (boot_info.flattened_devicetree_paddr.is_null())
|
|
PANIC("No devicetree configuration table was found. AArch64 systems without a devicetree UEFI configuration table are not supported.");
|
|
|
|
map_bootstrap_page(root_page_table, boot_info);
|
|
set_up_quickmap_page_table(root_page_table, boot_info);
|
|
|
|
auto maybe_kernel_page_directory = get_or_insert_page_table(root_page_table, boot_info.kernel_mapping_base, 1);
|
|
if (maybe_kernel_page_directory.is_error())
|
|
PANIC("Could not find the kernel page directory: {}", maybe_kernel_page_directory.release_error());
|
|
|
|
auto maybe_kernel_pdpt = get_or_insert_page_table(root_page_table, boot_info.kernel_mapping_base, 2);
|
|
if (maybe_kernel_pdpt.is_error())
|
|
PANIC("Could not find the kernel page directory pointer table: {}", maybe_kernel_pdpt.release_error());
|
|
|
|
boot_info.boot_pml4t = PhysicalAddress { bit_cast<PhysicalPtr>(root_page_table) };
|
|
boot_info.boot_pdpt = PhysicalAddress { bit_cast<PhysicalPtr>(maybe_kernel_pdpt.value()) };
|
|
boot_info.boot_pd_kernel = PhysicalAddress { bit_cast<PhysicalPtr>(maybe_kernel_page_directory.value()) };
|
|
}
|
|
|
|
[[noreturn]] void arch_enter_kernel(void* root_page_table, FlatPtr kernel_entry_vaddr, FlatPtr kernel_stack_pointer, FlatPtr boot_info_vaddr)
|
|
{
|
|
// Current execution state (from https://uefi.org/specs/UEFI/2.11/02_Overview.html#aarch64-platforms):
|
|
// * We are either in EL2 or EL1
|
|
// * MMU is enabled and all RAM is identity mapped (other memory regions undefined)
|
|
// * SCTLR_EL2/SCTLR_EL1 is set to:
|
|
// EE=0: Little endian
|
|
// I=1: Instruction cache on
|
|
// SA=1: Stack alignment check on
|
|
// C=1: Data cache on
|
|
// A=0: Alignment check off
|
|
// M=1: MMU on
|
|
// (other bits are undefined)
|
|
// * TCR_EL2/TCR_EL1 is set to:
|
|
// TBI=0: Top byte ignore off
|
|
// (I)PS: Set to the valid (intermediate) physical address size
|
|
// TG0=0: 4K translation granule (aka page size)
|
|
// (other bits are undefined)
|
|
// * Only TTBR0_EL2/TTBR0_EL1 must be used
|
|
// * Interrupts are enabled
|
|
// * CNTFRQ_EL0 is set to the correct timer frequency
|
|
// * CNTHCTL_EL2.{EL1PCTEN,EL1PCEN} are set to 1 (physical timer is accessible in EL1 and EL0)
|
|
|
|
// We should be in EL2 or EL1.
|
|
auto current_el = Aarch64::Asm::get_current_exception_level();
|
|
if (current_el == Aarch64::Asm::ExceptionLevel::EL3 || current_el == Aarch64::Asm::ExceptionLevel::EL0)
|
|
halt();
|
|
|
|
Aarch64::CPACR_EL1 cpacr_el1 = {};
|
|
cpacr_el1.ZEN = 0; // Trap SVE instructions at EL1 and EL0
|
|
cpacr_el1.FPEN = 0b11; // Don't trap Advanced SIMD and floating-point instructions
|
|
cpacr_el1.SMEN = 0; // Trap SME instructions at EL1 and EL0
|
|
cpacr_el1.TTA = 0; // Don't trap access to trace registers
|
|
Aarch64::CPACR_EL1::write(cpacr_el1);
|
|
|
|
// Prepare some register values that will be used in the assembly code down below.
|
|
|
|
// These register values will be used if we start in EL2:
|
|
|
|
// Hypervisor Configuration
|
|
// Stage 2 address translation is disabled, so intermediate physical address == physical address.
|
|
Aarch64::HCR_EL2 hcr_el2 = {};
|
|
hcr_el2.RW = 1; // EL1 to use AArch64
|
|
|
|
// System Control Register for EL2
|
|
auto sctlr_el2 = Aarch64::SCTLR_EL2::default_value();
|
|
|
|
// Process state for entering EL1
|
|
Aarch64::SPSR_EL2 spsr_el2 = {};
|
|
|
|
// All interrupts masked
|
|
spsr_el2.A = 1;
|
|
spsr_el2.I = 1;
|
|
spsr_el2.F = 1;
|
|
|
|
spsr_el2.M = Aarch64::SPSR_EL2::Mode::EL1h; // Enter EL1
|
|
|
|
// These register values are used to set up EL1:
|
|
|
|
// System Control Register for EL1
|
|
Aarch64::SCTLR_EL1 sctlr_el1 = Aarch64::SCTLR_EL1::default_value();
|
|
|
|
// Memory attributes
|
|
Aarch64::MAIR_EL1 mair_el1 = {};
|
|
mair_el1.Attr[0] = 0xFF; // Normal memory
|
|
mair_el1.Attr[1] = 0b00000100; // Device-nGnRE memory (non-cacheable)
|
|
mair_el1.Attr[2] = 0b01000100; // Normal Non-cacheable
|
|
|
|
// Address translation configuration
|
|
Aarch64::TCR_EL1 tcr_el1 = {};
|
|
|
|
tcr_el1.SH1 = Aarch64::TCR_EL1::InnerShareable;
|
|
tcr_el1.ORGN1 = Aarch64::TCR_EL1::NormalMemory_Outer_WriteBack_ReadAllocate_WriteAllocateCacheable;
|
|
tcr_el1.IRGN1 = Aarch64::TCR_EL1::NormalMemory_Inner_WriteBack_ReadAllocate_WriteAllocateCacheable;
|
|
tcr_el1.T1SZ = 16;
|
|
|
|
tcr_el1.SH0 = Aarch64::TCR_EL1::InnerShareable;
|
|
tcr_el1.ORGN0 = Aarch64::TCR_EL1::NormalMemory_Outer_WriteBack_ReadAllocate_WriteAllocateCacheable;
|
|
tcr_el1.IRGN0 = Aarch64::TCR_EL1::NormalMemory_Inner_WriteBack_ReadAllocate_WriteAllocateCacheable;
|
|
tcr_el1.T0SZ = 16;
|
|
|
|
tcr_el1.TG1 = Aarch64::TCR_EL1::TG1GranuleSize::Size_4KB;
|
|
tcr_el1.TG0 = Aarch64::TCR_EL1::TG0GranuleSize::Size_4KB;
|
|
|
|
// Auto-detect the Intermediate Physical Address Size.
|
|
Aarch64::ID_AA64MMFR0_EL1 feature_register = Aarch64::ID_AA64MMFR0_EL1::read();
|
|
tcr_el1.IPS = feature_register.PARange;
|
|
|
|
Aarch64::TCR_EL1::write(tcr_el1);
|
|
|
|
// System Control Register value for EL1 with MMU enabled
|
|
Aarch64::SCTLR_EL1 sctlr_el1_mmu_on = Aarch64::SCTLR_EL1::default_value();
|
|
sctlr_el1_mmu_on.M = 1; // Enable MMU
|
|
sctlr_el1_mmu_on.C = 1; // Enable data cache
|
|
sctlr_el1_mmu_on.I = 1; // Enable instruction cache
|
|
|
|
// The following code needs to disable the MMU while we load our MMU configuration.
|
|
// All memory accesses are Device-nGnRnE (uncacheable) while the MMU is disabled,
|
|
// which means that the following code isn't allowed to access any memory that was previously written to,
|
|
// as the firmware likely mapped it as cacheable memory.
|
|
// Memory accesses with incompatible attributes can result in unexpected behavior.
|
|
|
|
// Therefore, the following code is written in assembly to ensure that it doesn't access any memory (including the stack!).
|
|
|
|
asm volatile(R"(
|
|
cmp %w[current_el], #2
|
|
b.ne 1f
|
|
|
|
// We are in EL2, so we need to set up EL2 and enter EL1.
|
|
|
|
// Intialize SCTLR_EL2 with our defaults.
|
|
// This also disables the MMU for EL2.
|
|
msr sctlr_el2, %[sctlr_el2]
|
|
isb
|
|
|
|
// Set HCR_EL2 to a known value.
|
|
msr hcr_el2, %[hcr_el2]
|
|
|
|
// Intialize SCTLR_EL1 with our defaults.
|
|
msr sctlr_el1, %[sctlr_el1]
|
|
|
|
// Enter EL1.
|
|
|
|
// Configure SPSR_EL2 to enter EL1.
|
|
msr spsr_el2, %[spsr_el2]
|
|
// Copy the current stack pointer.
|
|
mov x0, sp
|
|
msr sp_el1, x0
|
|
// Enter at label 1.
|
|
adr x0, 1f
|
|
msr elr_el2, x0
|
|
|
|
eret
|
|
|
|
1:
|
|
// We are in EL1, so we need to set up EL1.
|
|
|
|
// Initialize SCTLR_EL1 with our defaults.
|
|
// This also ensures the MMU is disabled for EL1 while we load our MMU settings.
|
|
msr sctlr_el1, %[sctlr_el1]
|
|
isb
|
|
|
|
// Set up paging.
|
|
|
|
// Set TTBR*_EL1 to the root page table.
|
|
msr ttbr0_el1, %[root_page_table]
|
|
msr ttbr1_el1, %[root_page_table]
|
|
|
|
// Set MAIR_EL1 to our memory attributes.
|
|
msr mair_el1, %[mair_el1]
|
|
|
|
// Set TCR_EL1 to our address translation configuration.
|
|
msr tcr_el1, %[tcr_el1]
|
|
|
|
// Call the enter_kernel_helper function, which will enable the MMU and jump to the kernel entry point.
|
|
mov x0, %[sctlr_el1_mmu_on]
|
|
mov x1, %[kernel_entry_vaddr]
|
|
mov x2, %[kernel_stack_pointer]
|
|
mov x3, %[boot_info_vaddr]
|
|
b enter_kernel_helper
|
|
)" ::[current_el] "r"(current_el),
|
|
[sctlr_el2] "r"(sctlr_el2), [hcr_el2] "r"(hcr_el2), [spsr_el2] "r"(spsr_el2),
|
|
[sctlr_el1] "r"(sctlr_el1), [root_page_table] "r"(root_page_table), [mair_el1] "r"(mair_el1), [tcr_el1] "r"(tcr_el1),
|
|
[sctlr_el1_mmu_on] "r"(sctlr_el1_mmu_on), [kernel_entry_vaddr] "r"(kernel_entry_vaddr), [kernel_stack_pointer] "r"(kernel_stack_pointer), [boot_info_vaddr] "r"(boot_info_vaddr)
|
|
: "memory", "cc", "x0", "x1", "x2", "x3");
|
|
|
|
__builtin_unreachable();
|
|
}
|
|
|
|
}
|