tmk_vmm/
host_vmm.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Support for running as a host VMM.
5
6// UNSAFETY: needed to map guest memory.
7#![expect(unsafe_code)]
8
9use crate::run::RunContext;
10use crate::run::RunnerBuilder;
11use crate::run::TestResult;
12use anyhow::Context as _;
13use futures::executor::block_on;
14use guestmem::GuestMemory;
15use hvdef::Vtl;
16use std::future::Future;
17use std::future::poll_fn;
18use std::pin::pin;
19use std::sync::Arc;
20use std::sync::Weak;
21use std::task::Context;
22use std::task::Waker;
23use virt::BindProcessor;
24use virt::Hypervisor;
25use virt::Partition;
26use virt::PartitionConfig;
27use virt::PartitionMemoryMapper;
28use virt::ProtoPartition;
29use virt::ProtoPartitionConfig;
30use virt::VpIndex;
31
32impl RunContext<'_> {
33    pub async fn run_host_vmm<H: Hypervisor>(
34        &mut self,
35        mut hv: H,
36        test: &crate::load::TestInfo,
37    ) -> anyhow::Result<TestResult>
38    where
39        H::Partition: Partition + PartitionMemoryMapper,
40    {
41        let proto = hv
42            .new_partition(ProtoPartitionConfig {
43                processor_topology: &self.state.processor_topology,
44                hv_config: None,
45                vmtime: self.vmtime_source,
46                user_mode_apic: self.state.opts.disable_offloads,
47                isolation: virt::IsolationType::None,
48            })
49            .context("failed to create proto partition")?;
50
51        let guest_memory = GuestMemory::allocate(self.state.memory_layout.end_of_ram() as usize);
52
53        let (partition, vps) = proto
54            .build(PartitionConfig {
55                mem_layout: &self.state.memory_layout,
56                guest_memory: &guest_memory,
57                cpuid: &[],
58                vtl0_alias_map: None,
59            })
60            .context("failed to build partition")?;
61
62        let partition = Arc::new(partition);
63
64        // Map guest memory.
65        for r in self.state.memory_layout.ram() {
66            let range = r.range;
67            // SAFETY: the guest memory is left alive as long as the partition
68            // is using it.
69            unsafe {
70                partition
71                    .memory_mapper(Vtl::Vtl0)
72                    .map_range(
73                        guest_memory.inner_buf().unwrap()
74                            [range.start() as usize..range.end() as usize]
75                            .as_ptr()
76                            .cast_mut()
77                            .cast(),
78                        range.len() as usize,
79                        range.start(),
80                        true,
81                        true,
82                    )
83                    .context("failed to map memory")
84            }?;
85        }
86
87        let mut threads = Vec::new();
88        let r = self
89            .run(
90                &guest_memory,
91                partition.caps(),
92                test,
93                async |_this, runner| {
94                    let [vp] = vps.try_into().ok().unwrap();
95                    threads.push(start_vp(partition.clone(), vp, runner).await?);
96                    Ok(())
97                },
98            )
99            .await?;
100        for thread in threads {
101            thread.join().unwrap();
102        }
103
104        // Ensure the partition has not leaked.
105        Arc::into_inner(partition).expect("partition is no longer referenced");
106
107        Ok(r)
108    }
109}
110
111trait RequestYield: Send + Sync {
112    /// Forces the run_vp call to yield to the scheduler (i.e. return
113    /// Poll::Pending).
114    fn request_yield(&self, vp_index: VpIndex);
115}
116
117impl<T: Partition> RequestYield for T {
118    fn request_yield(&self, vp_index: VpIndex) {
119        self.request_yield(vp_index)
120    }
121}
122
123struct VpWaker {
124    partition: Weak<dyn RequestYield>,
125    vp: VpIndex,
126    inner: Waker,
127}
128
129impl VpWaker {
130    fn new(partition: Weak<dyn RequestYield>, vp: VpIndex, waker: Waker) -> Self {
131        Self {
132            partition,
133            vp,
134            inner: waker,
135        }
136    }
137}
138
139impl std::task::Wake for VpWaker {
140    fn wake_by_ref(self: &Arc<Self>) {
141        if let Some(partition) = self.partition.upgrade() {
142            partition.request_yield(self.vp);
143        }
144        self.inner.wake_by_ref();
145    }
146
147    fn wake(self: Arc<Self>) {
148        self.wake_by_ref()
149    }
150}
151
152async fn start_vp(
153    partition: Arc<dyn RequestYield>,
154    mut vp: impl 'static + BindProcessor + Send,
155    mut runner: RunnerBuilder,
156) -> anyhow::Result<std::thread::JoinHandle<()>> {
157    let (bind_result_send, bind_result_recv) = mesh::oneshot();
158    let vp_thread = std::thread::spawn(move || {
159        let vp_index = VpIndex::BSP;
160        let r = vp
161            .bind()
162            .context("failed to bind vp")
163            .and_then(|vp| runner.build(vp));
164        let (vp, r) = match r {
165            Ok(vp) => (Some(vp), Ok(())),
166            Err(err) => (None, Err(err)),
167        };
168
169        bind_result_send.send(r);
170        let Some(mut vp) = vp else { return };
171        block_on(async {
172            let mut run = pin!(vp.run_vp());
173            poll_fn(|cx| {
174                let waker = Waker::from(Arc::new(VpWaker::new(
175                    Arc::downgrade(&partition),
176                    vp_index,
177                    cx.waker().clone(),
178                )));
179                run.as_mut().poll(&mut Context::from_waker(&waker))
180            })
181            .await
182        })
183    });
184
185    bind_result_recv.await.unwrap()?;
186    Ok(vp_thread)
187}