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}