ax_cpu/aarch64/init.rs
1//! Helper functions to initialize the CPU states on systems bootstrapping.
2
3use aarch64_cpu::{asm::barrier, registers::*};
4use ax_memory_addr::PhysAddr;
5
6use super::paging::{ADDRESS_BITS, MAIR_VALUE};
7
8/// Swtich current exception level to EL1.
9///
10/// It usually used in the system booting process, where the startup code is
11/// running in EL2 or EL3. Besides, the stack is not available and the MMU is
12/// not enabled.
13///
14/// # Safety
15///
16/// This function is unsafe as it changes the CPU mode.
17pub unsafe fn switch_to_el1() {
18 SPSel.write(SPSel::SP::ELx);
19 SP_EL0.set(0);
20 let current_el = CurrentEL.read(CurrentEL::EL);
21 if current_el >= 2 {
22 if current_el == 3 {
23 // Set EL2 to 64bit and enable the HVC instruction.
24 SCR_EL3.write(
25 SCR_EL3::NS::NonSecure + SCR_EL3::HCE::HvcEnabled + SCR_EL3::RW::NextELIsAarch64,
26 );
27 // Set the return address and exception level.
28 SPSR_EL3.write(
29 SPSR_EL3::M::EL1h
30 + SPSR_EL3::D::Masked
31 + SPSR_EL3::A::Masked
32 + SPSR_EL3::I::Masked
33 + SPSR_EL3::F::Masked,
34 );
35 ELR_EL3.set(LR.get());
36 }
37 // Disable EL1 timer traps and the timer offset.
38 CNTHCTL_EL2.modify(CNTHCTL_EL2::EL1PCEN::SET + CNTHCTL_EL2::EL1PCTEN::SET);
39 CNTVOFF_EL2.set(0);
40 // Set EL1 to 64bit.
41 HCR_EL2.write(HCR_EL2::RW::EL1IsAarch64);
42 // Set the return address and exception level.
43 SPSR_EL2.write(
44 SPSR_EL2::M::EL1h
45 + SPSR_EL2::D::Masked
46 + SPSR_EL2::A::Masked
47 + SPSR_EL2::I::Masked
48 + SPSR_EL2::F::Masked,
49 );
50 SP_EL1.set(SP.get());
51 ELR_EL2.set(LR.get());
52 aarch64_cpu::asm::eret();
53 }
54}
55
56/// Configures and enables the MMU on the current CPU.
57///
58/// It first sets `MAIR_EL1`, `TCR_EL1`, `TTBR0_EL1`, `TTBR1_EL1` registers to
59/// the conventional values, and then enables the MMU and caches by setting
60/// `SCTLR_EL1`.
61///
62/// # Safety
63///
64/// This function is unsafe as it changes the address translation configuration.
65pub unsafe fn init_mmu(root_paddr: PhysAddr) {
66 MAIR_EL1.set(MAIR_VALUE);
67
68 // Enable TTBR0 and TTBR1 walks, page size = 4K, vaddr size = 48 bits, paddr size = 48 bits.
69 let tcr_flags0 = TCR_EL1::EPD0::EnableTTBR0Walks
70 + TCR_EL1::TG0::KiB_4
71 + TCR_EL1::SH0::Inner
72 + TCR_EL1::ORGN0::WriteBack_ReadAlloc_WriteAlloc_Cacheable
73 + TCR_EL1::IRGN0::WriteBack_ReadAlloc_WriteAlloc_Cacheable
74 + TCR_EL1::T0SZ.val((64 - ADDRESS_BITS) as u64);
75 let tcr_flags1 = TCR_EL1::EPD1::EnableTTBR1Walks
76 + TCR_EL1::TG1::KiB_4
77 + TCR_EL1::SH1::Inner
78 + TCR_EL1::ORGN1::WriteBack_ReadAlloc_WriteAlloc_Cacheable
79 + TCR_EL1::IRGN1::WriteBack_ReadAlloc_WriteAlloc_Cacheable
80 + TCR_EL1::T1SZ.val((64 - ADDRESS_BITS) as u64);
81 let asid_size = if ID_AA64MMFR0_EL1.read(ID_AA64MMFR0_EL1::ASIDBits) == 2 {
82 TCR_EL1::AS::ASID16Bits
83 } else {
84 TCR_EL1::AS::ASID8Bits
85 };
86 TCR_EL1.write(TCR_EL1::IPS::Bits_48 + asid_size + tcr_flags0 + tcr_flags1);
87 barrier::isb(barrier::SY);
88
89 // Set both TTBR0 and TTBR1
90 let root_paddr = root_paddr.as_usize() as u64;
91 TTBR0_EL1.set(root_paddr);
92 TTBR1_EL1.set(root_paddr);
93
94 // Flush the entire TLB
95 crate::asm::flush_tlb(None);
96
97 // Enable the MMU, I/D cache, and EL0-accessible cache instructions.
98 // UCT/DZE/UCI let userspace execute `MRS CTR_EL0`, `DC ZVA`, and
99 // `DC CVAU` / `IC IVAU`. musl, glibc, and Mesa emit these
100 // unconditionally during early process startup. Without them, the
101 // first userspace cache-line lookup traps as EC=0x18 and the process
102 // dies with SIGTRAP before reaching `main()`.
103 SCTLR_EL1.modify(
104 SCTLR_EL1::M::Enable
105 + SCTLR_EL1::C::Cacheable
106 + SCTLR_EL1::I::Cacheable
107 + SCTLR_EL1::UCT::DontTrap
108 + SCTLR_EL1::DZE::DontTrap
109 + SCTLR_EL1::UCI::DontTrap,
110 );
111 // Disable SPAN (bit 23; not exposed as a named field by aarch64-cpu).
112 SCTLR_EL1.set(SCTLR_EL1.get() | (1 << 23));
113 barrier::isb(barrier::SY);
114}
115
116/// Initializes trap handling on the current CPU.
117///
118/// In detail, it initializes the exception vector, and sets `TTBR0_EL1` to 0 to
119/// block low address access.
120pub fn init_trap() {
121 #[cfg(feature = "exception-table")]
122 crate::exception_table::init_exception_table();
123 #[cfg(feature = "uspace")]
124 {
125 CNTKCTL_EL1.modify(CNTKCTL_EL1::EL0VCTEN::TrappedNone + CNTKCTL_EL1::EL0PCTEN::TrappedNone);
126 // Start this CPU's free-running PMU cycle counter and let EL0 read it, so
127 // `PMCCNTR_EL0` yields real cycle counts at both EL1 and EL0. This is the
128 // exact-frequency oracle the DVFS calibration reads in-kernel and that
129 // `cpuprobe`'s `mhz_pmc` reads from userspace (both were 0/trapping before,
130 // because these registers were only ever set on the lazy perf_event_open
131 // path). Guarded on PMUv3 being present; a live system-wide `perf stat -e
132 // cycles` may momentarily reset/disable this shared counter, which is fine
133 // for a boot/idle calibration.
134 if crate::pmu::probe().is_some() {
135 crate::pmu::init_cpu();
136 crate::pmu::cycles::configure(false, false);
137 crate::pmu::cycles::enable();
138 }
139 barrier::isb(barrier::SY);
140 }
141 unsafe extern "C" {
142 fn exception_vector_base();
143 }
144 unsafe {
145 crate::asm::write_exception_vector_base(exception_vector_base as *const () as usize);
146 crate::asm::write_user_page_table(0.into());
147 }
148}