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    ///
439    /// The caller pins the backend and masks local IRQs across this call. Guest
440    /// execution must still allow host interrupts to force an exit independently
441    /// of the guest interrupt mask. Before returning, restore the host trap
442    /// environment and complete any acknowledged host IRQ on this CPU (or
443    /// transfer its token to the interrupt controller's retained route). Leave
444    /// unacknowledged sources pending for normal host IRQ entry when the caller
445    /// restores IRQs. The returned exit must not require replaying a host IRQ
446    /// snapshot after the backend is unloaded.
447    fn run(&mut self) -> VmBackendResult<Self::Exit>;
448    /// Binds the vCPU to the current physical CPU.
449    fn bind(&mut self) -> VmBackendResult;
450    /// Unbinds the vCPU from the current physical CPU.
451    fn unbind(&mut self) -> VmBackendResult;
452    /// Sets a general-purpose register.
453    fn set_gpr(&mut self, reg: usize, val: usize);
454    /// Decodes an architecture-specific memory fault as a legacy normalized
455    /// MMIO event when possible.
456    ///
457    /// This is kept as a transition helper for backends that still route
458    /// device faults through [`VmExit`]. New raw vCPU exits should use
459    /// [`Self::Exit`] and be handled in the architecture-local AxVM adapter.
460    fn decode_mmio_fault(
461        &mut self,
462        _fault_addr: GuestPhysAddr,
463        _access_flags: MappingFlags,
464    ) -> Option<VmExit> {
465        None
466    }
467    /// Injects an interrupt into the vCPU.
468    fn inject_interrupt(&mut self, vector: usize) -> VmBackendResult;
469    /// Injects an interrupt with trigger-mode metadata.
470    ///
471    /// The compatibility default delegates edge-triggered interrupts to
472    /// [`Self::inject_interrupt`]. Backends must override this method to
473    /// support level-triggered injection.
474    fn inject_interrupt_with_trigger(
475        &mut self,
476        vector: usize,
477        trigger: InterruptTriggerMode,
478    ) -> VmBackendResult {
479        match trigger {
480            InterruptTriggerMode::EdgeTriggered => self.inject_interrupt(vector),
481            InterruptTriggerMode::LevelTriggered => Err(VmBackendError::Unsupported),
482        }
483    }
484    /// Processes a guest EOI and returns an external EOI vector when needed.
485    fn handle_eoi(&mut self) -> Option<u8> {
486        None
487    }
488    /// Sets the guest return value.
489    fn set_return_value(&mut self, val: usize);
490}
491
492/// Architecture-specific per-CPU virtualization state consumed by AxVM.
493pub trait VmArchPerCpuOps: Sized {
494    /// Creates a new per-CPU state.
495    fn new(cpu_id: usize) -> VmBackendResult<Self>;
496    /// Whether virtualization is enabled on the current CPU.
497    fn is_enabled(&self) -> bool;
498    /// Enables virtualization on the current CPU.
499    fn hardware_enable(&mut self) -> VmBackendResult;
500    /// Disables virtualization on the current CPU.
501    fn hardware_disable(&mut self) -> VmBackendResult;
502    /// Returns the max guest page table levels supported by this architecture.
503    fn max_guest_page_table_levels(&self) -> usize {
504        4
505    }
506    /// Returns the guest physical address width supported by this CPU.
507    fn guest_phys_addr_bits(&self) -> usize {
508        match self.max_guest_page_table_levels() {
509            0..=3 => 39,
510            _ => 48,
511        }
512    }
513    /// Returns the architectural counter frequency recorded on this CPU.
514    ///
515    /// Architectures without an ARM-style shared counter return `None`.
516    fn timer_frequency_hz(&self) -> Option<u64> {
517        None
518    }
519}
520
521/// Execution state of an AxVM-owned vCPU wrapper.
522#[derive(Clone, Copy, Debug, PartialEq, Eq)]
523pub enum VmVcpuState {
524    /// Invalid state.
525    Invalid  = 0,
526    /// Initial state after vCPU creation.
527    Created  = 1,
528    /// vCPU is initialized and free.
529    Free     = 2,
530    /// vCPU is bound and ready to run.
531    Ready    = 3,
532    /// vCPU is currently running.
533    Running  = 4,
534    /// vCPU is blocked.
535    Blocked  = 5,
536    /// vCPU is reserved by PSCI CPU_ON and not yet runnable.
537    Starting = 6,
538}
539
540/// A part of `AxVMConfig`, which represents guest VM type.
541#[derive(Default, Clone, Copy, PartialEq, Eq, Debug)]
542pub enum VMType {
543    /// Host VM, used for boot from Linux like Jailhouse do, named "type1.5".
544    VMTHostVM = 0,
545    /// Guest RTOS, generally a simple guest OS with most of the resource passthrough.
546    #[default]
547    VMTRTOS   = 1,
548    /// Guest Linux, generally a full-featured guest OS with complicated device emulation requirements.
549    VMTLinux  = 2,
550}
551
552impl From<usize> for VMType {
553    fn from(value: usize) -> Self {
554        match value {
555            0 => Self::VMTHostVM,
556            1 => Self::VMTRTOS,
557            2 => Self::VMTLinux,
558            _ => Self::default(),
559        }
560    }
561}
562
563impl From<VMType> for usize {
564    fn from(value: VMType) -> Self {
565        value as usize
566    }
567}
568
569/// Guest physical address space population policy.
570#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
571pub enum AddressSpacePolicy {
572    /// Start from an empty guest physical address space and map only explicit
573    /// guest memory, boot-description regions, and explicitly configured
574    /// passthrough resources.
575    #[default]
576    Virtualized,
577    /// Start from a host-physical identity passthrough address space, then
578    /// punch holes for guest memory, boot-description regions, emulated
579    /// devices, and reserved ranges.
580    Passthrough,
581}
582
583/// The type of memory mapping used for VM memory regions.
584#[derive(Debug, Default, Clone, PartialEq, Eq)]
585#[repr(u8)]
586pub enum VmMemMappingType {
587    /// The memory region is allocated by the VM monitor.
588    #[default]
589    MapAlloc     = 0,
590    /// The memory region is identical to the host physical memory region.
591    MapIdentical = 1,
592    /// The memory region is reserved memory for the guest OS.
593    MapReserved  = 2,
594}
595
596/// Configuration for a virtual machine memory region.
597#[derive(Debug, Default, Clone)]
598pub struct VmMemConfig {
599    /// The start address of the memory region in GPA (Guest Physical Address).
600    pub gpa: usize,
601    /// The size of the memory region in bytes.
602    pub size: usize,
603    /// The mappings flags of the memory region.
604    pub flags: usize,
605    /// The type of memory mapping.
606    pub map_type: VmMemMappingType,
607}
608
609/// One host-firmware device assignment normalized before VM planning.
610#[derive(Debug, Default, Clone, PartialEq)]
611pub struct HostDeviceAssignment {
612    /// Stable firmware path or architecture-owned assignment name.
613    pub name: String,
614    /// The base GPA (Guest Physical Address) of the device.
615    pub base_gpa: usize,
616    /// The base HPA (Host Physical Address) of the device.
617    pub base_hpa: usize,
618    /// The address length of the device.
619    pub length: usize,
620}
621
622/// One architecture-owned host address assignment without a firmware node.
623#[derive(Debug, Default, Clone, PartialEq)]
624pub struct HostAddressAssignment {
625    /// The base GPA (Guest Physical Address).
626    pub base_gpa: usize,
627    /// The address length.
628    pub length: usize,
629}
630
631/// A guest physical address range reserved from default passthrough mapping.
632#[derive(Debug, Default, Clone, PartialEq, Eq)]
633pub struct ReservedAddressConfig {
634    /// The base GPA (Guest Physical Address).
635    pub base_gpa: usize,
636    /// The address length.
637    pub length: usize,
638}
639
640/// One architecture-owned host I/O port assignment.
641#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
642pub struct HostPortAssignment {
643    /// The first host I/O port number.
644    pub base: u16,
645    /// The number of ports in this range.
646    pub length: u16,
647}
648
649/// Describes how a guest VM should enter its boot image.
650#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
651pub enum VMBootProtocol {
652    /// Enter the configured kernel entry directly without a firmware image.
653    #[default]
654    Direct,
655    /// Use the legacy x86 axvm-bios/multiboot trampoline.
656    Multiboot,
657    /// Load an external UEFI firmware image and enter it without multiboot patching.
658    Uefi,
659}