Skip to main content

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                isolation: virt::ProtoPartitionIsolation::None,
47                nested_virt: false,
48                #[cfg(guest_arch = "aarch64")]
49                device_assignment_msi_iova_range: None,
50            })
51            .context("failed to create proto partition")?;
52
53        let guest_memory = GuestMemory::allocate(self.state.memory_layout.end_of_ram() as usize);
54
55        let (partition, vps) = proto
56            .build(PartitionConfig {
57                mem_layout: &self.state.memory_layout,
58                guest_memory: &guest_memory,
59                cpuid: &[],
60                vtl0_alias_map: None,
61                fault_resolver: None,
62            })
63            .context("failed to build partition")?;
64
65        let partition = Arc::new(partition);
66
67        // Map guest memory.
68        for r in self.state.memory_layout.ram() {
69            let range = r.range;
70            // SAFETY: the guest memory is left alive as long as the partition
71            // is using it.
72            unsafe {
73                partition
74                    .memory_mapper(Vtl::Vtl0)
75                    .map_range(
76                        guest_memory.inner_buf().unwrap()
77                            [range.start() as usize..range.end() as usize]
78                            .as_ptr()
79                            .cast_mut()
80                            .cast(),
81                        range.len() as usize,
82                        range.start(),
83                        true,
84                        true,
85                    )
86                    .context("failed to map memory")
87            }?;
88        }
89
90        let mut threads = Vec::new();
91        let r = self
92            .run(
93                &guest_memory,
94                partition.caps(),
95                test,
96                async |_this, runner| {
97                    let [vp] = vps.try_into().ok().unwrap();
98                    threads.push(start_vp(partition.clone(), vp, runner).await?);
99                    Ok(())
100                },
101            )
102            .await?;
103        for thread in threads {
104            thread.join().unwrap();
105        }
106
107        // Ensure the partition has not leaked.
108        Arc::into_inner(partition).expect("partition is no longer referenced");
109
110        Ok(r)
111    }
112}
113
114trait RequestYield: Send + Sync {
115    /// Forces the run_vp call to yield to the scheduler (i.e. return
116    /// Poll::Pending).
117    fn request_yield(&self, vp_index: VpIndex);
118}
119
120impl<T: Partition> RequestYield for T {
121    fn request_yield(&self, vp_index: VpIndex) {
122        self.request_yield(vp_index)
123    }
124}
125
126struct VpWaker {
127    partition: Weak<dyn RequestYield>,
128    vp: VpIndex,
129    inner: Waker,
130}
131
132impl VpWaker {
133    fn new(partition: Weak<dyn RequestYield>, vp: VpIndex, waker: Waker) -> Self {
134        Self {
135            partition,
136            vp,
137            inner: waker,
138        }
139    }
140}
141
142impl std::task::Wake for VpWaker {
143    fn wake_by_ref(self: &Arc<Self>) {
144        if let Some(partition) = self.partition.upgrade() {
145            partition.request_yield(self.vp);
146        }
147        self.inner.wake_by_ref();
148    }
149
150    fn wake(self: Arc<Self>) {
151        self.wake_by_ref()
152    }
153}
154
155async fn start_vp(
156    partition: Arc<dyn RequestYield>,
157    mut vp: impl 'static + BindProcessor + Send,
158    mut runner: RunnerBuilder,
159) -> anyhow::Result<std::thread::JoinHandle<()>> {
160    let (bind_result_send, bind_result_recv) = mesh::oneshot();
161    let vp_thread = std::thread::spawn(move || {
162        let vp_index = VpIndex::BSP;
163        let r = vp
164            .bind()
165            .context("failed to bind vp")
166            .and_then(|vp| runner.build(vp));
167        let (vp, r) = match r {
168            Ok(vp) => (Some(vp), Ok(())),
169            Err(err) => (None, Err(err)),
170        };
171
172        bind_result_send.send(r);
173        let Some(mut vp) = vp else { return };
174        block_on(async {
175            let mut run = pin!(vp.run_vp());
176            poll_fn(|cx| {
177                let waker = Waker::from(Arc::new(VpWaker::new(
178                    Arc::downgrade(&partition),
179                    vp_index,
180                    cx.waker().clone(),
181                )));
182                run.as_mut().poll(&mut Context::from_waker(&waker))
183            })
184            .await
185        })
186    });
187
188    bind_result_recv.await.unwrap()?;
189    Ok(vp_thread)
190}