Skip to main content

vmm_core/
partition_unit.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! State unit for managing the VM partition and associated virtual processors.
5
6mod debug;
7mod vp_set;
8
9pub use vp_set::Halt;
10pub use vp_set::RequestYield;
11pub use vp_set::RunCancelled;
12pub use vp_set::RunnerCanceller;
13pub use vp_set::VpRunner;
14pub use vp_set::block_on_vp;
15
16use self::vp_set::RegisterSetError;
17#[cfg(feature = "dump")]
18use anyhow::Context as _;
19use async_trait::async_trait;
20use futures::FutureExt;
21use futures::StreamExt;
22use guestmem::GuestMemory;
23use hvdef::Vtl;
24use inspect::InspectMut;
25use mesh::Receiver;
26use mesh::rpc::Rpc;
27use mesh::rpc::RpcSend;
28use pal_async::task::Spawn;
29use state_unit::NameInUse;
30use state_unit::SpawnedUnit;
31use state_unit::StateRequest;
32use state_unit::StateUnit;
33use state_unit::UnitBuilder;
34use state_unit::UnitHandle;
35use std::sync::Arc;
36use thiserror::Error;
37use virt::InitialPageImport;
38use virt::InitialRegs;
39use virt::InitialVpStateSource;
40#[cfg(feature = "dump")]
41use virt::VpIndex;
42use vm_topology::processor::ProcessorTopology;
43use vmcore::save_restore::ProtobufSaveRestore;
44use vmcore::save_restore::RestoreError;
45use vmcore::save_restore::SaveError;
46use vmcore::save_restore::SavedStateBlob;
47use vmm_core_defs::HaltReason;
48use vp_set::VpSet;
49
50/// The control point for managing a partition unit.
51pub struct PartitionUnit {
52    handle: SpawnedUnit<PartitionUnitRunner>,
53    req_send: mesh::Sender<PartitionRequest>,
54}
55
56/// Trait with the minimal methods needed to run the partition.
57#[async_trait]
58pub trait VmPartition: 'static + Send + Sync + InspectMut + ProtobufSaveRestore {
59    /// Returns the source of the initial virtual processor state.
60    fn initial_vp_state_source(&self) -> InitialVpStateSource;
61
62    /// Resets the partition.
63    fn reset(&mut self) -> anyhow::Result<()>;
64
65    /// Scrubs the VTL state for a partition.
66    fn scrub_vtl(&mut self, vtl: Vtl) -> anyhow::Result<()>;
67
68    /// Finalizes initial page imports on behalf of the loader.
69    fn accept_initial_pages(&mut self, pages: Vec<InitialPageImport>) -> anyhow::Result<()>;
70
71    /// Returns the guest OS ID (from `HV_X64_MSR_GUEST_OS_ID`).
72    ///
73    /// Returns 0 if the guest hasn't written the MSR (unenlightened guest)
74    /// or if the backend doesn't support reading it.
75    fn guest_os_id(&self) -> u64 {
76        0
77    }
78}
79
80/// An object to run the VM partition state unit.
81struct PartitionUnitRunner {
82    partition: Box<dyn VmPartition>,
83    vp_set: VpSet,
84    unit_started: bool,
85    vp_stop_count: usize,
86    needs_reset: bool,
87    halt_reason: Option<HaltReason>,
88    halt_request_recv: Receiver<InternalHaltReason>,
89    client_notify_send: mesh::Sender<HaltReason>,
90    req_recv: Receiver<PartitionRequest>,
91    topology: ProcessorTopology,
92    initial_regs: Option<Arc<InitialRegs>>,
93
94    #[cfg(feature = "gdb")]
95    debugger_state: debug::DebuggerState,
96}
97
98impl InspectMut for PartitionUnitRunner {
99    fn inspect_mut(&mut self, req: inspect::Request<'_>) {
100        req.respond()
101            .field(
102                "power_state",
103                self.halt_reason.as_ref().map_or("running", |_| "halted"),
104            )
105            .merge(&self.halt_reason)
106            .merge(&self.vp_set)
107            .field_mut_with("clear_halt", |clear| {
108                // Clear halt if "true" is specified.
109                if let Some(clear) = clear {
110                    match clear.parse::<bool>() {
111                        Ok(x) => {
112                            if x {
113                                self.clear_halt();
114                            }
115                            Ok(x)
116                        }
117                        Err(err) => Err(err),
118                    }
119                } else {
120                    Ok(false)
121                }
122            })
123            .field("topology", &self.topology)
124            .merge(&mut self.partition);
125    }
126}
127
128enum PartitionRequest {
129    ClearHalt(Rpc<(), bool>), // TODO: remove this, and use DebugRequest::Resume
130    SetInitialRegs(Rpc<(Vtl, Arc<InitialRegs>), Result<(), InitialRegError>>),
131    AcceptInitialPages(Rpc<Vec<InitialPageImport>, Result<(), AcceptInitialPagesError>>),
132    StopVps(Rpc<(), ()>),
133    StartVps,
134    /// Build the partition state blob for a dump file.
135    #[cfg(feature = "dump")]
136    BuildDumpPartitionState(Rpc<(), anyhow::Result<Vec<u8>>>),
137}
138
139pub struct PartitionUnitParams<'a> {
140    pub vtl_guest_memory: [Option<&'a GuestMemory>; 3],
141    pub processor_topology: &'a ProcessorTopology,
142    /// Tracks the halt state of VPs.
143    pub halt_vps: Arc<Halt>,
144    /// The receiver returned from `Halt::new()`.
145    pub halt_request_recv: HaltReasonReceiver,
146    /// Notified when the partition has been halted (due to a triple fault or
147    /// other reason).
148    pub client_notify_send: mesh::Sender<HaltReason>,
149    pub debugger_rpc: Option<Receiver<vmm_core_defs::debug_rpc::DebugRequest>>,
150}
151
152/// The halt reason receiver to pass to put in [`PartitionUnitParams`].
153pub struct HaltReasonReceiver(Receiver<InternalHaltReason>);
154
155enum InternalHaltReason {
156    Halt(HaltReason),
157    ReplayMtrrs,
158}
159
160/// Error returned by [`PartitionUnit::new()`].
161#[derive(Debug, Error)]
162pub enum Error {
163    #[error("debugging is not supported in this build")]
164    DebuggingNotSupported,
165    #[error(transparent)]
166    NameInUse(NameInUse),
167    #[error("missing guest memory required for gdb support")]
168    MissingGuestMemory,
169}
170
171/// Error returned by [`PartitionUnit::set_initial_regs()`].
172#[derive(Debug, Error)]
173pub enum InitialRegError {
174    #[error("failed to set registers")]
175    RegisterSet(#[source] RegisterSetError),
176    #[error("failed to scrub VTL state")]
177    ScrubVtl(#[source] anyhow::Error),
178}
179
180/// Error returned by [`PartitionUnit::accept_initial_pages()`].
181#[derive(Debug, Error)]
182pub enum AcceptInitialPagesError {
183    #[error("failed to finalize initial page imports")]
184    Finalize(#[source] anyhow::Error),
185}
186
187impl PartitionUnit {
188    /// Creates a new VM partition state unit.
189    ///
190    /// The caller is responsible for launching a thread for each VP and running
191    /// the VP using the returned [`VpRunner`]s.
192    pub fn new(
193        spawner: impl Spawn,
194        builder: UnitBuilder<'_>,
195        partition: impl VmPartition,
196        params: PartitionUnitParams<'_>,
197    ) -> Result<(Self, Vec<VpRunner>), Error> {
198        #[cfg(not(feature = "gdb"))]
199        if params.debugger_rpc.is_some() {
200            return Err(Error::DebuggingNotSupported);
201        }
202
203        let mut vp_set = VpSet::new(params.vtl_guest_memory.map(|m| m.cloned()), params.halt_vps);
204        let vps = params
205            .processor_topology
206            .vps_arch()
207            .map(|vp| vp_set.add(vp))
208            .collect();
209
210        let (req_send, req_recv) = mesh::channel();
211
212        let mut runner = PartitionUnitRunner {
213            partition: Box::new(partition),
214            vp_set,
215            unit_started: false,
216            vp_stop_count: 0,
217            needs_reset: false,
218            halt_reason: None,
219            halt_request_recv: params.halt_request_recv.0,
220            client_notify_send: params.client_notify_send,
221            req_recv,
222            topology: params.processor_topology.clone(),
223            initial_regs: None,
224            #[cfg(feature = "gdb")]
225            debugger_state: debug::DebuggerState::new(
226                params.vtl_guest_memory[0]
227                    .ok_or(Error::MissingGuestMemory)?
228                    .clone(),
229                params.debugger_rpc,
230            ),
231        };
232
233        let handle = builder
234            .spawn(spawner, async |recv| {
235                runner.run(recv).await;
236                runner
237            })
238            .unwrap();
239
240        Ok((Self { handle, req_send }, vps))
241    }
242
243    /// Gets the handle for the partition unit.
244    pub fn unit_handle(&self) -> &UnitHandle {
245        self.handle.handle()
246    }
247
248    /// Tears down the state unit, returning the `client_notify_send` sender
249    /// passed to [`Self::new()`].
250    pub async fn teardown(self) -> mesh::Sender<HaltReason> {
251        let runner = self.handle.remove().await;
252        runner.vp_set.teardown().await;
253        runner.client_notify_send
254    }
255
256    /// Clears the current halt reason from the partition, resuming the VPs if
257    /// they are stopped.
258    pub async fn clear_halt(&mut self) -> bool {
259        self.req_send
260            .call(PartitionRequest::ClearHalt, ())
261            .await
262            .unwrap()
263    }
264
265    /// Temporarily stops the VPs, returning a guard that will resume them when
266    /// dropped.
267    pub async fn temporarily_stop_vps(&mut self) -> StopGuard {
268        self.req_send
269            .call(PartitionRequest::StopVps, ())
270            .await
271            .unwrap();
272
273        StopGuard(self.req_send.clone())
274    }
275
276    /// Sets the register state for the VPs for initial boot.
277    ///
278    /// If the VM has been run before and has not been reset since it last ran,
279    /// the target VTL will be scrubbed first so that the partition state is
280    /// clean.
281    pub async fn set_initial_regs(
282        &mut self,
283        vtl: Vtl,
284        state: Arc<InitialRegs>,
285    ) -> Result<(), InitialRegError> {
286        self.req_send
287            .call(PartitionRequest::SetInitialRegs, (vtl, state))
288            .await
289            .unwrap()
290    }
291
292    pub async fn accept_initial_pages(
293        &mut self,
294        initial_pages: Vec<InitialPageImport>,
295    ) -> Result<(), AcceptInitialPagesError> {
296        self.req_send
297            .call(PartitionRequest::AcceptInitialPages, initial_pages)
298            .await
299            .unwrap()
300    }
301
302    /// Builds the partition state blob for a `.vmrs` dump file.
303    ///
304    /// Stops VPs internally for a consistent snapshot and resumes them
305    /// afterward. Returns the serialized partition state (VP registers
306    /// as hypervisor save/restore chunks).
307    #[cfg(feature = "dump")]
308    pub async fn build_dump_partition_state(&mut self) -> anyhow::Result<Vec<u8>> {
309        self.req_send
310            .call(PartitionRequest::BuildDumpPartitionState, ())
311            .await
312            .unwrap()
313    }
314}
315
316impl PartitionUnitRunner {
317    /// Runs the VM partition, handling state change requests from `recv`.
318    async fn run(&mut self, mut recv: Receiver<StateRequest>) {
319        loop {
320            enum Event {
321                State(Option<StateRequest>),
322                Halt(InternalHaltReason),
323                Request(PartitionRequest),
324                #[cfg(feature = "gdb")]
325                Debug(vmm_core_defs::debug_rpc::DebugRequest),
326            }
327
328            #[cfg(feature = "gdb")]
329            let debug = self.debugger_state.wait_rpc();
330            #[cfg(not(feature = "gdb"))]
331            let debug = std::future::pending();
332
333            let event = futures::select! {  // merge semantics
334                request = recv.next() => Event::State(request),
335                request = self.halt_request_recv.select_next_some() => Event::Halt(request),
336                request = self.req_recv.select_next_some() => Event::Request(request),
337                request = debug.fuse() => {
338                    #[cfg(feature = "gdb")]
339                    {
340                        Event::Debug(request)
341                    }
342                    #[cfg(not(feature = "gdb"))]
343                    {
344                        let _: std::convert::Infallible = request;
345                        unreachable!()
346                    }
347                }
348            };
349
350            match event {
351                Event::State(request) => {
352                    if let Some(request) = request {
353                        request.apply(self).await;
354                    } else {
355                        break;
356                    }
357                }
358                Event::Halt(reason) => {
359                    // Wait for the VPs to stop before reporting this anywhere.
360                    // This is generally good behavior, but it is especially
361                    // necessary because Self::clear_halt() will call
362                    // VpSet::clear_halt(), which relies on the VPs being
363                    // affirmatively stopped.
364                    self.vp_set.stop().await;
365                    self.handle_halt(reason).await;
366                }
367                Event::Request(request) => match request {
368                    PartitionRequest::ClearHalt(rpc) => rpc.handle_sync(|()| self.clear_halt()),
369                    PartitionRequest::SetInitialRegs(rpc) => {
370                        rpc.handle(async |(vtl, state)| self.set_initial_regs(vtl, state).await)
371                            .await
372                    }
373                    PartitionRequest::AcceptInitialPages(rpc) => {
374                        rpc.handle(async |initial_pages| {
375                            self.accept_initial_pages(initial_pages).await
376                        })
377                        .await
378                    }
379                    PartitionRequest::StopVps(rpc) => {
380                        rpc.handle(async |()| self.stop_vps().await).await
381                    }
382                    PartitionRequest::StartVps => {
383                        self.resume_vps();
384                    }
385                    #[cfg(feature = "dump")]
386                    PartitionRequest::BuildDumpPartitionState(rpc) => {
387                        rpc.handle(async |()| self.build_dump_partition_state().await)
388                            .await
389                    }
390                },
391                #[cfg(feature = "gdb")]
392                Event::Debug(request) => {
393                    self.handle_gdb(request).await;
394                }
395            }
396        }
397
398        if self.unit_started {
399            self.vp_set.stop().await;
400        }
401    }
402
403    async fn handle_halt(&mut self, reason: InternalHaltReason) {
404        match reason {
405            InternalHaltReason::Halt(reason) => {
406                // Only report the first halt request per boot so that the
407                // client does not have to deal with multiple halt reasons
408                // due to race conditions.
409                if self.halt_reason.is_none() {
410                    self.halt_reason = Some(reason.clone());
411
412                    // Report the halt to the debugger.
413                    #[cfg(feature = "gdb")]
414                    let reported = self.debugger_state.report_halt_to_debugger(&reason);
415                    #[cfg(not(feature = "gdb"))]
416                    let reported = false;
417
418                    // If the debugger is not attached, then report the halt
419                    // to the client.
420                    if !reported {
421                        self.client_notify_send.send(reason);
422                    }
423                } else {
424                    // Clear this specific halt.
425                    self.vp_set.clear_halt();
426                }
427            }
428            InternalHaltReason::ReplayMtrrs => {
429                if let Some(initial_regs) = self.initial_regs.clone() {
430                    if let Err(err) = self
431                        .vp_set
432                        .set_initial_regs(
433                            Vtl::Vtl0,
434                            initial_regs,
435                            vp_set::RegistersToSet::MtrrsOnly,
436                        )
437                        .await
438                    {
439                        tracing::error!(
440                            error = &err as &dyn std::error::Error,
441                            "failed to replay mtrrs, guest may see inconsistent results"
442                        );
443                    }
444                } else {
445                    tracing::warn!("no initial mtrrs to replay");
446                }
447                self.vp_set.clear_halt();
448                self.try_start();
449            }
450        }
451    }
452
453    /// Clears the halt and resumes the VPs if the partition is started. Returns
454    /// `false` if VPs were not already halted.
455    fn clear_halt(&mut self) -> bool {
456        if self.halt_reason.is_some() {
457            self.halt_reason = None;
458            self.vp_set.clear_halt();
459            self.try_start();
460            true
461        } else {
462            false
463        }
464    }
465
466    async fn set_initial_regs(
467        &mut self,
468        vtl: Vtl,
469        state: Arc<InitialRegs>,
470    ) -> Result<(), InitialRegError> {
471        assert!(!self.unit_started || self.vp_stop_count > 0);
472
473        // If this VM has been run before, then automatically scrub the target
474        // VTL state.
475        if self.needs_reset {
476            self.partition
477                .scrub_vtl(vtl)
478                .map_err(InitialRegError::ScrubVtl)?;
479            self.vp_set
480                .scrub(vtl)
481                .await
482                .map_err(InitialRegError::ScrubVtl)?;
483            self.needs_reset = false;
484        }
485
486        match self.partition.initial_vp_state_source() {
487            InitialVpStateSource::Registers => {
488                self.vp_set
489                    .set_initial_regs(vtl, state.clone(), vp_set::RegistersToSet::All)
490                    .await
491                    .map_err(InitialRegError::RegisterSet)?;
492            }
493            InitialVpStateSource::ImportedContext => {}
494        }
495
496        self.initial_regs = Some(state);
497        Ok(())
498    }
499
500    async fn accept_initial_pages(
501        &mut self,
502        initial_pages: Vec<InitialPageImport>,
503    ) -> Result<(), AcceptInitialPagesError> {
504        assert!(!self.unit_started);
505
506        self.partition
507            .accept_initial_pages(initial_pages)
508            .map_err(AcceptInitialPagesError::Finalize)
509    }
510
511    fn try_start(&mut self) {
512        if self.unit_started && self.halt_reason.is_none() && self.vp_stop_count == 0 {
513            self.needs_reset = true;
514            self.vp_set.start();
515        }
516    }
517
518    async fn stop_vps(&mut self) {
519        self.vp_set.stop().await;
520        self.vp_stop_count += 1;
521    }
522
523    fn resume_vps(&mut self) {
524        assert!(
525            self.vp_stop_count > 0,
526            "resume_vps called without matching stop"
527        );
528        self.vp_stop_count -= 1;
529        self.try_start();
530    }
531}
532
533#[cfg(feature = "dump")]
534impl PartitionUnitRunner {
535    /// Builds the partition state blob for a `.vmrs` dump file.
536    ///
537    /// Collects VP register state and assembles the chunk stream.
538    async fn build_dump_partition_state(&mut self) -> anyhow::Result<Vec<u8>> {
539        // Stop VPs for a consistent snapshot (and to ensure guest_os_id
540        // doesn't block on backends that retrieve it from a VP).
541        self.stop_vps().await;
542        let result = self.build_dump_partition_state_inner().await;
543        self.resume_vps();
544        result
545    }
546
547    async fn build_dump_partition_state_inner(&mut self) -> anyhow::Result<Vec<u8>> {
548        use hyperv_dump::PartitionStateBuilder;
549        use hyperv_dump::ProcessorArch;
550
551        #[cfg(guest_arch = "x86_64")]
552        let arch = ProcessorArch::X64;
553        #[cfg(guest_arch = "aarch64")]
554        let arch = ProcessorArch::Aarch64;
555
556        let mut builder = PartitionStateBuilder::new(arch);
557        builder.set_os_id(self.partition.guest_os_id());
558
559        let vp_count = self.topology.vp_count();
560        for vp_idx in 0..vp_count {
561            let vtl = Vtl::Vtl0;
562            let vp_state = self
563                .vp_set
564                .get_dump_vp_state(VpIndex::new(vp_idx), vtl)
565                .await
566                .with_context(|| format!("failed to get state for VP {vp_idx}"))?;
567
568            builder.add_vp(vp_idx, vec![(vtl, vp_state)]);
569        }
570
571        Ok(builder.finish())
572    }
573}
574
575#[must_use = "when dropped, the VPs will be resumed"]
576pub struct StopGuard(mesh::Sender<PartitionRequest>);
577
578impl Drop for StopGuard {
579    fn drop(&mut self) {
580        self.0.send(PartitionRequest::StartVps);
581    }
582}
583
584impl StateUnit for PartitionUnitRunner {
585    async fn start(&mut self) {
586        self.unit_started = true;
587        self.try_start();
588    }
589
590    async fn stop(&mut self) {
591        self.vp_set.stop().await;
592        self.unit_started = false;
593
594        // Now that the VM is stopped, flush any guest-initiated
595        // power state change that may have raced with this request.
596        while let Ok(reason) = self.halt_request_recv.try_recv() {
597            self.handle_halt(reason).await;
598        }
599    }
600
601    async fn reset(&mut self) -> anyhow::Result<()> {
602        self.partition.reset()?;
603        self.vp_set.reset().await?;
604        self.clear_halt();
605        self.needs_reset = false;
606        Ok(())
607    }
608
609    async fn save(&mut self) -> Result<Option<SavedStateBlob>, SaveError> {
610        let state = self.save().await?;
611        Ok(Some(SavedStateBlob::new(state)))
612    }
613
614    async fn restore(&mut self, buffer: SavedStateBlob) -> Result<(), RestoreError> {
615        // TODO: restore halted state
616        self.needs_reset = true;
617        self.restore(buffer.parse()?).await?;
618        Ok(())
619    }
620}
621
622mod save_restore {
623    use super::PartitionUnitRunner;
624    use virt::VpIndex;
625    use vmcore::save_restore::RestoreError;
626    use vmcore::save_restore::SaveError;
627
628    mod state {
629        use mesh::payload::Protobuf;
630        use vmcore::save_restore::SavedStateBlob;
631        use vmcore::save_restore::SavedStateRoot;
632
633        #[derive(Protobuf, SavedStateRoot)]
634        #[mesh(package = "partition")]
635        pub struct Partition {
636            #[mesh(1)]
637            pub(super) partition: SavedStateBlob,
638            #[mesh(2)]
639            pub(super) vps: Vec<Vp>,
640            // TODO: save halted state
641        }
642
643        #[derive(Protobuf)]
644        #[mesh(package = "partition")]
645        pub struct Vp {
646            #[mesh(1)]
647            pub vp_index: u32,
648            #[mesh(2)]
649            pub data: SavedStateBlob,
650        }
651    }
652
653    impl PartitionUnitRunner {
654        pub async fn save(&mut self) -> Result<state::Partition, SaveError> {
655            let partition = self.partition.save()?;
656            let vps = self.vp_set.save().await?;
657            let vps = vps
658                .into_iter()
659                .map(|(vp_index, data)| state::Vp {
660                    vp_index: vp_index.index(),
661                    data,
662                })
663                .collect();
664
665            Ok(state::Partition { partition, vps })
666        }
667
668        pub async fn restore(&mut self, state: state::Partition) -> Result<(), RestoreError> {
669            let state::Partition { partition, vps } = state;
670            self.partition.restore(partition)?;
671            self.vp_set
672                .restore(
673                    vps.into_iter()
674                        .map(|state::Vp { vp_index, data }| (VpIndex::new(vp_index), data)),
675                )
676                .await?;
677            Ok(())
678        }
679    }
680}