1mod 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
50pub struct PartitionUnit {
52 handle: SpawnedUnit<PartitionUnitRunner>,
53 req_send: mesh::Sender<PartitionRequest>,
54}
55
56#[async_trait]
58pub trait VmPartition: 'static + Send + Sync + InspectMut + ProtobufSaveRestore {
59 fn initial_vp_state_source(&self) -> InitialVpStateSource;
61
62 fn reset(&mut self) -> anyhow::Result<()>;
64
65 fn scrub_vtl(&mut self, vtl: Vtl) -> anyhow::Result<()>;
67
68 fn accept_initial_pages(&mut self, pages: Vec<InitialPageImport>) -> anyhow::Result<()>;
70
71 fn guest_os_id(&self) -> u64 {
76 0
77 }
78}
79
80struct 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 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>), SetInitialRegs(Rpc<(Vtl, Arc<InitialRegs>), Result<(), InitialRegError>>),
131 AcceptInitialPages(Rpc<Vec<InitialPageImport>, Result<(), AcceptInitialPagesError>>),
132 StopVps(Rpc<(), ()>),
133 StartVps,
134 #[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 pub halt_vps: Arc<Halt>,
144 pub halt_request_recv: HaltReasonReceiver,
146 pub client_notify_send: mesh::Sender<HaltReason>,
149 pub debugger_rpc: Option<Receiver<vmm_core_defs::debug_rpc::DebugRequest>>,
150}
151
152pub struct HaltReasonReceiver(Receiver<InternalHaltReason>);
154
155enum InternalHaltReason {
156 Halt(HaltReason),
157 ReplayMtrrs,
158}
159
160#[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#[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#[derive(Debug, Error)]
182pub enum AcceptInitialPagesError {
183 #[error("failed to finalize initial page imports")]
184 Finalize(#[source] anyhow::Error),
185}
186
187impl PartitionUnit {
188 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 pub fn unit_handle(&self) -> &UnitHandle {
245 self.handle.handle()
246 }
247
248 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 pub async fn clear_halt(&mut self) -> bool {
259 self.req_send
260 .call(PartitionRequest::ClearHalt, ())
261 .await
262 .unwrap()
263 }
264
265 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 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 #[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 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! { 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 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 if self.halt_reason.is_none() {
410 self.halt_reason = Some(reason.clone());
411
412 #[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 !reported {
421 self.client_notify_send.send(reason);
422 }
423 } else {
424 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 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 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 async fn build_dump_partition_state(&mut self) -> anyhow::Result<Vec<u8>> {
539 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 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 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 }
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}