xref: /cloud-hypervisor/vmm/src/igvm/loader.rs (revision 88a9f799449c04180c6b9a21d3b9c0c4b57e2bd6)
17030b15eSMuminul Islam // SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause
27030b15eSMuminul Islam //
37030b15eSMuminul Islam // Copyright © 2023, Microsoft Corporation
47030b15eSMuminul Islam //
57030b15eSMuminul Islam use range_map_vec::{Entry, RangeMap};
67030b15eSMuminul Islam use thiserror::Error;
77030b15eSMuminul Islam use vm_memory::bitmap::AtomicBitmap;
87030b15eSMuminul Islam use vm_memory::{
97030b15eSMuminul Islam     Bytes, GuestAddress, GuestAddressSpace, GuestMemory, GuestMemoryAtomic, GuestMemoryMmap,
107030b15eSMuminul Islam     GuestMemoryRegion,
117030b15eSMuminul Islam };
127030b15eSMuminul Islam 
13*88a9f799SRob Bradford use crate::igvm::{BootPageAcceptance, StartupMemoryType, HV_PAGE_SIZE};
14*88a9f799SRob Bradford 
157030b15eSMuminul Islam /// Structure to hold the guest memory info/layout to check
167030b15eSMuminul Islam /// the if the memory is accepted within the layout.
177030b15eSMuminul Islam /// Adds up the total bytes written to the guest memory
187030b15eSMuminul Islam pub struct Loader {
197030b15eSMuminul Islam     memory: GuestMemoryAtomic<GuestMemoryMmap<AtomicBitmap>>,
207030b15eSMuminul Islam     accepted_ranges: RangeMap<u64, BootPageAcceptance>,
217030b15eSMuminul Islam     bytes_written: u64,
227030b15eSMuminul Islam }
237030b15eSMuminul Islam 
247030b15eSMuminul Islam #[derive(Debug)]
257030b15eSMuminul Islam pub struct ImportRegion {
267030b15eSMuminul Islam     pub page_base: u64,
277030b15eSMuminul Islam     pub page_count: u64,
287030b15eSMuminul Islam     pub acceptance: BootPageAcceptance,
297030b15eSMuminul Islam }
307030b15eSMuminul Islam 
317030b15eSMuminul Islam #[derive(Debug, Error)]
327030b15eSMuminul Islam pub enum Error {
337030b15eSMuminul Islam     #[error("overlaps with existing import region {0:?}")]
347030b15eSMuminul Islam     OverlapsExistingRegion(ImportRegion),
357030b15eSMuminul Islam     #[error("memory unavailable")]
367030b15eSMuminul Islam     MemoryUnavailable,
377030b15eSMuminul Islam     #[error("failed to import pages")]
387030b15eSMuminul Islam     ImportPagesFailed,
397030b15eSMuminul Islam     #[error("invalid vp context memory")]
407030b15eSMuminul Islam     InvalidVpContextMemory(&'static str),
417030b15eSMuminul Islam     #[error("data larger than imported region")]
427030b15eSMuminul Islam     DataTooLarge,
437030b15eSMuminul Islam }
447030b15eSMuminul Islam 
457030b15eSMuminul Islam impl Loader {
new(memory: GuestMemoryAtomic<GuestMemoryMmap<AtomicBitmap>>) -> Loader467030b15eSMuminul Islam     pub fn new(memory: GuestMemoryAtomic<GuestMemoryMmap<AtomicBitmap>>) -> Loader {
477030b15eSMuminul Islam         Loader {
487030b15eSMuminul Islam             memory,
497030b15eSMuminul Islam             accepted_ranges: RangeMap::new(),
507030b15eSMuminul Islam             bytes_written: 0,
517030b15eSMuminul Islam         }
527030b15eSMuminul Islam     }
537030b15eSMuminul Islam 
547030b15eSMuminul Islam     /// Accept a new page range with a given acceptance into the map of accepted ranges.
accept_new_range( &mut self, page_base: u64, page_count: u64, acceptance: BootPageAcceptance, ) -> Result<(), Error>557030b15eSMuminul Islam     pub fn accept_new_range(
567030b15eSMuminul Islam         &mut self,
577030b15eSMuminul Islam         page_base: u64,
587030b15eSMuminul Islam         page_count: u64,
597030b15eSMuminul Islam         acceptance: BootPageAcceptance,
607030b15eSMuminul Islam     ) -> Result<(), Error> {
617030b15eSMuminul Islam         let page_end = page_base + page_count - 1;
627030b15eSMuminul Islam         match self.accepted_ranges.entry(page_base..=page_end) {
637030b15eSMuminul Islam             Entry::Overlapping(entry) => {
647030b15eSMuminul Islam                 let &(overlap_start, overlap_end, overlap_acceptance) = entry.get();
657030b15eSMuminul Islam 
667030b15eSMuminul Islam                 Err(Error::OverlapsExistingRegion(ImportRegion {
677030b15eSMuminul Islam                     page_base: overlap_start,
687030b15eSMuminul Islam                     page_count: overlap_end - overlap_start + 1,
697030b15eSMuminul Islam                     acceptance: overlap_acceptance,
707030b15eSMuminul Islam                 }))
717030b15eSMuminul Islam             }
727030b15eSMuminul Islam             Entry::Vacant(entry) => {
737030b15eSMuminul Islam                 entry.insert(acceptance);
747030b15eSMuminul Islam                 Ok(())
757030b15eSMuminul Islam             }
767030b15eSMuminul Islam         }
777030b15eSMuminul Islam     }
787030b15eSMuminul Islam 
import_pages( &mut self, page_base: u64, page_count: u64, acceptance: BootPageAcceptance, data: &[u8], ) -> Result<(), Error>797030b15eSMuminul Islam     pub fn import_pages(
807030b15eSMuminul Islam         &mut self,
817030b15eSMuminul Islam         page_base: u64,
827030b15eSMuminul Islam         page_count: u64,
837030b15eSMuminul Islam         acceptance: BootPageAcceptance,
847030b15eSMuminul Islam         data: &[u8],
857030b15eSMuminul Islam     ) -> Result<(), Error> {
869b84c6c3SMuminul Islam         // Once we are here at this point, we have a page with
879b84c6c3SMuminul Islam         // some data or empty, empty does not mean there is no data,
889b84c6c3SMuminul Islam         // it rather means it's full of zeros. We can skip writing the
899b84c6c3SMuminul Islam         // data as the guest page is already zeroed. So we return with
909b84c6c3SMuminul Islam         // updating the bytes_written variable
919b84c6c3SMuminul Islam         if data.is_empty() {
929b84c6c3SMuminul Islam             self.bytes_written += page_count * HV_PAGE_SIZE;
939b84c6c3SMuminul Islam             return Ok(());
949b84c6c3SMuminul Islam         }
957030b15eSMuminul Islam         // Page count must be larger or equal to data.
967030b15eSMuminul Islam         if page_count * HV_PAGE_SIZE < data.len() as u64 {
977030b15eSMuminul Islam             return Err(Error::DataTooLarge);
987030b15eSMuminul Islam         }
997030b15eSMuminul Islam 
1007030b15eSMuminul Islam         // Track accepted ranges for duplicate imports.
1017030b15eSMuminul Islam         self.accept_new_range(page_base, page_count, acceptance)?;
1027030b15eSMuminul Islam 
1037030b15eSMuminul Islam         let bytes_written = self
1047030b15eSMuminul Islam             .memory
1057030b15eSMuminul Islam             .memory()
1067030b15eSMuminul Islam             .write(data, GuestAddress(page_base * HV_PAGE_SIZE))
1077030b15eSMuminul Islam             .map_err(|_e| {
1087030b15eSMuminul Islam                 debug!("Importing pages failed due to MemoryError");
1097030b15eSMuminul Islam                 Error::MemoryUnavailable
1107030b15eSMuminul Islam             })?;
1119b84c6c3SMuminul Islam 
1129b84c6c3SMuminul Islam         // A page could be partially filled and the rest of the content is zero.
1139b84c6c3SMuminul Islam         // Our IGVM generation tool only fills data here if there is some data without zeros.
1149b84c6c3SMuminul Islam         // Rest of them are padded. We only write data without padding and compare whether we
1159b84c6c3SMuminul Islam         // complete writing the buffer content. Still it's a full page and update the variable
1169b84c6c3SMuminul Islam         // with length of the page.
1179b84c6c3SMuminul Islam         if bytes_written != data.len() {
1187030b15eSMuminul Islam             return Err(Error::ImportPagesFailed);
1197030b15eSMuminul Islam         }
1209b84c6c3SMuminul Islam         self.bytes_written += page_count * HV_PAGE_SIZE;
1217030b15eSMuminul Islam         Ok(())
1227030b15eSMuminul Islam     }
1237030b15eSMuminul Islam 
verify_startup_memory_available( &mut self, page_base: u64, page_count: u64, memory_type: StartupMemoryType, ) -> Result<(), Error>1247030b15eSMuminul Islam     pub fn verify_startup_memory_available(
1257030b15eSMuminul Islam         &mut self,
1267030b15eSMuminul Islam         page_base: u64,
1277030b15eSMuminul Islam         page_count: u64,
1287030b15eSMuminul Islam         memory_type: StartupMemoryType,
1297030b15eSMuminul Islam     ) -> Result<(), Error> {
1307030b15eSMuminul Islam         if memory_type != StartupMemoryType::Ram {
1317030b15eSMuminul Islam             return Err(Error::MemoryUnavailable);
1327030b15eSMuminul Islam         }
1337030b15eSMuminul Islam 
1347030b15eSMuminul Islam         let mut memory_found = false;
1357030b15eSMuminul Islam 
1367030b15eSMuminul Islam         for range in self.memory.memory().iter() {
1377030b15eSMuminul Islam             // Today, the memory layout only describes normal ram and mmio. Thus the memory
1387030b15eSMuminul Islam             // request must live completely within a single range, since any gaps are mmio.
1397030b15eSMuminul Islam             let base_address = page_base * HV_PAGE_SIZE;
1407030b15eSMuminul Islam             let end_address = base_address + (page_count * HV_PAGE_SIZE) - 1;
1417030b15eSMuminul Islam 
1427030b15eSMuminul Islam             if base_address >= range.start_addr().0 && base_address < range.last_addr().0 {
1437030b15eSMuminul Islam                 if end_address > range.last_addr().0 {
1447030b15eSMuminul Islam                     debug!("startup memory end bigger than the current range");
1457030b15eSMuminul Islam                     return Err(Error::MemoryUnavailable);
1467030b15eSMuminul Islam                 }
1477030b15eSMuminul Islam 
1487030b15eSMuminul Islam                 memory_found = true;
1497030b15eSMuminul Islam             }
1507030b15eSMuminul Islam         }
1517030b15eSMuminul Islam 
1527030b15eSMuminul Islam         if memory_found {
1537030b15eSMuminul Islam             Ok(())
1547030b15eSMuminul Islam         } else {
1557030b15eSMuminul Islam             debug!("no valid memory range available for startup memory verify");
1567030b15eSMuminul Islam             Err(Error::MemoryUnavailable)
1577030b15eSMuminul Islam         }
1587030b15eSMuminul Islam     }
1597030b15eSMuminul Islam }
160