blob: 3e9cb4b9786b33bf7a2390219c99548b9d3a4c4e [file] [log] [blame]
/*
* Copyright (c) 2025 Google Inc. All rights reserved
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#![no_std]
use core::ffi::{c_ulong, c_void};
use rust_support::{
mmu::{ARCH_MMU_FLAG_CACHED, ARCH_MMU_FLAG_PERM_NO_EXECUTE, ARCH_MMU_FLAG_PERM_RO, PAGE_SIZE},
status_t,
vmm::{vmm_alloc_physical, vmm_free_region, vmm_get_kernel_aspace},
};
use thiserror::Error;
use zerocopy::{FromBytes, Immutable, KnownLayout};
const PAGE_MASK: usize = PAGE_SIZE as usize - 1;
extern "C" {
static lk_boot_args: [c_ulong; 4];
}
#[derive(Error, Debug)]
pub enum MappingError {
#[error("failed to convert address: {0}")]
ConversionError(#[from] core::num::TryFromIntError),
#[error("mapping error {0}")]
MappingError(status_t),
}
struct Mapped<T: ?Sized + FromBytes> {
size: usize,
ptr: *mut c_void,
aligned_ptr: *mut c_void,
_phantom: core::marker::PhantomData<T>,
}
impl<T: ?Sized + FromBytes> Mapped<T> {
/// Maps [`size`] bytes at at the [`paddr`] physical address into virtual memory.
/// If the [`paddr`] is not page aligned, the function will also map the preceding space
/// to a closest page aligned address. Similarly the function will align up the size of
/// the mapped region to page alignment.
///
/// # Safety
/// - The caller must be sure that [`paddr`] is mappable of at least [`size`] bytes
/// and readable
/// - The caller must be sure that [`paddr`] is properly aligned for T
/// - The caller must be sure that [`paddr`]..[`paddr`] + [`size`] contains the correct data
/// for T
unsafe fn map_nbytes(paddr: u64, size: usize) -> Result<Self, MappingError> {
let paddr = usize::try_from(paddr).map_err(MappingError::ConversionError)?;
// Page align address and size
let aligned_paddr = paddr & !PAGE_MASK;
let aligned_size = (size + PAGE_MASK) & !PAGE_MASK;
let offset = paddr - aligned_paddr;
assert!(offset < aligned_size);
assert_ne!(size, 0);
let mut aligned_ptr: *mut c_void = core::ptr::null_mut();
// Map the physical address to virtual memory
// SAFETY:Delegated to caller
let ret = unsafe {
// vmm_alloc_physical function accepts a constant reference for outputting a pointer to
// mapped region. Pass mutable reference and silence the clippy warning.
#[allow(clippy::unnecessary_mut_passed)]
vmm_alloc_physical(
vmm_get_kernel_aspace(),
c"rust-setup_data".as_ptr() as _,
aligned_size,
&mut aligned_ptr,
0,
aligned_paddr,
0,
ARCH_MMU_FLAG_CACHED | ARCH_MMU_FLAG_PERM_RO | ARCH_MMU_FLAG_PERM_NO_EXECUTE,
)
};
// Make sure that the region was mapped correctly
if ret != 0 || aligned_ptr.is_null() {
return Err(MappingError::MappingError(ret));
}
// Adjust the pointer to virtual memory back from aligned address to desired offset
// SAFETY: The pointer is within mapped range
let ptr = unsafe { aligned_ptr.add(paddr - aligned_paddr) };
Ok(Self { size, ptr, aligned_ptr, _phantom: Default::default() })
}
}
impl<T: FromBytes> Mapped<T> {
/// Maps T at at the [`paddr`] physical address into virtual memory. If the [`paddr`] is not
/// page aligned, the function will also map the preceding space to a closest page aligned
/// address. Similarly the function will align up the size of [`T`] to page alignment.
///
/// # Safety
///
/// - The caller must be sure that [`paddr`] is mappable of at least sizeof(T) bytes
/// and readable.
/// - The caller must be sure that [`paddr`] is properly aligned for T
/// - The caller must be sure that [`paddr`]..[`paddr`] + sizeof(T) contains the correct
/// data for T
pub unsafe fn map(paddr: u64) -> Result<Self, MappingError> {
// SAFETY:Delegated to caller
Self::map_nbytes(paddr, core::mem::size_of::<T>())
}
}
impl<T: FromBytes> AsRef<T> for Mapped<T> {
fn as_ref(&self) -> &T {
debug_assert!(self.size == core::mem::size_of::<T>());
// SAFETY:[`Self`] created with [`Self::map`] is at least the T size and the alignment
// for T is asserted during the construction of [`Self`]. The bit pattern property is
// asserted by requiring T to be [`FromBytes`].
unsafe { self.ptr.cast::<T>().as_ref().unwrap() }
}
}
impl<T: FromBytes> Mapped<[T]> {
/// Maps `[T; size]` as a slice at at the [`paddr`] physical address into virtual memory
/// If the [`paddr`] is not page aligned, the function will also map the preceding space
/// to a closest page aligned address. Similarly the function will align up the size of
/// `[T; size]` to page alignment.
///
/// # Safety
/// - The caller must be sure that [`paddr`] is mappable of at least [`size`] * sizeof(T)
/// bytes and readable.
/// - The caller must be sure that [`paddr`] is properly aligned for T
/// - The caller must be sure that [`paddr`]..[`paddr`] + [`size`] * sizeof(T) contains
/// the correct data for [T; size]
/// - The [`size`] must not be zero.
pub unsafe fn map_slice(paddr: u64, size: usize) -> Result<Self, MappingError> {
// SAFETY:Delegated to caller
Self::map_nbytes(paddr, size * core::mem::size_of::<T>())
}
}
impl<T: ?Sized + FromBytes> Drop for Mapped<T> {
fn drop(&mut self) {
// Unmap the no longer needed memory region from virtual memory
// SAFETY:: ptr came from vmm_alloc_physical
unsafe { vmm_free_region(vmm_get_kernel_aspace(), self.aligned_ptr as _) };
}
}
impl<T: FromBytes> AsRef<[T]> for Mapped<[T]> {
fn as_ref(&self) -> &[T] {
let n = self.size / core::mem::size_of::<T>();
assert_ne!(n, 0);
// SAFETY: The pointer compes from a successful vmm_alloc_physical call, so it's not null
// and valid. It is mapped as RO making it immutable. The caller of constructor is
// required to be sure that the data under the pointer is correct for [T; n] and properly
// aligned.
unsafe { core::slice::from_raw_parts::<'_, T>(self.ptr.cast::<T>(), n) }
}
}
const BOOT_PARAMS_BOOT_FLAG_OFFSET: usize = 0x1fe;
const BOOT_PARAMS_BOOT_FLAG_MAGIC: u16 = 0xaa55;
const BOOT_PARAMS_HEADER_OFFSET: usize = 0x202;
const BOOT_PARAMS_HEADER_MAGIC: u32 = 0x53726448;
const BOOT_PARAMS_SETUP_DATA_OFFSET: usize = 0x250;
/// Based on crosvm's SETUP_DTB (x86_64/src/lib.rs)
pub const SETUP_DTB: u32 = 2;
/// Based on crosvm's setup_data_hdr (x86_64/src/lib.rs) which is
/// based on https://www.kernel.org/doc/html/latest/arch/x86/boot.html
#[repr(C)]
#[derive(Debug, Clone, Copy, FromBytes, Immutable, KnownLayout)]
struct setup_data_hdr {
next: u64,
type_: u32,
len: u32,
}
/// Error type returned by [`SetupDataIter`] functions
#[derive(Error, Debug)]
pub enum FindSetupDataError {
#[error("failed to map a memory region: {0}")]
MappingError(#[from] MappingError),
#[error("invalid magic in boot params structure")]
InvalidMagic,
#[error("failed to convert a value: {0}")]
ConversionError(#[from] core::num::TryFromIntError),
}
/// Unpacked type and data from [`setup_data_hdr`]
pub struct SetupData {
type_: u32,
data: Mapped<[u8]>,
}
/// Iterator over `setup_data` chain rooted in `boot_params` structure
pub struct SetupDataIter {
next: u64,
}
impl SetupDataIter {
/// Searches for boot_params using second boot argument and then creates a iterator over
/// setup_data chain.
pub fn find() -> Result<Self, FindSetupDataError> {
// SAFETY: lk_boot_args are set in early init and not modified afterwards
let boot_params_addr = unsafe { lk_boot_args[1] };
// Map the boot_params structure
// SAFETY: boot_params struct should be passed by boot loader in second register
let mapped_boot_params = unsafe {
Mapped::<[u8]>::map_slice(
boot_params_addr,
BOOT_PARAMS_SETUP_DATA_OFFSET + core::mem::size_of::<u64>(),
)?
};
let boot_params: &[u8] = mapped_boot_params.as_ref();
// Verify that constant value of boot_flag in boot_params matches
let boot_flag = u16::from_le_bytes(
boot_params[BOOT_PARAMS_BOOT_FLAG_OFFSET..][..2].try_into().unwrap(),
);
if boot_flag != BOOT_PARAMS_BOOT_FLAG_MAGIC {
return Err(FindSetupDataError::InvalidMagic);
}
// Verify that constant value of header in boot_params matches
let header =
u32::from_le_bytes(boot_params[BOOT_PARAMS_HEADER_OFFSET..][..4].try_into().unwrap());
if header != BOOT_PARAMS_HEADER_MAGIC {
return Err(FindSetupDataError::InvalidMagic);
}
// Get the first setup_data_hdr node address in the chain
let next = u64::from_le_bytes(
boot_params[BOOT_PARAMS_SETUP_DATA_OFFSET..][..8].try_into().unwrap(),
);
Ok(Self { next })
}
fn find_next(&mut self) -> Result<Option<SetupData>, FindSetupDataError> {
// Check if the end of chain has been reached
if self.next == 0u64 {
return Ok(None);
}
// Briefly map setup_data_hdr into memory and copy into variable.
// SAFETY:Each setup_data/next address passed using boot_params struct from bootloader
// is expected to be valid.
let mapped_hdr = unsafe { Mapped::<setup_data_hdr>::map(self.next)? };
let hdr: setup_data_hdr = *mapped_hdr.as_ref();
drop(mapped_hdr);
// Calculate data start address
let payload = self.next + u64::try_from(core::mem::size_of::<setup_data_hdr>())?;
// Set the next setup_data_hdr address in the chain
self.next = hdr.next;
// Map the data into virtual memory and return it
// SAFETY: The setup_data pointee is expected to be a valid mappable address and
// size.
let data = unsafe { Mapped::<[u8]>::map_slice(payload, usize::try_from(hdr.len)?)? };
Ok(Some(SetupData { type_: hdr.type_, data }))
}
}
impl Iterator for SetupDataIter {
type Item = Result<SetupData, FindSetupDataError>;
fn next(&mut self) -> Option<Self::Item> {
// Repack the Option and Result
match self.find_next() {
Ok(Some(next)) => Some(Ok(next)),
Ok(None) => None,
Err(err) => {
// Prevent next iterations to avoid dead lock
self.next = 0u64;
Some(Err(err))
}
}
}
}
/// Searches for boot_params structure and returns iterator that yields setup_datas with DTBs
pub fn find_dtbs(
) -> Result<impl Iterator<Item = Result<impl AsRef<[u8]>, FindSetupDataError>>, FindSetupDataError>
{
Ok(SetupDataIter::find()?.filter_map(|setup| match setup {
// Filter out setup_data_hdr that are not DTBs
Ok(setup) if setup.type_ == SETUP_DTB => Some(Ok(setup.data)),
Ok(_) => None,
Err(err) => Some(Err(err)),
}))
}