| // 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) }; |
| } |