1 // Copyright 2023, The Android Open Source Project
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 //! Docs for booting on AArch64 is at:
16 //!
17 //! https://www.kernel.org/doc/html/v5.11/arm64/booting.html
18
19 use core::arch::asm;
20
21 #[derive(Debug, PartialEq)]
22 pub enum ExceptionLevel {
23 EL0,
24 EL1,
25 EL2,
26 EL3,
27 }
28
29 /// Gets the current EL;
current_el() -> ExceptionLevel30 pub fn current_el() -> ExceptionLevel {
31 let mut el: u64;
32 // SAFETY: The assembly code only read current exception level.
33 unsafe {
34 asm!(
35 "mrs {el}, CurrentEL",
36 el = out(reg) el,
37 );
38 }
39 el = (el >> 2) & 3;
40 match el {
41 0 => ExceptionLevel::EL0,
42 1 => ExceptionLevel::EL1,
43 2 => ExceptionLevel::EL2,
44 3 => ExceptionLevel::EL3,
45 v => panic!("Unknown EL {v}"),
46 }
47 }
48
49 extern "C" {
50 /// Clean and invalidate data cache by address range. The function is from ATF library.
flush_dcache_range(addr: usize, len: usize)51 fn flush_dcache_range(addr: usize, len: usize);
52 }
53
54 /// Flush all data cache for the given buffer.
flush_dcache_buffer(buf: &[u8])55 fn flush_dcache_buffer(buf: &[u8]) {
56 unsafe { flush_dcache_range(buf.as_ptr() as usize, buf.len()) }
57 // SAFETY: Assembly code for instruction synchronization.
58 unsafe { asm!("isb") };
59 }
60
61 /// Disable cache, MMU and jump to the given kernel address with arguments.
62 ///
63 /// # Args
64 ///
65 /// * `addr`: Address to jump.
66 /// * `arg[0-3]`: Arguments for the target jump address.
67 ///
68 /// # Safety
69 ///
70 /// * Caller must ensure that `addr` contains valid execution code.
71 /// * Caller must ensure to flush any data cache for memory regions that contain data to be accessed
72 /// by the destination code, including the execution code itself at address `addr`
jump_kernel(addr: usize, arg0: usize, arg1: usize, arg2: usize, arg3: usize) -> !73 unsafe fn jump_kernel(addr: usize, arg0: usize, arg1: usize, arg2: usize, arg3: usize) -> ! {
74 // TODO(b/334962949): Disable other stuffs such as interrupt, async abort, branch prediction etc.
75
76 // After disabling MMU and cache, memory regions that have unflushed cache are stale and cannot
77 // be trusted, including stack memory. Therefore all needed data including local variables must
78 // be ensured to be loaded to registers first. `disable_cache_mmu_and_jump` only operates on
79 // registers and does not access stack or any other memory.
80 asm!(
81 "b disable_cache_mmu_and_jump",
82 in("x0") arg0,
83 in("x1") arg1,
84 in("x2") arg2,
85 in("x3") arg3,
86 in("x4") addr,
87 );
88 unreachable!();
89 }
90
91 /// Boots a Linux kernel in mode EL2 or lower with the given FDT blob.
92 ///
93 /// # Safety
94 ///
95 /// Caller must ensure that `kernel` contains a valid Linux kernel.
jump_linux_el2_or_lower(kernel: &[u8], ramdisk: &[u8], fdt: &[u8]) -> !96 pub unsafe fn jump_linux_el2_or_lower(kernel: &[u8], ramdisk: &[u8], fdt: &[u8]) -> ! {
97 assert_ne!(current_el(), ExceptionLevel::EL3);
98 // The following is sufficient to work for existing use cases such as Cuttlefish. But there are
99 // additional initializations listed
100 // https://www.kernel.org/doc/html/v5.11/arm64/booting.html that may need to be performed
101 // explicitly for other platforms.
102
103 flush_dcache_buffer(kernel);
104 flush_dcache_buffer(ramdisk);
105 flush_dcache_buffer(fdt);
106 // SAFETY:
107 // * `kernel`, `ramdisk` and `fdt` have been flushed.
108 // * By requirement of this function, `kernel` is a valid kernel entry point.
109 unsafe { jump_kernel(kernel.as_ptr() as _, fdt.as_ptr() as _, 0, 0, 0) };
110 }
111
112 /// Boots a ZBI kernel in mode EL2 or lower with the given ZBI blob.
113 ///
114 /// # Safety
115 ///
116 /// Caller must ensure that `zbi_kernel` contains a valid zircon kernel ZBI item and `entry_off` is
117 /// the correct kernel entry offset.
jump_zircon_el2_or_lower(zbi_kernel: &[u8], entry_off: usize, zbi_item: &[u8]) -> !118 pub unsafe fn jump_zircon_el2_or_lower(zbi_kernel: &[u8], entry_off: usize, zbi_item: &[u8]) -> ! {
119 assert_ne!(current_el(), ExceptionLevel::EL3);
120 flush_dcache_buffer(zbi_kernel);
121 flush_dcache_buffer(zbi_item);
122 let addr = (zbi_kernel.as_ptr() as usize).checked_add(entry_off).unwrap();
123 // SAFETY:
124 // * `zbi_kernel` and `zbi_item` have been flushed.
125 // * By requirement of this function, the computed `addr` is a valid kernel entry point.
126 unsafe { jump_kernel(addr, zbi_item.as_ptr() as _, 0, 0, 0) };
127 }
128