Skip to main content

axvm_types/
lib.rs

1// Copyright 2025 The Axvisor Team
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Shared base types for AxVM and virtualization capability components.
16//!
17//! This crate intentionally contains only small value types and aliases. It is
18//! not a host capability API and must not depend on any OS-specific crate.
19
20#![no_std]
21
22extern crate alloc;
23
24mod error;
25
26use alloc::string::String;
27use core::fmt::{Debug, Formatter, LowerHex, UpperHex};
28
29use ax_memory_addr::{AddrRange, PhysAddr, VirtAddr, def_usize_addr, def_usize_addr_formatter};
30pub use error::{VmBackendError, VmBackendResult};
31
32bitflags::bitflags! {
33    /// Generic memory mapping permissions and attributes exchanged between
34    /// AxVM components.
35    #[derive(Clone, Copy, PartialEq, Eq)]
36    pub struct MappingFlags: usize {
37        /// The memory is readable.
38        const READ          = 1 << 0;
39        /// The memory is writable.
40        const WRITE         = 1 << 1;
41        /// The memory is executable.
42        const EXECUTE       = 1 << 2;
43        /// The memory is user accessible.
44        const USER          = 1 << 3;
45        /// The memory is device memory.
46        const DEVICE        = 1 << 4;
47        /// The memory is uncached.
48        const UNCACHED      = 1 << 5;
49    }
50}
51
52impl Debug for MappingFlags {
53    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
54        Debug::fmt(&self.0, f)
55    }
56}
57
58/// Virtual machine identifier.
59pub type VMId = usize;
60
61/// Virtual CPU identifier within a VM.
62pub type VCpuId = usize;
63
64/// Interrupt vector number injected into a guest.
65pub type InterruptVector = u8;
66
67/// Interrupt trigger mode.
68///
69/// Represents the trigger mode of an interrupt in a platform-neutral way.
70/// Every architecture adapter must explicitly define where this metadata is
71/// consumed, even when its vCPU backend does not distinguish the modes.
72#[derive(Debug, Clone, Copy, PartialEq, Eq)]
73pub enum InterruptTriggerMode {
74    /// Edge-triggered interrupt.
75    EdgeTriggered,
76    /// Level-triggered interrupt.
77    LevelTriggered,
78}
79
80/// Identifier of an interrupt line within a virtual machine.
81#[derive(Clone, Copy, Debug, Eq, PartialEq)]
82pub struct IrqLineId(pub usize);
83
84/// The maximum number of virtual CPUs supported in a virtual machine.
85pub const MAX_VCPU_NUM: usize = 64;
86
87/// A set of virtual CPUs.
88pub type VCpuSet = ax_cpumask::CpuMask<MAX_VCPU_NUM>;
89
90/// Host virtual address.
91pub type HostVirtAddr = VirtAddr;
92
93/// Host physical address.
94pub type HostPhysAddr = PhysAddr;
95
96/// Architecture-specific nested paging configuration selected by AxVM.
97#[derive(Debug, Clone, Copy, PartialEq, Eq)]
98pub struct NestedPagingConfig {
99    /// Root physical address of the nested page table.
100    pub root_paddr: HostPhysAddr,
101    /// Number of page-table levels.
102    pub levels: usize,
103    /// Guest physical address width in bits.
104    pub gpa_bits: usize,
105    /// Architecture-specific hardware mode encoding.
106    pub mode: usize,
107}
108
109impl NestedPagingConfig {
110    /// Creates a nested paging configuration.
111    pub const fn new(
112        root_paddr: HostPhysAddr,
113        levels: usize,
114        gpa_bits: usize,
115        mode: usize,
116    ) -> Self {
117        Self {
118            root_paddr,
119            levels,
120            gpa_bits,
121            mode,
122        }
123    }
124}
125
126def_usize_addr! {
127    /// Guest virtual address.
128    pub type GuestVirtAddr;
129
130    /// Guest physical address.
131    pub type GuestPhysAddr;
132}
133
134def_usize_addr_formatter! {
135    GuestVirtAddr = "GVA:{}";
136    GuestPhysAddr = "GPA:{}";
137}
138
139/// Guest virtual address range.
140pub type GuestVirtAddrRange = AddrRange<GuestVirtAddr>;
141
142/// Guest physical address range.
143pub type GuestPhysAddrRange = AddrRange<GuestPhysAddr>;
144
145/// The width of a guest bus access.
146///
147/// The term "word" follows the x86 convention and means 16 bits.
148#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
149pub enum AccessWidth {
150    /// 8-bit access.
151    Byte,
152    /// 16-bit access.
153    Word,
154    /// 32-bit access.
155    Dword,
156    /// 64-bit access.
157    Qword,
158}
159
160impl TryFrom<usize> for AccessWidth {
161    type Error = ();
162
163    fn try_from(value: usize) -> Result<Self, Self::Error> {
164        match value {
165            1 => Ok(Self::Byte),
166            2 => Ok(Self::Word),
167            4 => Ok(Self::Dword),
168            8 => Ok(Self::Qword),
169            _ => Err(()),
170        }
171    }
172}
173
174impl From<AccessWidth> for usize {
175    fn from(width: AccessWidth) -> usize {
176        match width {
177            AccessWidth::Byte => 1,
178            AccessWidth::Word => 2,
179            AccessWidth::Dword => 4,
180            AccessWidth::Qword => 8,
181        }
182    }
183}
184
185impl AccessWidth {
186    /// Returns the size of this access in bytes.
187    pub fn size(&self) -> usize {
188        (*self).into()
189    }
190
191    /// Returns the bit range covered by this access.
192    pub fn bits_range(&self) -> core::ops::Range<usize> {
193        match self {
194            AccessWidth::Byte => 0..8,
195            AccessWidth::Word => 0..16,
196            AccessWidth::Dword => 0..32,
197            AccessWidth::Qword => 0..64,
198        }
199    }
200}
201
202/// The port number of an x86 I/O operation.
203#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
204pub struct Port(pub u16);
205
206impl Port {
207    /// Creates a new [`Port`].
208    pub const fn new(port: u16) -> Self {
209        Self(port)
210    }
211
212    /// Returns the raw port number.
213    pub const fn number(&self) -> u16 {
214        self.0
215    }
216}
217
218impl LowerHex for Port {
219    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
220        write!(f, "Port({:#x})", self.0)
221    }
222}
223
224impl UpperHex for Port {
225    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
226        write!(f, "Port({:#X})", self.0)
227    }
228}
229
230impl Debug for Port {
231    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
232        write!(f, "Port({})", self.0)
233    }
234}
235
236/// A system register address.
237#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
238pub struct SysRegAddr(pub usize);
239
240impl SysRegAddr {
241    /// Creates a new [`SysRegAddr`].
242    pub const fn new(addr: usize) -> Self {
243        Self(addr)
244    }
245
246    /// Returns the raw register address.
247    pub const fn addr(&self) -> usize {
248        self.0
249    }
250}
251
252impl From<usize> for SysRegAddr {
253    fn from(value: usize) -> Self {
254        Self(value)
255    }
256}
257
258impl LowerHex for SysRegAddr {
259    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
260        write!(f, "SysRegAddr({:#x})", self.0)
261    }
262}
263
264impl UpperHex for SysRegAddr {
265    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
266        write!(f, "SysRegAddr({:#X})", self.0)
267    }
268}
269
270impl Debug for SysRegAddr {
271    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
272        write!(f, "SysRegAddr({})", self.0)
273    }
274}
275
276/// Information about a nested guest page-table fault.
277#[derive(Debug)]
278pub struct NestedPageFaultInfo {
279    /// Access type that caused the nested page fault.
280    pub access_flags: MappingFlags,
281    /// Guest physical address that caused the nested page fault.
282    pub fault_guest_paddr: GuestPhysAddr,
283}
284
285/// Legacy/common normalized VM event.
286///
287/// New AxVM architecture backends should expose their raw VM-exit type through
288/// [`VmArchVcpuOps::Exit`] and handle it inside their `axvm::arch` module.
289/// This enum remains for compatibility and as a transitional normalized event
290/// shape for backends that have not split out an architecture-owned exit enum.
291#[non_exhaustive]
292#[derive(Debug)]
293pub enum VmExit {
294    /// A guest instruction triggered a hypercall to the hypervisor.
295    Hypercall {
296        /// Hypercall number.
297        nr: u64,
298        /// Hypercall arguments.
299        args: [u64; 6],
300    },
301    /// The guest performed an MMIO read.
302    MmioRead {
303        /// Guest physical address being read.
304        addr: GuestPhysAddr,
305        /// Access width.
306        width: AccessWidth,
307        /// Destination guest register.
308        reg: usize,
309        /// Destination register width.
310        reg_width: AccessWidth,
311        /// Whether the value should be sign-extended.
312        signed_ext: bool,
313    },
314    /// The guest performed an MMIO write.
315    MmioWrite {
316        /// Guest physical address being written.
317        addr: GuestPhysAddr,
318        /// Access width.
319        width: AccessWidth,
320        /// Value written by the guest.
321        data: u64,
322    },
323    /// The guest performed a system register read.
324    SysRegRead {
325        /// System register address.
326        addr: SysRegAddr,
327        /// Destination guest register.
328        reg: usize,
329    },
330    /// The guest performed a system register write.
331    SysRegWrite {
332        /// System register address.
333        addr: SysRegAddr,
334        /// Value written by the guest.
335        value: u64,
336    },
337    /// The guest performed an x86 port I/O read.
338    IoRead {
339        /// Port number.
340        port: Port,
341        /// Access width.
342        width: AccessWidth,
343    },
344    /// The guest performed an x86 port I/O write.
345    IoWrite {
346        /// Port number.
347        port: Port,
348        /// Access width.
349        width: AccessWidth,
350        /// Value written by the guest.
351        data: u64,
352    },
353    /// An external interrupt was delivered to the vCPU.
354    ExternalInterrupt {
355        /// Interrupt vector number.
356        vector: u64,
357    },
358    /// A nested page fault occurred during guest execution.
359    NestedPageFault {
360        /// Guest physical address that caused the fault.
361        addr: GuestPhysAddr,
362        /// Access type that caused the fault.
363        access_flags: MappingFlags,
364    },
365    /// The guest halted.
366    Halt,
367    /// The guest reached an idle instruction.
368    Idle,
369    /// The guest requested secondary CPU startup.
370    CpuUp {
371        /// Target CPU identifier in the architecture namespace.
372        target_cpu: u64,
373        /// Secondary entry point.
374        entry_point: GuestPhysAddr,
375        /// Secondary boot argument.
376        arg: u64,
377    },
378    /// The guest powered down one vCPU.
379    CpuDown {
380        /// Architecture power-state payload.
381        _state: u64,
382    },
383    /// The guest requested VM shutdown.
384    SystemDown,
385    /// No VMM action is required.
386    Nothing,
387    /// Hardware virtualization preemption timer expired.
388    PreemptionTimer,
389    /// The guest completed interrupt service with EOI.
390    InterruptEnd {
391        /// EOI vector, when available.
392        vector: Option<u8>,
393    },
394    /// VM entry failed.
395    FailEntry {
396        /// Architecture-specific failure code.
397        hardware_entry_failure_reason: u64,
398    },
399    /// The guest requested an IPI.
400    SendIPI {
401        /// Target CPU identifier in the architecture namespace.
402        target_cpu: u64,
403        /// Auxiliary target selector.
404        target_cpu_aux: u64,
405        /// Whether to broadcast to all CPUs except the sender.
406        send_to_all: bool,
407        /// Whether to target the current vCPU.
408        send_to_self: bool,
409        /// IPI vector.
410        vector: u64,
411    },
412}
413
414/// Architecture-specific vCPU operations consumed by AxVM.
415pub trait VmArchVcpuOps: Sized {
416    /// Architecture-specific creation configuration.
417    type CreateConfig;
418    /// Architecture-specific setup configuration.
419    type SetupConfig;
420    /// Architecture-specific VM-exit type returned by [`Self::run`].
421    type Exit: Debug;
422
423    /// Creates a new architecture-specific vCPU.
424    fn new(vm_id: VMId, vcpu_id: VCpuId, config: Self::CreateConfig) -> VmBackendResult<Self>;
425
426    /// Returns the guest-visible MPIDR encoded in a vCPU create config, if any.
427    fn guest_mpidr_from_create_config(_config: &Self::CreateConfig) -> Option<u64> {
428        None
429    }
430
431    /// Sets the guest entry point.
432    fn set_entry(&mut self, entry: GuestPhysAddr) -> VmBackendResult;
433    /// Sets the nested page table selected by AxVM.
434    fn set_nested_page_table(&mut self, config: NestedPagingConfig) -> VmBackendResult;
435    /// Completes architecture-specific setup.
436    fn setup(&mut self, config: Self::SetupConfig) -> VmBackendResult;
437    /// Runs the vCPU until an architecture-specific VM exit.
438    fn run(&mut self) -> VmBackendResult<Self::Exit>;
439    /// Binds the vCPU to the current physical CPU.
440    fn bind(&mut self) -> VmBackendResult;
441    /// Unbinds the vCPU from the current physical CPU.
442    fn unbind(&mut self) -> VmBackendResult;
443    /// Sets a general-purpose register.
444    fn set_gpr(&mut self, reg: usize, val: usize);
445    /// Decodes an architecture-specific memory fault as a legacy normalized
446    /// MMIO event when possible.
447    ///
448    /// This is kept as a transition helper for backends that still route
449    /// device faults through [`VmExit`]. New raw vCPU exits should use
450    /// [`Self::Exit`] and be handled in the architecture-local AxVM adapter.
451    fn decode_mmio_fault(
452        &mut self,
453        _fault_addr: GuestPhysAddr,
454        _access_flags: MappingFlags,
455    ) -> Option<VmExit> {
456        None
457    }
458    /// Injects an interrupt into the vCPU.
459    fn inject_interrupt(&mut self, vector: usize) -> VmBackendResult;
460    /// Injects an interrupt with trigger-mode metadata.
461    ///
462    /// The compatibility default delegates edge-triggered interrupts to
463    /// [`Self::inject_interrupt`]. Backends must override this method to
464    /// support level-triggered injection.
465    fn inject_interrupt_with_trigger(
466        &mut self,
467        vector: usize,
468        trigger: InterruptTriggerMode,
469    ) -> VmBackendResult {
470        match trigger {
471            InterruptTriggerMode::EdgeTriggered => self.inject_interrupt(vector),
472            InterruptTriggerMode::LevelTriggered => Err(VmBackendError::Unsupported),
473        }
474    }
475    /// Processes a guest EOI and returns an external EOI vector when needed.
476    fn handle_eoi(&mut self) -> Option<u8> {
477        None
478    }
479    /// Sets the guest return value.
480    fn set_return_value(&mut self, val: usize);
481}
482
483/// Architecture-specific per-CPU virtualization state consumed by AxVM.
484pub trait VmArchPerCpuOps: Sized {
485    /// Creates a new per-CPU state.
486    fn new(cpu_id: usize) -> VmBackendResult<Self>;
487    /// Whether virtualization is enabled on the current CPU.
488    fn is_enabled(&self) -> bool;
489    /// Enables virtualization on the current CPU.
490    fn hardware_enable(&mut self) -> VmBackendResult;
491    /// Disables virtualization on the current CPU.
492    fn hardware_disable(&mut self) -> VmBackendResult;
493    /// Returns the max guest page table levels supported by this architecture.
494    fn max_guest_page_table_levels(&self) -> usize {
495        4
496    }
497    /// Returns the guest physical address width supported by this CPU.
498    fn guest_phys_addr_bits(&self) -> usize {
499        match self.max_guest_page_table_levels() {
500            0..=3 => 39,
501            _ => 48,
502        }
503    }
504    /// Returns the architectural counter frequency recorded on this CPU.
505    ///
506    /// Architectures without an ARM-style shared counter return `None`.
507    fn timer_frequency_hz(&self) -> Option<u64> {
508        None
509    }
510}
511
512/// Execution state of an AxVM-owned vCPU wrapper.
513#[derive(Clone, Copy, Debug, PartialEq, Eq)]
514pub enum VmVcpuState {
515    /// Invalid state.
516    Invalid  = 0,
517    /// Initial state after vCPU creation.
518    Created  = 1,
519    /// vCPU is initialized and free.
520    Free     = 2,
521    /// vCPU is bound and ready to run.
522    Ready    = 3,
523    /// vCPU is currently running.
524    Running  = 4,
525    /// vCPU is blocked.
526    Blocked  = 5,
527    /// vCPU is reserved by PSCI CPU_ON and not yet runnable.
528    Starting = 6,
529}
530
531/// A part of `AxVMConfig`, which represents guest VM type.
532#[derive(Default, Clone, Copy, PartialEq, Eq, Debug)]
533pub enum VMType {
534    /// Host VM, used for boot from Linux like Jailhouse do, named "type1.5".
535    VMTHostVM = 0,
536    /// Guest RTOS, generally a simple guest OS with most of the resource passthrough.
537    #[default]
538    VMTRTOS   = 1,
539    /// Guest Linux, generally a full-featured guest OS with complicated device emulation requirements.
540    VMTLinux  = 2,
541}
542
543impl From<usize> for VMType {
544    fn from(value: usize) -> Self {
545        match value {
546            0 => Self::VMTHostVM,
547            1 => Self::VMTRTOS,
548            2 => Self::VMTLinux,
549            _ => Self::default(),
550        }
551    }
552}
553
554impl From<VMType> for usize {
555    fn from(value: VMType) -> Self {
556        value as usize
557    }
558}
559
560/// Guest physical address space population policy.
561#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
562pub enum AddressSpacePolicy {
563    /// Start from an empty guest physical address space and map only explicit
564    /// guest memory, boot-description regions, and explicitly configured
565    /// passthrough resources.
566    #[default]
567    Virtualized,
568    /// Start from a host-physical identity passthrough address space, then
569    /// punch holes for guest memory, boot-description regions, emulated
570    /// devices, and reserved ranges.
571    Passthrough,
572}
573
574/// The type of memory mapping used for VM memory regions.
575#[derive(Debug, Default, Clone, PartialEq, Eq)]
576#[repr(u8)]
577pub enum VmMemMappingType {
578    /// The memory region is allocated by the VM monitor.
579    #[default]
580    MapAlloc     = 0,
581    /// The memory region is identical to the host physical memory region.
582    MapIdentical = 1,
583    /// The memory region is reserved memory for the guest OS.
584    MapReserved  = 2,
585}
586
587/// Configuration for a virtual machine memory region.
588#[derive(Debug, Default, Clone)]
589pub struct VmMemConfig {
590    /// The start address of the memory region in GPA (Guest Physical Address).
591    pub gpa: usize,
592    /// The size of the memory region in bytes.
593    pub size: usize,
594    /// The mappings flags of the memory region.
595    pub flags: usize,
596    /// The type of memory mapping.
597    pub map_type: VmMemMappingType,
598}
599
600/// One host-firmware device assignment normalized before VM planning.
601#[derive(Debug, Default, Clone, PartialEq)]
602pub struct HostDeviceAssignment {
603    /// Stable firmware path or architecture-owned assignment name.
604    pub name: String,
605    /// The base GPA (Guest Physical Address) of the device.
606    pub base_gpa: usize,
607    /// The base HPA (Host Physical Address) of the device.
608    pub base_hpa: usize,
609    /// The address length of the device.
610    pub length: usize,
611}
612
613/// One architecture-owned host address assignment without a firmware node.
614#[derive(Debug, Default, Clone, PartialEq)]
615pub struct HostAddressAssignment {
616    /// The base GPA (Guest Physical Address).
617    pub base_gpa: usize,
618    /// The address length.
619    pub length: usize,
620}
621
622/// A guest physical address range reserved from default passthrough mapping.
623#[derive(Debug, Default, Clone, PartialEq, Eq)]
624pub struct ReservedAddressConfig {
625    /// The base GPA (Guest Physical Address).
626    pub base_gpa: usize,
627    /// The address length.
628    pub length: usize,
629}
630
631/// One architecture-owned host I/O port assignment.
632#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
633pub struct HostPortAssignment {
634    /// The first host I/O port number.
635    pub base: u16,
636    /// The number of ports in this range.
637    pub length: u16,
638}
639
640/// Describes how a guest VM should enter its boot image.
641#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
642pub enum VMBootProtocol {
643    /// Enter the configured kernel entry directly without a firmware image.
644    #[default]
645    Direct,
646    /// Use the legacy x86 axvm-bios/multiboot trampoline.
647    Multiboot,
648    /// Load an external UEFI firmware image and enter it without multiboot patching.
649    Uefi,
650}
651
652#[cfg(test)]
653mod tests {
654    use super::*;
655
656    struct MockPerCpu {
657        enabled: bool,
658    }
659
660    impl VmArchPerCpuOps for MockPerCpu {
661        fn new(_cpu_id: usize) -> VmBackendResult<Self> {
662            Ok(Self { enabled: false })
663        }
664
665        fn is_enabled(&self) -> bool {
666            self.enabled
667        }
668
669        fn hardware_enable(&mut self) -> VmBackendResult {
670            self.enabled = true;
671            Ok(())
672        }
673
674        fn hardware_disable(&mut self) -> VmBackendResult {
675            self.enabled = false;
676            Ok(())
677        }
678    }
679
680    #[derive(Debug, PartialEq, Eq)]
681    enum MockExit {
682        SysRegRead { reg: usize },
683    }
684
685    struct MockVcpu;
686
687    impl VmArchVcpuOps for MockVcpu {
688        type CreateConfig = ();
689        type SetupConfig = ();
690        type Exit = MockExit;
691
692        fn new(
693            _vm_id: VMId,
694            _vcpu_id: VCpuId,
695            _config: Self::CreateConfig,
696        ) -> VmBackendResult<Self> {
697            Ok(Self)
698        }
699
700        fn set_entry(&mut self, _entry: GuestPhysAddr) -> VmBackendResult {
701            Ok(())
702        }
703
704        fn set_nested_page_table(&mut self, _config: NestedPagingConfig) -> VmBackendResult {
705            Ok(())
706        }
707
708        fn setup(&mut self, _config: Self::SetupConfig) -> VmBackendResult {
709            Ok(())
710        }
711
712        fn run(&mut self) -> VmBackendResult<Self::Exit> {
713            Ok(MockExit::SysRegRead { reg: 2 })
714        }
715
716        fn bind(&mut self) -> VmBackendResult {
717            Ok(())
718        }
719
720        fn unbind(&mut self) -> VmBackendResult {
721            Ok(())
722        }
723
724        fn set_gpr(&mut self, _reg: usize, _val: usize) {}
725
726        fn inject_interrupt(&mut self, _vector: usize) -> VmBackendResult {
727            Ok(())
728        }
729
730        fn inject_interrupt_with_trigger(
731            &mut self,
732            _vector: usize,
733            _trigger: InterruptTriggerMode,
734        ) -> VmBackendResult {
735            Ok(())
736        }
737
738        fn set_return_value(&mut self, _val: usize) {}
739    }
740
741    #[test]
742    fn vcpu_protocol_lives_in_axvm_types() {
743        let mut percpu = MockPerCpu::new(0).unwrap();
744        assert!(!percpu.is_enabled());
745        percpu.hardware_enable().unwrap();
746        assert!(percpu.is_enabled());
747
748        let mut vcpu = MockVcpu::new(1, 0, ()).unwrap();
749        vcpu.set_entry(GuestPhysAddr::from(0x8020_0000)).unwrap();
750        vcpu.set_nested_page_table(NestedPagingConfig::new(
751            HostPhysAddr::from(0x1000),
752            4,
753            48,
754            0,
755        ))
756        .unwrap();
757        vcpu.setup(()).unwrap();
758        assert!(matches!(
759            vcpu.run().unwrap(),
760            MockExit::SysRegRead { reg: 2 }
761        ));
762    }
763
764    #[test]
765    fn vm_exit_keeps_access_width_and_state_types() {
766        let state = VmVcpuState::Created;
767        assert_eq!(state as u8, 1);
768
769        let exit = VmExit::MmioRead {
770            addr: GuestPhysAddr::from(0x1000),
771            width: AccessWidth::Dword,
772            reg: 3,
773            reg_width: AccessWidth::Qword,
774            signed_ext: true,
775        };
776        assert!(matches!(
777            exit,
778            VmExit::MmioRead {
779                width: AccessWidth::Dword,
780                reg: 3,
781                ..
782            }
783        ));
784    }
785}