blob: 3dd1a6f2bad98b63e39b8750460327d94b173b37 [file]
// Copyright 2023, The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Docs for booting on AArch64 is at:
//!
//! https://www.kernel.org/doc/html/v5.11/arm64/booting.html
use core::arch::asm;
use zbi::ZbiContainer;
/// ARM exception levels.
#[allow(missing_docs)]
#[derive(Debug, PartialEq)]
pub enum ExceptionLevel {
EL0,
EL1,
EL2,
EL3,
}
/// Gets the current EL;
pub fn current_el() -> ExceptionLevel {
let mut el: u64;
// SAFETY: The assembly code only read current exception level.
unsafe {
asm!(
"mrs {el}, CurrentEL",
el = out(reg) el,
);
}
el = (el >> 2) & 3;
match el {
0 => ExceptionLevel::EL0,
1 => ExceptionLevel::EL1,
2 => ExceptionLevel::EL2,
3 => ExceptionLevel::EL3,
v => panic!("Unknown EL {v}"),
}
}
extern "C" {
/// Clean and invalidate data cache by address range. The function is from ATF library.
fn flush_dcache_range(addr: usize, len: usize);
}
/// Flush all data cache for the given buffer.
fn flush_dcache_buffer(buf: &[u8]) {
unsafe { flush_dcache_range(buf.as_ptr() as usize, buf.len()) }
// SAFETY: Assembly code for instruction synchronization.
unsafe { asm!("isb") };
}
/// Disable cache, MMU and jump to the given kernel address with arguments.
///
/// # Args
///
/// * `addr`: Address to jump.
/// * `arg[0-3]`: Arguments for the target jump address.
///
/// # Safety
///
/// * Caller must ensure that `addr` contains valid execution code.
/// * Caller must ensure to flush any data cache for memory regions that contain data to be accessed
/// by the destination code, including the execution code itself at address `addr`
unsafe fn jump_kernel(addr: usize, arg0: usize, arg1: usize, arg2: usize, arg3: usize) -> ! {
// TODO(b/334962949): Disable other stuffs such as interrupt, async abort, branch prediction etc.
// After disabling MMU and cache, memory regions that have unflushed cache are stale and cannot
// be trusted, including stack memory. Therefore all needed data including local variables must
// be ensured to be loaded to registers first. `disable_cache_mmu_and_jump` only operates on
// registers and does not access stack or any other memory.
//
// SAFETY: By safety requirement of this function, `addr` contains valid execution code.
unsafe {
asm!(
"b disable_cache_mmu_and_jump",
in("x0") arg0,
in("x1") arg1,
in("x2") arg2,
in("x3") arg3,
in("x4") addr,
)
};
unreachable!();
}
/// Boots a Linux kernel in mode EL2 or lower with the given FDT blob.
///
/// # Safety
///
/// Caller must ensure that `kernel` contains a valid Linux kernel.
pub unsafe fn jump_linux_el2_or_lower(kernel: &[u8], ramdisk: &[u8], fdt: &[u8]) -> ! {
assert_ne!(current_el(), ExceptionLevel::EL3);
// The following is sufficient to work for existing use cases such as Cuttlefish. But there are
// additional initializations listed
// https://www.kernel.org/doc/html/v5.11/arm64/booting.html that may need to be performed
// explicitly for other platforms.
flush_dcache_buffer(kernel);
flush_dcache_buffer(ramdisk);
flush_dcache_buffer(fdt);
// SAFETY:
// * `kernel`, `ramdisk` and `fdt` have been flushed.
// * By requirement of this function, `kernel` is a valid kernel entry point.
unsafe { jump_kernel(kernel.as_ptr() as _, fdt.as_ptr() as _, 0, 0, 0) };
}
/// Boots a ZBI kernel in mode EL2 or lower with the given ZBI blob.
///
/// # Safety
///
/// Caller must ensure that `zbi_kernel` contains a valid zircon kernel ZBI item.
pub unsafe fn jump_zircon_el2_or_lower(kernel: &[u8], zbi_item: &[u8]) -> ! {
assert_ne!(current_el(), ExceptionLevel::EL3);
let (entry, _) =
ZbiContainer::parse(kernel).unwrap().get_kernel_entry_and_reserved_memory_size().unwrap();
flush_dcache_buffer(kernel);
flush_dcache_buffer(zbi_item);
let addr = (kernel.as_ptr() as usize).checked_add(usize::try_from(entry).unwrap()).unwrap();
// SAFETY:
// * `zbi_kernel` and `zbi_item` have been flushed.
// * By requirement of this function, the computed `addr` is a valid kernel entry point.
unsafe { jump_kernel(addr, zbi_item.as_ptr() as _, 0, 0, 0) };
}