pvh 0.1.0

Xen's x86/HVM direct boot ABI (PVH).
Documentation
//! PVH start info readers.
//!
//! The start info itself, as well as the module list, the memory map, the
//! module data, and the command lines, are provided as physical addresses.
//!
//! The [`MemMap`] trait converts physical addresses into pointers.
//! [`IdentityMap`] is a [`MemMap`] implementation suitable for systems without
//! paging as well as systems where the physical memory is identity mapped.
//!
//! The readers ([`StartInfoReader`] and [`ModlistEntryReader`]) allow safely
//! reading the data that is provided via their physical addresses.
//!
//! # Examples
//!
//! ```no_run
//! use pvh::start_info::reader::StartInfoReader;
//!
//! # let start_info_paddr = 0x6000;
//! let start_info = unsafe { StartInfoReader::from_paddr_identity(start_info_paddr).unwrap() };
//!
//! println!("Start info:");
//! println!("{start_info:#?}");
//! ```

mod debug;
mod mem_map;

use core::alloc::Layout;
use core::ffi::{CStr, c_char};
use core::{ptr, slice};

pub use self::mem_map::{IdentityMap, MemMap};
use crate::start_info::{MemmapTableEntry, ModlistEntry, StartInfo};

/// A start info reader.
///
/// This struct allows reading the data that is provided as physical addresses
/// via the start info.
///
/// For this, a way of creating pointers from physical addresses is needed
/// ([`MemMap`]), for example [`IdentityMap`].
///
/// # Examples
///
/// ```no_run
/// use pvh::start_info::reader::StartInfoReader;
///
/// # let start_info_paddr = 0x6000;
/// let start_info = unsafe { StartInfoReader::from_paddr_identity(start_info_paddr).unwrap() };
///
/// println!("Start info:");
/// println!("{start_info:#?}");
/// ```
pub struct StartInfoReader<'a, M> {
    // Invariant: The start info contains valid physical addresses and sizes.
    start_info: &'a StartInfo,

    // Invariant: The memory map is valid for `'a`.
    mem_map: M,
}

impl StartInfoReader<'_, IdentityMap> {
    /// Creates a start info reader from a physical address that relies on
    /// identity-mapping.
    ///
    /// This is a convenience method for
    /// <code>[from_paddr]\([IdentityMap])</code>.
    ///
    /// # Safety
    ///
    /// - The structure at `paddr` must be valid. Specifically, the physical
    ///   addresses in the structure's fields must be valid.
    /// - [`IdentityMap`] must be valid for the lifetime of the start info.
    ///
    /// [from_paddr]: Self::from_paddr
    #[inline]
    #[must_use]
    pub unsafe fn from_paddr_identity(paddr: u32) -> Option<Self> {
        // SAFETY: The caller upholds all safety conditions.
        unsafe { Self::from_paddr(paddr, IdentityMap) }
    }
}

impl<'a, M: MemMap> StartInfoReader<'a, M> {
    /// Creates a start info reader from a physical address and a memory map.
    ///
    /// [`from_paddr_identity`] is convenience method for identity-mappings.
    ///
    /// # Safety
    ///
    /// - The structure at `paddr` must be valid. Specifically, the physical
    ///   addresses in the structure's fields must be valid.
    /// - `mem_map` must be valid for the lifetime of the start info. For
    ///   details, see [`MemMap`].
    ///
    /// [`from_paddr_identity`]: Self::from_paddr_identity
    #[must_use]
    pub unsafe fn from_paddr(paddr: u32, mem_map: M) -> Option<Self> {
        if paddr == 0 {
            return None;
        }

        let paddr = usize::try_from(paddr).ok()?;
        let layout = Layout::new::<StartInfo>();

        let start_info = mem_map.ptr(paddr, layout).cast::<StartInfo>();
        // SAFETY: The caller upholds that `paddr` and `mem_map` are valid. We uphold
        // that `layout` is valid.
        let start_info = unsafe { StartInfo::from_ptr(start_info)? };

        Some(Self {
            start_info,
            mem_map,
        })
    }

    /// Returns the underlying raw start info.
    #[inline]
    #[must_use]
    pub fn raw(&self) -> &'a StartInfo {
        self.start_info
    }

    /// Returns a pointer to the array of modules.
    #[must_use]
    fn modlist_vaddr(&self) -> *const ModlistEntry {
        if self.start_info.modlist_paddr == 0 {
            return ptr::null();
        }

        let Ok(paddr) = usize::try_from(self.start_info.modlist_paddr) else {
            return ptr::null();
        };

        let Ok(size) = usize::try_from(self.start_info.nr_modules) else {
            return ptr::null();
        };

        let Ok(layout) = Layout::array::<ModlistEntry>(size) else {
            return ptr::null();
        };

        self.mem_map.ptr(paddr, layout).cast::<ModlistEntry>()
    }

    /// Returns a slice to the array of modules.
    #[must_use]
    fn raw_modlist(&self) -> &'a [ModlistEntry] {
        let ptr = self.modlist_vaddr();

        if ptr.is_null() {
            return &[];
        }

        let Ok(len) = usize::try_from(self.start_info.nr_modules) else {
            return &[];
        };

        // SAFETY: The physical addresses, sizes and memory map are valid by
        // construction.
        unsafe { slice::from_raw_parts(ptr, len) }
    }

    /// Returns an iterator of module list entry readers.
    pub fn modlist(&self) -> impl Iterator<Item = ModlistEntryReader<'a, &M>> {
        let mem_map = &self.mem_map;
        self.raw_modlist()
            .iter()
            .map(move |modlist_entry| ModlistEntryReader {
                modlist_entry,
                mem_map,
            })
    }

    /// Returns a pointer to the command line.
    #[must_use]
    fn cmdline_vaddr(&self) -> *const c_char {
        if self.start_info.cmdline_paddr == 0 {
            return ptr::null();
        }

        let Ok(paddr) = usize::try_from(self.start_info.cmdline_paddr) else {
            return ptr::null();
        };

        let layout = Layout::new::<c_char>();

        self.mem_map.ptr(paddr, layout).cast::<c_char>()
    }

    /// Returns the command line.
    #[must_use]
    pub fn cmdline(&self) -> Option<&'a CStr> {
        let ptr = self.cmdline_vaddr();

        if ptr.is_null() {
            return None;
        }

        // SAFETY: The physical addresses, sizes and memory map are valid by
        // construction.
        let cmdline = unsafe { CStr::from_ptr(ptr) };
        Some(cmdline)
    }

    /// Returns a pointer to the memory map table.
    #[must_use]
    fn memmap_vaddr(&self) -> *const MemmapTableEntry {
        if self.start_info.version < 1 {
            return ptr::null();
        }

        if self.start_info.memmap_paddr == 0 {
            return ptr::null();
        }

        let Ok(paddr) = usize::try_from(self.start_info.memmap_paddr) else {
            return ptr::null();
        };

        let Ok(size) = usize::try_from(self.start_info.memmap_entries) else {
            return ptr::null();
        };

        let Ok(layout) = Layout::array::<MemmapTableEntry>(size) else {
            return ptr::null();
        };

        self.mem_map.ptr(paddr, layout).cast::<MemmapTableEntry>()
    }

    /// Returns the memory map.
    #[must_use]
    pub fn memmap(&self) -> &'a [MemmapTableEntry] {
        let ptr = self.memmap_vaddr();

        if ptr.is_null() {
            return &[];
        }

        let Ok(len) = usize::try_from(self.start_info.memmap_entries) else {
            return &[];
        };

        // SAFETY: The physical addresses, sizes and memory map are valid by
        // construction.
        unsafe { slice::from_raw_parts(ptr, len) }
    }
}

/// A module list entry reader.
///
/// This is returned from [`StartInfoReader::modlist`].
pub struct ModlistEntryReader<'a, M> {
    modlist_entry: &'a ModlistEntry,
    mem_map: M,
}

impl<'a, M: MemMap> ModlistEntryReader<'a, M> {
    /// Returns the underlying raw module list entry.
    #[inline]
    #[must_use]
    pub fn raw(&self) -> &'a ModlistEntry {
        self.modlist_entry
    }

    /// Returns a pointer to the module.
    #[must_use]
    fn vaddr(&self) -> *const u8 {
        let Ok(paddr) = usize::try_from(self.modlist_entry.paddr) else {
            return ptr::null();
        };

        let Ok(size) = usize::try_from(self.modlist_entry.size) else {
            return ptr::null();
        };

        let Ok(layout) = Layout::array::<u8>(size) else {
            return ptr::null();
        };

        self.mem_map.ptr(paddr, layout).cast::<u8>()
    }

    /// Returns a slice of the module.
    #[must_use]
    pub fn as_slice(&self) -> &'a [u8] {
        let ptr = self.vaddr();

        let Ok(len) = usize::try_from(self.modlist_entry.size) else {
            return &[];
        };

        // SAFETY: The physical addresses, sizes and memory map are valid by
        // construction.
        unsafe { slice::from_raw_parts(ptr, len) }
    }

    /// Returns a pointer to the command line.
    #[must_use]
    fn cmdline_vaddr(&self) -> *const c_char {
        let Ok(paddr) = usize::try_from(self.modlist_entry.cmdline_paddr) else {
            return ptr::null();
        };

        let layout = Layout::new::<c_char>();

        self.mem_map.ptr(paddr, layout).cast::<c_char>()
    }

    /// Returns the command line.
    #[must_use]
    pub fn cmdline(&self) -> Option<&'a CStr> {
        let ptr = self.cmdline_vaddr();

        if ptr.is_null() {
            return None;
        }

        // SAFETY: The physical addresses, sizes and memory map are valid by
        // construction.
        let cmdline = unsafe { CStr::from_ptr(ptr) };
        Some(cmdline)
    }
}

#[expect(unused)]
#[cfg(test)]
mod start_info_reader_lifetimes {
    use super::*;

    type Reader<'a> = StartInfoReader<'a, IdentityMap>;

    fn raw(start_info: Reader<'_>) -> &StartInfo {
        start_info.raw()
    }

    fn raw_modlist(start_info: Reader<'_>) -> &[ModlistEntry] {
        start_info.raw_modlist()
    }

    fn first_modlist_as_slice(start_info: Reader<'_>) -> &[u8] {
        start_info.modlist().next().unwrap().as_slice()
    }

    fn cmdline(start_info: Reader<'_>) -> &CStr {
        start_info.cmdline().unwrap()
    }

    fn memmap(start_info: Reader<'_>) -> &[MemmapTableEntry] {
        start_info.memmap()
    }
}

#[expect(unused)]
#[cfg(test)]
mod modlist_entry_reader_lifetimes {
    use super::*;

    type Reader<'a> = ModlistEntryReader<'a, IdentityMap>;

    fn raw(modlist_entry: Reader<'_>) -> &ModlistEntry {
        modlist_entry.raw()
    }

    fn as_slice(modlist_entry: Reader<'_>) -> &[u8] {
        modlist_entry.as_slice()
    }

    fn cmdline(modlist_entry: Reader<'_>) -> &CStr {
        modlist_entry.cmdline().unwrap()
    }
}