Skip to main content

openhcl_boot/host_params/dt/
mod.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Parse partition info using the IGVM device tree parameter.
5
6use super::PartitionInfo;
7use super::shim_params::ShimParams;
8use crate::cmdline::BootCommandLineOptions;
9use crate::cmdline::SidecarOptions;
10use crate::host_params::COMMAND_LINE_SIZE;
11use crate::host_params::MAX_CPU_COUNT;
12use crate::host_params::MAX_ENTROPY_SIZE;
13use crate::host_params::MAX_NUMA_NODES;
14use crate::host_params::MAX_PARTITION_RAM_RANGES;
15use crate::host_params::MAX_VTL2_RAM_RANGES;
16use crate::host_params::dt::dma_hint::pick_private_pool_size;
17use crate::host_params::mmio::select_vtl2_mmio_range;
18use crate::host_params::shim_params::IsolationType;
19use crate::memory::AddressSpaceManager;
20use crate::memory::AddressSpaceManagerBuilder;
21use crate::memory::AllocationPolicy;
22use crate::memory::AllocationType;
23use crate::single_threaded::OffStackRef;
24use crate::single_threaded::off_stack;
25use arrayvec::ArrayString;
26use arrayvec::ArrayVec;
27use bump_alloc::ALLOCATOR;
28use core::cmp::max;
29use core::fmt::Write;
30use host_fdt_parser::MemoryAllocationMode;
31use host_fdt_parser::MemoryEntry;
32use host_fdt_parser::ParsedDeviceTree;
33use host_fdt_parser::VmbusInfo;
34use hvdef::HV_PAGE_SIZE;
35use igvm_defs::MemoryMapEntryType;
36use loader_defs::paravisor::CommandLinePolicy;
37use loader_defs::shim::MemoryVtlType;
38use loader_defs::shim::PersistedStateHeader;
39use memory_range::MemoryRange;
40use memory_range::subtract_ranges;
41use memory_range::walk_ranges;
42use thiserror::Error;
43use zerocopy::FromBytes;
44
45mod bump_alloc;
46mod dma_hint;
47
48/// Errors when reading the host device tree.
49#[derive(Debug, Error)]
50pub enum DtError {
51    /// Host did not provide a device tree.
52    #[error("no device tree provided by host")]
53    NoDeviceTree,
54    /// Invalid device tree.
55    #[error("host provided device tree is invalid")]
56    DeviceTree(#[source] host_fdt_parser::Error<'static>),
57    /// PartitionInfo's command line is too small to write the parsed legacy
58    /// command line.
59    #[error("commandline storage is too small to write the parsed command line")]
60    CommandLineSize,
61    /// Device tree did not contain a vmbus node for VTL2.
62    #[error("device tree did not contain a vmbus node for VTL2")]
63    Vtl2Vmbus,
64    /// Device tree did not contain a vmbus node for VTL0.
65    #[error("device tree did not contain a vmbus node for VTL0")]
66    Vtl0Vmbus,
67    /// Host provided high MMIO range is insufficient to cover VTL0 and VTL2.
68    #[error("host provided high MMIO range is insufficient to cover VTL0 and VTL2")]
69    NotEnoughVtl0Mmio,
70    /// Host provided MMIO range is insufficient to cover VTL2.
71    #[error("host provided MMIO range is insufficient to cover VTL2")]
72    NotEnoughVtl2Mmio,
73}
74
75/// Allocate the private pool across NUMA nodes.
76///
77/// By default, tries to allocate the entire pool on NUMA node 0 (preserving
78/// previous behavior). If that fails, or if `force_numa_split` is true, the
79/// pool is split evenly across all available NUMA nodes (one range per node).
80fn allocate_private_pool(
81    address_space: &mut AddressSpaceManager,
82    vtl2_ram: &[MemoryEntry],
83    pool_size_bytes: u64,
84    force_numa_split: bool,
85    enable_vtl2_gpa_pool: crate::cmdline::Vtl2GpaPoolConfig,
86    device_dma_page_count: Option<u64>,
87    vp_count: usize,
88    mem_size: u64,
89) {
90    // Try allocating the entire pool on node 0 first. We do this to maintain
91    // compatibility with older openhcl_boot images that do not understand how
92    // to handle a split private pool, and to maintain previous behavior where
93    // the pool was completely allocated on numa node 0.
94    //
95    // Allocate from high memory downward to avoid overlapping any used ranges
96    // in low memory when openhcl's usage gets bigger, as otherwise the
97    // used_range by the bootshim could overlap the pool range chosen when
98    // servicing to a new image.
99    if !force_numa_split {
100        if let Some(pool) = address_space.allocate(
101            Some(0),
102            pool_size_bytes,
103            AllocationType::GpaPool,
104            AllocationPolicy::HighMemory,
105        ) {
106            log::info!("allocated VTL2 pool at {:#x?}", pool.range);
107            return;
108        }
109        log::info!("node 0 cannot fit full pool, splitting across NUMA nodes");
110    } else {
111        log::info!("forcing VTL2 pool NUMA split across nodes");
112    }
113
114    // Enumerate unique NUMA nodes from VTL2 RAM.
115    //
116    // FUTURE: Handle cases where the are CPU only or RAM only numa nodes.
117    let mut numa_nodes = off_stack!(ArrayVec<u32, MAX_NUMA_NODES>, ArrayVec::new_const());
118    for entry in vtl2_ram.iter() {
119        match numa_nodes.binary_search(&entry.vnode) {
120            Ok(_) => {}
121            Err(index) => {
122                numa_nodes.insert(index, entry.vnode);
123            }
124        }
125    }
126
127    let num_nodes = numa_nodes.len() as u64;
128    // Split the per node size to page size aligned chunks, and give the
129    // remainder to the last node.
130    let per_node_size = (pool_size_bytes / num_nodes) & !(HV_PAGE_SIZE - 1);
131    let last_node_size = pool_size_bytes - per_node_size * (num_nodes - 1);
132    let mut remaining = pool_size_bytes;
133
134    // If per-node-size is zero, we're in some strange configuration. We should
135    // have been able to allocate this from a single node, as this would mean
136    // the number of nodes is larger than the number of pages requested for the
137    // pool, so fail explicitly.
138    if per_node_size == 0 {
139        panic!(
140            "cannot split VTL2 pool of size {pool_size_bytes:#x} bytes across \
141            {num_nodes} nodes, per node size {per_node_size:#x} bytes; \
142            enable_vtl2_gpa_pool={enable_vtl2_gpa_pool:?}, \
143            device_dma_page_count={device_dma_page_count:#x?}, \
144            vp_count={vp_count}, mem_size={mem_size:#x}"
145        );
146    }
147
148    for (i, vnode) in numa_nodes.iter().enumerate() {
149        if remaining == 0 {
150            break;
151        }
152
153        let is_last = i == numa_nodes.len() - 1;
154        let alloc_size = if is_last {
155            last_node_size
156        } else {
157            per_node_size
158        };
159
160        // Make sure to allocate high memory downward, for the same reason as
161        // described in the numa 0 case.
162        match address_space.allocate(
163            Some(*vnode),
164            alloc_size,
165            AllocationType::GpaPool,
166            AllocationPolicy::HighMemory,
167        ) {
168            Some(pool) => {
169                remaining -= pool.range.len();
170                log::info!(
171                    "allocated VTL2 pool on node {} at {:#x?}",
172                    vnode,
173                    pool.range
174                );
175            }
176            None => {
177                let mut free_ranges = off_stack!(ArrayString<2048>, ArrayString::new_const());
178                for node in numa_nodes.iter() {
179                    for range in address_space.free_ranges(*node) {
180                        if write!(
181                            free_ranges,
182                            "n{}:[{:#x?}, {:#x?}) ",
183                            node,
184                            range.start(),
185                            range.end()
186                        )
187                        .is_err()
188                        {
189                            let _ = write!(free_ranges, "...");
190                            break;
191                        }
192                    }
193                }
194                let highest_numa_node = vtl2_ram.iter().map(|e| e.vnode).max().unwrap_or(0);
195                panic!(
196                    "failed to allocate VTL2 pool on node {vnode}: \
197                     need {alloc_size:#x} bytes, pool total {pool_size_bytes:#x} bytes \
198                     (enable_vtl2_gpa_pool={enable_vtl2_gpa_pool:?}, \
199                     device_dma_page_count={device_dma_page_count:#x?}, \
200                     vp_count={vp_count}, mem_size={mem_size:#x}), \
201                     highest_numa_node={highest_numa_node}, \
202                     free_ranges=[ {}]",
203                    free_ranges.as_str()
204                );
205            }
206        }
207    }
208
209    assert_eq!(
210        remaining, 0,
211        "pool allocation arithmetic error: {remaining:#x} bytes unallocated"
212    );
213}
214
215/// Allocate VTL2 ram from the partition's memory map.
216fn allocate_vtl2_ram(
217    params: &ShimParams,
218    partition_memory_map: &[MemoryEntry],
219    ram_size: Option<u64>,
220) -> OffStackRef<'static, impl AsRef<[MemoryEntry]> + use<>> {
221    // First, calculate how many numa nodes there are by looking at unique numa
222    // nodes in the memory map.
223    let mut numa_nodes = off_stack!(ArrayVec<u32, MAX_NUMA_NODES>, ArrayVec::new_const());
224
225    for entry in partition_memory_map.iter() {
226        match numa_nodes.binary_search(&entry.vnode) {
227            Ok(_) => {}
228            Err(index) => {
229                numa_nodes.insert(index, entry.vnode);
230            }
231        }
232    }
233
234    let numa_node_count = numa_nodes.len();
235
236    let vtl2_size = if let Some(ram_size) = ram_size {
237        if ram_size < params.memory_size {
238            panic!(
239                "host provided vtl2 ram size {:x} is smaller than measured size {:x}",
240                ram_size, params.memory_size
241            );
242        }
243        max(ram_size, params.memory_size)
244    } else {
245        params.memory_size
246    };
247
248    // Next, calculate the amount of memory that needs to be allocated per NUMA
249    // node. The lower VTL permission bitmaps require RAM boundaries to be
250    // aligned to one bitmap byte, which represents eight pages.
251    const ALIGNMENT_GRANULARITY: u64 = HV_PAGE_SIZE * 8;
252    let ram_per_node = (vtl2_size / numa_node_count as u64).next_multiple_of(ALIGNMENT_GRANULARITY);
253
254    // Seed the remaining allocation list with the memory required per node.
255    let mut memory_per_node = off_stack!(ArrayVec<u64, MAX_NUMA_NODES>, ArrayVec::new_const());
256    memory_per_node.extend((0..numa_node_count).map(|_| 0));
257    for entry in partition_memory_map.iter() {
258        memory_per_node[entry.vnode as usize] = ram_per_node;
259    }
260
261    // The range the IGVM file was loaded into is special - it is already
262    // counted as "allocated". This may have been split across different numa
263    // nodes. Walk the used range, add it to vtl2 ram, and subtract it from the
264    // used ranges.
265    let mut vtl2_ram = off_stack!(ArrayVec<MemoryEntry, MAX_NUMA_NODES>, ArrayVec::new_const());
266    let mut free_memory_after_vtl2 = off_stack!(ArrayVec<MemoryEntry, 1024>, ArrayVec::new_const());
267    let file_memory_range = MemoryRange::new(
268        params.memory_start_address..(params.memory_start_address + params.memory_size),
269    );
270
271    for (range, result) in walk_ranges(
272        [(file_memory_range, ())],
273        partition_memory_map.iter().map(|e| (e.range, e)),
274    ) {
275        match result {
276            memory_range::RangeWalkResult::Right(entry) => {
277                // Add this entry to the free list.
278                free_memory_after_vtl2.push(MemoryEntry {
279                    range,
280                    mem_type: entry.mem_type,
281                    vnode: entry.vnode,
282                });
283            }
284            memory_range::RangeWalkResult::Both(_, entry) => {
285                // Add this entry to the vtl2 ram list.
286                vtl2_ram.push(MemoryEntry {
287                    range,
288                    mem_type: entry.mem_type,
289                    vnode: entry.vnode,
290                });
291            }
292            memory_range::RangeWalkResult::Left(_) => {
293                panic!("used file range {range:#x?} is not reported as ram by host memmap")
294            }
295            // Ranges in neither are ignored.
296            memory_range::RangeWalkResult::Neither => {}
297        }
298    }
299
300    // Now remove ranges from the free list that were part of the initial launch
301    // context.
302    let mut free_memory = off_stack!(ArrayVec<MemoryEntry, 1024>, ArrayVec::new_const());
303    for (range, result) in walk_ranges(
304        params
305            .imported_regions()
306            .filter_map(|(range, _preaccepted)| {
307                if !file_memory_range.contains(&range) {
308                     // There should be no overlap - either the preaccepted range
309                    // is exclusively covered by the preaccpted VTL2 range or it
310                    // is not.
311                    assert!(!file_memory_range.overlaps(&range), "imported range {range:#x?} overlaps vtl2 range and is not fully contained within vtl2 range");
312                    Some((range, ()))
313                } else {
314                    None
315                }
316            }),
317        free_memory_after_vtl2.iter().map(|e| (e.range, e)),
318    ) {
319        match result {
320            memory_range::RangeWalkResult::Right(entry) => {
321                free_memory.push(MemoryEntry {
322                    range,
323                    mem_type: entry.mem_type,
324                    vnode: entry.vnode,
325                });
326            }
327            memory_range::RangeWalkResult::Left(_) => {
328                // On TDX, the reset vector page is not reported as ram by the
329                // host, but is preaccepted. Ignore it.
330                #[cfg(target_arch = "x86_64")]
331                if params.isolation_type == IsolationType::Tdx && range.start_4k_gpn() == 0xFFFFF && range.len() == 0x1000 {
332                    continue;
333                }
334
335                panic!("launch context range {range:#x?} is not reported as ram by host memmap")
336            }
337            memory_range::RangeWalkResult::Both(_, _) => {
338                // Range was part of the preaccepted import, is not free to
339                // allocate additional VTL2 ram from.
340            }
341            // Ranges in neither are ignored.
342            memory_range::RangeWalkResult::Neither => {}
343        }
344    }
345
346    // Subtract the used ranges from vtl2_ram
347    for entry in vtl2_ram.iter() {
348        let mem_req = &mut memory_per_node[entry.vnode as usize];
349
350        if entry.range.len() > *mem_req {
351            // TODO: Today if a used range is larger than the mem required, we
352            // just subtract that numa range to zero. Should we instead subtract
353            // from other numa nodes equally for over allocation?
354            log::warn!(
355                "entry {entry:?} is larger than required {mem_req} for vnode {}",
356                entry.vnode
357            );
358            *mem_req = 0;
359        } else {
360            *mem_req -= entry.range.len();
361        }
362    }
363
364    // Allocate remaining memory per node required.
365    for (node, required_mem) in memory_per_node.iter().enumerate() {
366        let mut required_mem = *required_mem;
367        if required_mem == 0 {
368            continue;
369        }
370
371        // Start allocation from the top of the free list, which is high memory
372        // in reverse order.
373        for entry in free_memory.iter_mut().rev() {
374            if entry.vnode == node as u32 && !entry.range.is_empty() {
375                assert!(required_mem != 0);
376                let bytes_to_allocate = core::cmp::min(entry.range.len(), required_mem);
377
378                // Allocate top down from the range. Round the allocation start
379                // down so that any range left for VTL0 ends on an eight-page
380                // bitmap boundary. This can allocate up to seven extra pages.
381                let allocation_start =
382                    (entry.range.end() - bytes_to_allocate) & !(ALIGNMENT_GRANULARITY - 1);
383                let offset = allocation_start
384                    .saturating_sub(entry.range.start())
385                    .min(entry.range.len());
386                let (remaining, alloc) = MemoryRange::split_at_offset(&entry.range, offset);
387
388                entry.range = remaining;
389                vtl2_ram.push(MemoryEntry {
390                    range: alloc,
391                    mem_type: entry.mem_type,
392                    vnode: node as u32,
393                });
394
395                required_mem = required_mem.saturating_sub(alloc.len());
396
397                // Stop allocating if we're done allocating.
398                if required_mem == 0 {
399                    break;
400                }
401            }
402        }
403
404        if required_mem != 0 {
405            // TODO: Handle fallback allocations on other numa nodes when a node
406            // is exhausted.
407            panic!(
408                "failed to allocate {required_mem:#x} for vnode {node:#x}, no memory remaining for vnode"
409            );
410        }
411    }
412
413    // Sort VTL2 ram as we may have allocated from different places.
414    vtl2_ram.sort_unstable_by_key(|e| e.range.start());
415
416    vtl2_ram
417}
418
419/// Parse VTL2 ram from host provided ranges.
420fn parse_host_vtl2_ram(
421    params: &ShimParams,
422    memory: &[MemoryEntry],
423) -> OffStackRef<'static, impl AsRef<[MemoryEntry]> + use<>> {
424    // If no VTL2 protectable ram was provided by the host, use the build time
425    // value encoded in ShimParams.
426    let mut vtl2_ram = off_stack!(ArrayVec<MemoryEntry, MAX_NUMA_NODES>, ArrayVec::new_const());
427    if params.isolation_type.is_hardware_isolated() {
428        // Hardware isolated VMs use the size hint by the host, but use the base
429        // address encoded in the file.
430        let vtl2_size = memory.iter().fold(0, |acc, entry| {
431            if entry.mem_type == MemoryMapEntryType::VTL2_PROTECTABLE {
432                acc + entry.range.len()
433            } else {
434                acc
435            }
436        });
437
438        log::info!(
439            "host provided vtl2 ram size is {:x}, measured size is {:x}",
440            vtl2_size,
441            params.memory_size
442        );
443
444        let vtl2_size = max(vtl2_size, params.memory_size);
445        vtl2_ram.push(MemoryEntry {
446            range: MemoryRange::new(
447                params.memory_start_address..(params.memory_start_address + vtl2_size),
448            ),
449            mem_type: MemoryMapEntryType::VTL2_PROTECTABLE,
450            vnode: 0,
451        });
452    } else {
453        for &entry in memory
454            .iter()
455            .filter(|entry| entry.mem_type == MemoryMapEntryType::VTL2_PROTECTABLE)
456        {
457            vtl2_ram.push(entry);
458        }
459    }
460
461    if vtl2_ram.is_empty() {
462        log::info!("using measured vtl2 ram");
463        vtl2_ram.push(MemoryEntry {
464            range: MemoryRange::try_new(
465                params.memory_start_address..(params.memory_start_address + params.memory_size),
466            )
467            .expect("range is valid"),
468            mem_type: MemoryMapEntryType::VTL2_PROTECTABLE,
469            vnode: 0,
470        });
471    }
472
473    vtl2_ram
474}
475
476fn init_heap(params: &ShimParams) {
477    // Initialize the temporary heap.
478    //
479    // This is only to be enabled for mesh decode.
480    //
481    // SAFETY: The heap range is reserved at file build time, and is
482    // guaranteed to be unused by anything else.
483    unsafe {
484        ALLOCATOR.init(params.heap);
485    }
486}
487
488type ParsedDt =
489    ParsedDeviceTree<MAX_PARTITION_RAM_RANGES, MAX_CPU_COUNT, COMMAND_LINE_SIZE, MAX_ENTROPY_SIZE>;
490
491/// Add common ranges to [`AddressSpaceManagerBuilder`] regardless if creating
492/// topology from the host or from saved state.
493fn add_common_ranges<'a, I: Iterator<Item = MemoryRange>>(
494    params: &ShimParams,
495    mut builder: AddressSpaceManagerBuilder<'a, I>,
496) -> AddressSpaceManagerBuilder<'a, I> {
497    // Add the log buffer which is always present.
498    builder = builder.with_log_buffer(params.log_buffer);
499
500    if params.vtl2_reserved_region_size != 0 {
501        builder = builder.with_reserved_range(MemoryRange::new(
502            params.vtl2_reserved_region_start
503                ..(params.vtl2_reserved_region_start + params.vtl2_reserved_region_size),
504        ));
505    }
506
507    if params.sidecar_size != 0 {
508        builder = builder.with_sidecar_image(MemoryRange::new(
509            params.sidecar_base..(params.sidecar_base + params.sidecar_size),
510        ));
511    }
512
513    builder
514}
515
516#[derive(Debug, PartialEq, Eq)]
517struct PartitionTopology {
518    vtl2_ram: &'static [MemoryEntry],
519    vtl0_mmio: ArrayVec<MemoryRange, 2>,
520    vtl2_mmio: ArrayVec<MemoryRange, 2>,
521    memory_allocation_mode: MemoryAllocationMode,
522}
523
524/// State derived while constructing the partition topology
525/// from persisted state.
526#[derive(Debug, PartialEq, Eq)]
527struct PersistedPartitionTopology {
528    topology: PartitionTopology,
529    sidecar_excluded_cpus: &'static [u32],
530}
531
532// Calculate the default mmio size for VTL2 when not specified by the host.
533//
534// This is half of the high mmio gap size, rounded down, with a minimum of 128
535// MB and a maximum of 1 GB.
536fn calculate_default_mmio_size(parsed: &ParsedDt) -> Result<u64, DtError> {
537    const MINIMUM_MMIO_SIZE: u64 = 128 * (1 << 20);
538    const MAXIMUM_MMIO_SIZE: u64 = 1 << 30;
539    let half_high_gap = parsed.vmbus_vtl0.as_ref().ok_or(DtError::Vtl0Vmbus)?.mmio[1].len() / 2;
540    Ok(half_high_gap.clamp(MINIMUM_MMIO_SIZE, MAXIMUM_MMIO_SIZE))
541}
542
543/// Read topology from the host provided device tree.
544fn topology_from_host_dt(
545    params: &ShimParams,
546    parsed: &ParsedDt,
547    options: &BootCommandLineOptions,
548    address_space: &mut AddressSpaceManager,
549) -> Result<PartitionTopology, DtError> {
550    log::info!("reading topology from host device tree");
551
552    let mut vtl2_ram =
553        off_stack!(ArrayVec<MemoryEntry, MAX_VTL2_RAM_RANGES>, ArrayVec::new_const());
554
555    // TODO: Decide if isolated guests always use VTL2 allocation mode.
556
557    let memory_allocation_mode = parsed.memory_allocation_mode;
558    match memory_allocation_mode {
559        MemoryAllocationMode::Host => {
560            vtl2_ram
561                .try_extend_from_slice(parse_host_vtl2_ram(params, &parsed.memory).as_ref())
562                .expect("vtl2 ram should only be 64 big");
563        }
564        MemoryAllocationMode::Vtl2 {
565            memory_size,
566            mmio_size: _,
567        } => {
568            vtl2_ram
569                .try_extend_from_slice(
570                    allocate_vtl2_ram(params, &parsed.memory, memory_size).as_ref(),
571                )
572                .expect("vtl2 ram should only be 64 big");
573        }
574    }
575
576    // The host is responsible for allocating MMIO ranges for non-isolated
577    // guests when it also provides the ram VTL2 should use.
578    //
579    // For isolated guests, or when VTL2 has been asked to carve out its own
580    // memory, first check if the host provided a VTL2 mmio range. If so, the
581    // mmio range must be large enough. Otherwise, choose to carve out a range
582    // from the VTL0 allotment.
583    let (vtl0_mmio, vtl2_mmio) = if params.isolation_type != IsolationType::None
584        || matches!(
585            parsed.memory_allocation_mode,
586            MemoryAllocationMode::Vtl2 { .. }
587        ) {
588        // Decide the amount of mmio VTL2 should allocate, which is different
589        // depending on the heuristic used.
590        //
591        // On a newer host where a vtl2 mmio range is provided inside the
592        // vmbus_vtl2 device tree node, use the size provided by the host inside
593        // the openhcl node for memory allocation mode.
594        //
595        // If the host did not provide a vtl2 mmio range, then use the maximum
596        // of the host provided value inside the openhcl node and the calculated
597        // default.
598        let host_provided_size = match parsed.memory_allocation_mode {
599            MemoryAllocationMode::Vtl2 { mmio_size, .. } => mmio_size.unwrap_or(0),
600            _ => 0,
601        };
602        let vmbus_vtl2 = parsed.vmbus_vtl2.as_ref().ok_or(DtError::Vtl2Vmbus)?;
603        let vmbus_vtl2_mmio_size = vmbus_vtl2.mmio.iter().map(|r| r.len()).sum::<u64>();
604        let mmio_size = if vmbus_vtl2_mmio_size != 0 {
605            host_provided_size
606        } else {
607            max(host_provided_size, calculate_default_mmio_size(parsed)?)
608        };
609
610        log::info!("allocating vtl2 mmio size {mmio_size:#x} bytes");
611        log::info!("host provided vtl2 mmio ranges are {vmbus_vtl2_mmio_size:#x} bytes");
612
613        let vmbus_vtl0 = parsed.vmbus_vtl0.as_ref().ok_or(DtError::Vtl0Vmbus)?;
614        if vmbus_vtl2_mmio_size != 0 {
615            // Verify the host provided mmio is large enough.
616            if vmbus_vtl2_mmio_size < mmio_size {
617                return Err(DtError::NotEnoughVtl2Mmio);
618            }
619
620            log::info!("using host provided vtl2 mmio: {:x?}", vmbus_vtl2.mmio);
621            (vmbus_vtl0.mmio.clone(), vmbus_vtl2.mmio.clone())
622        } else {
623            // Allocate vtl2 mmio from vtl0 mmio.
624            log::info!("no vtl2 mmio provided by host, allocating from vtl0 mmio");
625            let selected_vtl2_mmio = select_vtl2_mmio_range(&vmbus_vtl0.mmio, mmio_size)?;
626
627            // Update vtl0 mmio to exclude vtl2 mmio.
628            let vtl0_mmio = subtract_ranges(vmbus_vtl0.mmio.iter().cloned(), [selected_vtl2_mmio])
629                .collect::<ArrayVec<MemoryRange, 2>>();
630            let vtl2_mmio = [selected_vtl2_mmio]
631                .into_iter()
632                .collect::<ArrayVec<MemoryRange, 2>>();
633
634            // TODO: For now, if we have only a single vtl0_mmio range left,
635            // panic. In the future decide if we want to report this as a start
636            // failure in usermode, change allocation strategy, or something
637            // else.
638            assert_eq!(
639                vtl0_mmio.len(),
640                2,
641                "vtl0 mmio ranges are not 2 {:#x?}",
642                vtl0_mmio
643            );
644
645            log::info!("vtl0 mmio: {vtl0_mmio:x?}, vtl2 mmio: {vtl2_mmio:x?}");
646
647            (vtl0_mmio, vtl2_mmio)
648        }
649    } else {
650        (
651            parsed
652                .vmbus_vtl0
653                .as_ref()
654                .ok_or(DtError::Vtl0Vmbus)?
655                .mmio
656                .clone(),
657            parsed
658                .vmbus_vtl2
659                .as_ref()
660                .ok_or(DtError::Vtl2Vmbus)?
661                .mmio
662                .clone(),
663        )
664    };
665
666    // The host provided device tree is marked as normal ram, as the
667    // bootshim is responsible for constructing anything usermode needs from
668    // it, and passing it via the device tree provided to the kernel.
669    let reclaim_base = params.dt_start();
670    let reclaim_end = params.dt_start() + params.dt_size();
671    let vtl2_config_region_reclaim =
672        MemoryRange::try_new(reclaim_base..reclaim_end).expect("range is valid");
673
674    log::info!("reclaim device tree memory {reclaim_base:x}-{reclaim_end:x}");
675
676    // Initialize the address space manager with fixed at build time ranges.
677    let vtl2_config_region = MemoryRange::new(
678        params.parameter_region_start
679            ..(params.parameter_region_start + params.parameter_region_size),
680    );
681
682    // NOTE: Size the region as 20 pages. This should be plenty enough for the
683    // worst case encoded size (about 50 bytes worst case per memory entry, with
684    // the max number of ram ranges), and is small enough that we can reserve it
685    // on all sizes. Revisit this calculation if we persist more state in the
686    // future.
687    const PERSISTED_REGION_SIZE: u64 = 20 * 4096;
688    let (persisted_state_region, remainder) = params
689        .persisted_state
690        .split_at_offset(PERSISTED_REGION_SIZE);
691    log::info!(
692        "persisted state region sized to {persisted_state_region:#x?}, remainder {remainder:#x?}"
693    );
694
695    let mut address_space_builder = AddressSpaceManagerBuilder::new(
696        address_space,
697        &vtl2_ram,
698        params.used,
699        persisted_state_region,
700        subtract_ranges([vtl2_config_region], [vtl2_config_region_reclaim]),
701    );
702
703    address_space_builder = add_common_ranges(params, address_space_builder);
704
705    address_space_builder
706        .init()
707        .expect("failed to initialize address space manager");
708
709    if params.isolation_type == IsolationType::None {
710        let enable_vtl2_gpa_pool = options.enable_vtl2_gpa_pool;
711        let device_dma_page_count = parsed.device_dma_page_count;
712        let vp_count = parsed.cpu_count();
713        let mem_size = vtl2_ram.iter().map(|e| e.range.len()).sum();
714        if let Some(vtl2_gpa_pool_size) = pick_private_pool_size(
715            enable_vtl2_gpa_pool,
716            device_dma_page_count,
717            vp_count,
718            mem_size,
719        ) {
720            // Reserve the specified number of pages for the pool. Use the used
721            // ranges to figure out which VTL2 memory is free to allocate from.
722            let pool_size_bytes = vtl2_gpa_pool_size * HV_PAGE_SIZE;
723
724            allocate_private_pool(
725                address_space,
726                &vtl2_ram,
727                pool_size_bytes,
728                options.vtl2_gpa_pool_numa_split,
729                enable_vtl2_gpa_pool,
730                device_dma_page_count,
731                vp_count,
732                mem_size,
733            );
734        }
735    }
736
737    Ok(PartitionTopology {
738        vtl2_ram: OffStackRef::<'_, ArrayVec<MemoryEntry, MAX_VTL2_RAM_RANGES>>::leak(vtl2_ram),
739        vtl0_mmio,
740        vtl2_mmio,
741        memory_allocation_mode,
742    })
743}
744
745/// Read topology from the persisted state region and protobuf payload.
746fn topology_from_persisted_state(
747    header: PersistedStateHeader,
748    params: &ShimParams,
749    parsed: &ParsedDt,
750    address_space: &mut AddressSpaceManager,
751) -> Result<PersistedPartitionTopology, DtError> {
752    log::info!("reading topology from persisted state");
753
754    // Verify the header describes a protobuf region within the bootshim
755    // persisted region. We expect it to live there as today we rely on the
756    // build time generated pagetable to identity map the protobuf region.
757    let protobuf_region =
758        MemoryRange::new(header.protobuf_base..(header.protobuf_base + header.protobuf_region_len));
759    assert!(
760        params.persisted_state.contains(&protobuf_region),
761        "protobuf region {protobuf_region:#x?} is not contained within the persisted state region {:#x?}",
762        params.persisted_state
763    );
764
765    // Verify protobuf payload len is smaller than region.
766    assert!(
767        header.protobuf_payload_len <= header.protobuf_region_len,
768        "protobuf payload len {} is larger than region len {}",
769        header.protobuf_payload_len,
770        header.protobuf_region_len
771    );
772
773    // SAFETY: The region lies within the persisted state region, which is
774    // identity mapped via the build time generated pagetable.
775    let protobuf_raw = unsafe {
776        core::slice::from_raw_parts(
777            header.protobuf_base as *const u8,
778            header.protobuf_payload_len as usize,
779        )
780    };
781
782    let parsed_protobuf: loader_defs::shim::save_restore::SavedState =
783        bump_alloc::with_global_alloc(|| {
784            log::info!("decoding protobuf of size {}", protobuf_raw.len());
785            mesh_protobuf::decode(protobuf_raw).expect("failed to decode protobuf")
786        });
787
788    let loader_defs::shim::save_restore::SavedState {
789        partition_memory,
790        partition_mmio,
791        cpus_with_mapped_interrupts_no_io,
792        cpus_with_outstanding_io,
793    } = parsed_protobuf;
794
795    log::info!(
796        "persisted state: cpus_with_mapped_interrupts_no_io={:?}, cpus_with_outstanding_io={:?}",
797        cpus_with_mapped_interrupts_no_io,
798        cpus_with_outstanding_io,
799    );
800
801    let mut sidecar_excluded_cpus = off_stack!(ArrayVec<u32, MAX_CPU_COUNT>, ArrayVec::new_const());
802    sidecar_excluded_cpus.clear();
803    // Keep the list sorted and deduplicated as we insert, so it's ready for
804    // binary search lookups later.
805    for c in cpus_with_outstanding_io
806        .iter()
807        .chain(cpus_with_mapped_interrupts_no_io.iter())
808        .copied()
809    {
810        if let Err(i) = sidecar_excluded_cpus.binary_search(&c) {
811            sidecar_excluded_cpus.insert(i, c);
812        }
813    }
814
815    // FUTURE: should memory allocation mode should persist in saved state and
816    // verify the host did not change it?
817    let memory_allocation_mode = parsed.memory_allocation_mode;
818
819    let mut vtl2_ram =
820        off_stack!(ArrayVec<MemoryEntry, MAX_VTL2_RAM_RANGES>, ArrayVec::new_const());
821
822    // Determine which ranges are memory ranges used by VTL2.
823    let previous_vtl2_ram = partition_memory.iter().filter_map(|entry| {
824        if entry.vtl_type.ram() && entry.vtl_type.vtl2() {
825            Some(MemoryEntry {
826                range: entry.range,
827                mem_type: entry.igvm_type.clone().into(),
828                vnode: entry.vnode,
829            })
830        } else {
831            None
832        }
833    });
834
835    // Merge adjacent ranges as saved state reports the final usage of ram which
836    // includes reserved in separate ranges. Here we want the whole underlying
837    // ram ranges, merged with adjacent types if they share the same igvm types.
838    let previous_vtl2_ram = memory_range::merge_adjacent_ranges(
839        previous_vtl2_ram.map(|entry| (entry.range, (entry.mem_type, entry.vnode))),
840    );
841
842    vtl2_ram.extend(
843        previous_vtl2_ram.map(|(range, (mem_type, vnode))| MemoryEntry {
844            range,
845            mem_type,
846            vnode,
847        }),
848    );
849
850    // If the host was responsible for allocating VTL2 ram, verify the ram
851    // parsed from the previous instance matches.
852    //
853    // FUTURE: When VTL2 itself did allocation, we should verify that all ranges
854    // are still within the provided memory map.
855    if matches!(memory_allocation_mode, MemoryAllocationMode::Host) {
856        let host_vtl2_ram = parse_host_vtl2_ram(params, &parsed.memory);
857        assert_eq!(
858            vtl2_ram.as_slice(),
859            host_vtl2_ram.as_ref(),
860            "vtl2 ram from persisted state does not match host provided ram"
861        );
862    }
863
864    // Merge the persisted state header and protobuf region, and report that as
865    // the persisted region.
866    //
867    // NOTE: We could choose to resize the persisted region at this point, which
868    // we would need to do if we expect the saved state to grow larger.
869    let persisted_header = partition_memory
870        .iter()
871        .find(|entry| entry.vtl_type == MemoryVtlType::VTL2_PERSISTED_STATE_HEADER)
872        .expect("persisted state header missing");
873    let persisted_protobuf = partition_memory
874        .iter()
875        .find(|entry| entry.vtl_type == MemoryVtlType::VTL2_PERSISTED_STATE_PROTOBUF)
876        .expect("persisted state protobuf region missing");
877    assert_eq!(persisted_header.range.end(), protobuf_region.start());
878    let persisted_state_region =
879        MemoryRange::new(persisted_header.range.start()..persisted_protobuf.range.end());
880
881    // The host provided device tree is marked as normal ram, as the
882    // bootshim is responsible for constructing anything usermode needs from
883    // it, and passing it via the device tree provided to the kernel.
884    let reclaim_base = params.dt_start();
885    let reclaim_end = params.dt_start() + params.dt_size();
886    let vtl2_config_region_reclaim =
887        MemoryRange::try_new(reclaim_base..reclaim_end).expect("range is valid");
888
889    log::info!("reclaim device tree memory {reclaim_base:x}-{reclaim_end:x}");
890
891    let vtl2_config_region = MemoryRange::new(
892        params.parameter_region_start
893            ..(params.parameter_region_start + params.parameter_region_size),
894    );
895
896    let mut address_space_builder = AddressSpaceManagerBuilder::new(
897        address_space,
898        &vtl2_ram,
899        params.used,
900        persisted_state_region,
901        subtract_ranges([vtl2_config_region], [vtl2_config_region_reclaim]),
902    );
903
904    // NOTE: The only other region we take from the previous instance is any
905    // allocated vtl2 pool. Today, we do not allocate a new/larger pool if the
906    // command line arguments or host device tree changed, as that's not
907    // something we expect to happen in practice.
908    let pool_ranges = partition_memory.iter().filter_map(|entry| {
909        if entry.vtl_type == MemoryVtlType::VTL2_GPA_POOL {
910            Some(entry.range)
911        } else {
912            None
913        }
914    });
915
916    address_space_builder = address_space_builder.with_pool_ranges(pool_ranges);
917
918    // As described above, other ranges come from this current boot.
919    address_space_builder = add_common_ranges(params, address_space_builder);
920
921    address_space_builder
922        .init()
923        .expect("failed to initialize address space manager");
924
925    // Read previous mmio for VTL0 and VTL2.
926    let vtl0_mmio = partition_mmio
927        .iter()
928        .filter_map(|entry| {
929            if entry.vtl_type == MemoryVtlType::VTL0_MMIO {
930                Some(entry.range)
931            } else {
932                None
933            }
934        })
935        .collect::<ArrayVec<MemoryRange, 2>>();
936    let vtl2_mmio = partition_mmio
937        .iter()
938        .filter_map(|entry| {
939            if entry.vtl_type == MemoryVtlType::VTL2_MMIO {
940                Some(entry.range)
941            } else {
942                None
943            }
944        })
945        .collect::<ArrayVec<MemoryRange, 2>>();
946
947    Ok(PersistedPartitionTopology {
948        topology: PartitionTopology {
949            vtl2_ram: OffStackRef::<'_, ArrayVec<MemoryEntry, MAX_VTL2_RAM_RANGES>>::leak(vtl2_ram),
950            vtl0_mmio,
951            vtl2_mmio,
952            memory_allocation_mode,
953        },
954        sidecar_excluded_cpus: OffStackRef::leak(sidecar_excluded_cpus),
955    })
956}
957
958/// Read the persisted header from the start of the persisted state region
959/// described at file build time. If the magic value is not set, `None` is
960/// returned.
961fn read_persisted_region_header(params: &ShimParams) -> Option<PersistedStateHeader> {
962    // TODO CVM: On an isolated guest, these pages may not be accepted. We need
963    // to rethink how this will work in order to handle this correctly, as on a
964    // first boot we'd need to accept them early, but subsequent boots should
965    // not accept any pages.
966    //
967    // This may require some value passed in via a register or something early
968    // that indicates this is a servicing boot, which we could set if OpenHCL
969    // itself launches the next instance.
970    if params.isolation_type != IsolationType::None {
971        return None;
972    }
973
974    // SAFETY: The header lies at the start of the shim described persisted state
975    // region. This range is guaranteed to be identity mapped at file build
976    // time.
977    let buf = unsafe {
978        core::slice::from_raw_parts(
979            params.persisted_state.start() as *const u8,
980            size_of::<PersistedStateHeader>(),
981        )
982    };
983
984    let header = PersistedStateHeader::read_from_bytes(buf)
985        .expect("region is page aligned and the correct size");
986
987    if header.magic == PersistedStateHeader::MAGIC {
988        Some(header)
989    } else {
990        None
991    }
992}
993
994impl PartitionInfo {
995    // Read the IGVM provided DT for the vtl2 partition info.
996    pub fn read_from_dt<'a>(
997        params: &'a ShimParams,
998        storage: &'a mut Self,
999        address_space: &'_ mut AddressSpaceManager,
1000        mut options: BootCommandLineOptions,
1001        can_trust_host: bool,
1002    ) -> Result<&'a mut Self, DtError> {
1003        let dt = params.device_tree();
1004
1005        if dt[0] == 0 {
1006            log::error!("host did not provide a device tree");
1007            return Err(DtError::NoDeviceTree);
1008        }
1009
1010        let mut dt_storage = off_stack!(ParsedDt, ParsedDeviceTree::new());
1011
1012        let parsed = ParsedDeviceTree::parse(dt, &mut *dt_storage).map_err(DtError::DeviceTree)?;
1013
1014        let command_line = params.command_line();
1015
1016        // Always write the measured command line.
1017        write!(
1018            storage.cmdline,
1019            "{}",
1020            command_line
1021                .command_line()
1022                .expect("measured command line should be valid")
1023        )
1024        .map_err(|_| DtError::CommandLineSize)?;
1025
1026        match command_line.policy {
1027            CommandLinePolicy::STATIC => {
1028                // Nothing to do, we already wrote the measured command line.
1029            }
1030            CommandLinePolicy::APPEND_CHOSEN if can_trust_host => {
1031                // Check the host-provided command line for options for ourself,
1032                // and pass it along to the kernel.
1033                options.parse(&parsed.command_line);
1034                write!(storage.cmdline, " {}", parsed.command_line)
1035                    .map_err(|_| DtError::CommandLineSize)?;
1036            }
1037            CommandLinePolicy::APPEND_CHOSEN if !can_trust_host => {
1038                // Nothing to do, we ignore the host provided command line.
1039            }
1040            _ => unreachable!(),
1041        }
1042
1043        init_heap(params);
1044
1045        let persisted_state_header = read_persisted_region_header(params);
1046        log::info!(
1047            "read_from_dt: persisted_state_header present={}, sidecar={:?}",
1048            persisted_state_header.is_some(),
1049            options.sidecar,
1050        );
1051        let (topology, sidecar_excluded_cpus) = if let Some(header) = persisted_state_header {
1052            log::info!("found persisted state header");
1053            let persisted_topology =
1054                topology_from_persisted_state(header, params, parsed, address_space)?;
1055            (
1056                persisted_topology.topology,
1057                persisted_topology.sidecar_excluded_cpus,
1058            )
1059        } else {
1060            (
1061                topology_from_host_dt(params, parsed, &options, address_space)?,
1062                &[][..],
1063            )
1064        };
1065
1066        let Self {
1067            vtl2_ram,
1068            partition_ram,
1069            isolation,
1070            bsp_reg,
1071            cpus,
1072            sidecar_cpu_overrides,
1073            vmbus_vtl0,
1074            vmbus_vtl2,
1075            cmdline: _,
1076            com3_serial,
1077            gic,
1078            pmu_gsiv,
1079            memory_allocation_mode,
1080            entropy,
1081            vtl0_alias_map,
1082            nvme_keepalive,
1083            boot_options,
1084        } = storage;
1085
1086        // During servicing restore, selectively exclude CPUs that had
1087        // restored device state (outstanding NVMe I/O or just a mapped NVMe
1088        // interrupt) from sidecar startup. These CPUs need immediate kernel
1089        // access to handle device interrupts and complete the keepalive
1090        // restore. All other CPUs still benefit from sidecar's parallel
1091        // startup. Falls back to disabling sidecar entirely if CPU IDs exceed
1092        // the per-CPU state array capacity (>400 CPUs).
1093        //
1094        // Sidecar is automatically disabled when: all NUMA nodes have exactly
1095        // one CPU (nothing to parallelize), x2apic is unavailable, the VM is
1096        // isolated (CVM), or the sidecar image is not present (sidecar_size == 0).
1097        // It is also disabled via command line with OPENHCL_SIDECAR=off. In all
1098        // other cases sidecar is active and uses a fan-out pattern to bring up
1099        // APs in parallel across NUMA nodes.
1100        //
1101        // TODO: the `cpu_threshold` field in `SidecarOptions::Enabled` is
1102        // not used at present. Based on production performance data, either
1103        // remove `cpu_threshold` from `SidecarOptions` in cmdline.rs, or
1104        // add a VP-count cutoff here to disable sidecar for small VMs.
1105        if let (SidecarOptions::Enabled { .. }, true) =
1106            (&boot_options.sidecar, !sidecar_excluded_cpus.is_empty())
1107        {
1108            let max_cpu_id = *sidecar_excluded_cpus.iter().max().unwrap() as usize;
1109            if parsed.cpu_count() <= sidecar_cpu_overrides.sidecar_starts_cpu.len()
1110                && max_cpu_id < sidecar_cpu_overrides.sidecar_starts_cpu.len()
1111            {
1112                // Mark specific CPUs as kernel-started instead of sidecar-started.
1113                sidecar_cpu_overrides.per_cpu_state_specified = true;
1114                for &cpu_id in sidecar_excluded_cpus {
1115                    sidecar_cpu_overrides.sidecar_starts_cpu[cpu_id as usize] = false;
1116                }
1117                log::info!(
1118                    "sidecar: excluding CPUs {:?} due to restored NVMe device state",
1119                    sidecar_excluded_cpus,
1120                );
1121            } else {
1122                // CPU IDs exceed per-cpu array capacity; disable sidecar entirely.
1123                log::info!(
1124                    "sidecar: disabling, too many CPUs for per-CPU state (max id {max_cpu_id})"
1125                );
1126                boot_options.sidecar = SidecarOptions::DisabledServicing;
1127                options.sidecar = SidecarOptions::DisabledServicing;
1128            }
1129        }
1130
1131        // Set ram and memory alloction mode.
1132        vtl2_ram.clear();
1133        vtl2_ram.extend(topology.vtl2_ram.iter().copied());
1134        partition_ram.clear();
1135        partition_ram.extend(parsed.memory.iter().copied());
1136        *memory_allocation_mode = topology.memory_allocation_mode;
1137
1138        // Set vmbus fields. The connection ID comes from the host, but mmio
1139        // comes from topology.
1140        *vmbus_vtl0 = VmbusInfo {
1141            connection_id: parsed
1142                .vmbus_vtl0
1143                .as_ref()
1144                .ok_or(DtError::Vtl0Vmbus)?
1145                .connection_id,
1146            mmio: topology.vtl0_mmio,
1147        };
1148        *vmbus_vtl2 = VmbusInfo {
1149            connection_id: parsed
1150                .vmbus_vtl2
1151                .as_ref()
1152                .ok_or(DtError::Vtl2Vmbus)?
1153                .connection_id,
1154            mmio: topology.vtl2_mmio,
1155        };
1156
1157        // If we can trust the host, use the provided alias map
1158        if can_trust_host {
1159            *vtl0_alias_map = parsed.vtl0_alias_map;
1160        }
1161
1162        *isolation = params.isolation_type;
1163
1164        *bsp_reg = parsed.boot_cpuid_phys;
1165        cpus.extend(parsed.cpus.iter().copied());
1166        *com3_serial = parsed.com3_serial.clone();
1167        *gic = parsed.gic.clone();
1168        *pmu_gsiv = parsed.pmu_gsiv;
1169        *entropy = parsed.entropy.clone();
1170        *nvme_keepalive = parsed.nvme_keepalive;
1171        *boot_options = options;
1172
1173        Ok(storage)
1174    }
1175}