Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

102 changes: 102 additions & 0 deletions virtualization/axvm/src/irq/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
// Copyright 2025 The Axvisor Team
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! VM-owned interrupt line routing.

use alloc::sync::Arc;

use ax_errno::{AxResult, ax_err};
use axdevice::IrqResolver;
use axdevice_base::{InterruptTriggerMode, IrqLine, IrqLineId, IrqSink};
use axvm_types::VMInterruptMode;

/// Resolves device interrupt lines against one VM's interrupt backend.
///
/// The fabric owns only the backend capability. It never owns or references the
/// containing VM, so devices can retain [`IrqLine`] objects without creating an
/// `AxVM -> device -> IRQ -> AxVM` reference cycle.
pub struct InterruptFabric {
mode: VMInterruptMode,
sink: Option<Arc<dyn IrqSink>>,
}

impl InterruptFabric {
/// Creates a fabric without an interrupt backend.
pub const fn new(mode: VMInterruptMode) -> Self {
Self { mode, sink: None }
}

/// Creates a fabric that routes lines to `sink`.
pub fn with_sink(mode: VMInterruptMode, sink: Arc<dyn IrqSink>) -> AxResult<Self> {
if mode == VMInterruptMode::NoIrq {
return ax_err!(
InvalidInput,
"a VM configured with interrupt_mode=no_irq cannot install an IRQ backend"
);
}
Ok(Self {
mode,
sink: Some(sink),
})
}

/// Returns the VM interrupt mode associated with this fabric.
pub const fn mode(&self) -> VMInterruptMode {
self.mode
}

/// Returns whether this fabric has an interrupt backend.
pub const fn has_backend(&self) -> bool {
self.sink.is_some()
}

pub(crate) fn validate_mode(&self, mode: VMInterruptMode) -> AxResult {
if self.mode != mode {
return ax_err!(
InvalidInput,
format_args!(
"interrupt fabric mode {:?} does not match VM interrupt mode {:?}",
self.mode, mode
)
);
}
Ok(())
}
}

impl Default for InterruptFabric {
fn default() -> Self {
Self::new(VMInterruptMode::NoIrq)
}
}

impl IrqResolver for InterruptFabric {
fn resolve_irq(&self, line: usize, trigger: InterruptTriggerMode) -> AxResult<IrqLine> {
let Some(sink) = &self.sink else {
if self.mode == VMInterruptMode::NoIrq {
return ax_err!(
InvalidInput,
format_args!("cannot resolve IRQ line {line}: the VM interrupt mode is NoIrq")
);
}
return ax_err!(
Unsupported,
format_args!(
"cannot resolve IRQ line {line}: no VM interrupt backend is installed"
)
);
};
Ok(IrqLine::new(IrqLineId(line), trigger, sink.clone()))
}
}
2 changes: 2 additions & 0 deletions virtualization/axvm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ extern crate log;
mod arch;
mod cache;
mod host;
pub mod irq;
mod manager;
mod percpu;
mod runtime;
Expand All @@ -45,6 +46,7 @@ pub(crate) use host::{
paging::HostPagingHandler,
task::{AxTaskExt, AxTaskRef, TaskInner, WaitQueue, WaitQueueHandle as HostWaitQueueHandle},
};
pub use irq::InterruptFabric;
pub use manager::{
AxvmRuntime, current_vcpu_id, current_vm_id, get_vm_by_id, get_vm_list,
inject_current_vcpu_interrupt, register_vm,
Expand Down
53 changes: 42 additions & 11 deletions virtualization/axvm/src/vm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@ use ax_errno::{AxError, AxResult, ax_err, ax_err_type};
use ax_kspin::SpinNoIrq as Mutex;
use ax_memory_addr::{align_down_4k, align_up_4k};
use axaddrspace::{AddrSpace, MappingFlags};
use axdevice::{AxVmDeviceConfig, AxVmDevices};
use axdevice::{
AxVmDeviceConfig, AxVmDevices, DeviceBuildContext, DeviceFactoryRegistry,
register_builtin_factories,
};
use axdevice_base::AccessWidth;
use axvcpu::{AxVCpu, AxVCpuExitReason};
#[cfg(target_arch = "x86_64")]
Expand All @@ -37,6 +40,7 @@ use crate::vcpu::get_sysreg_device;
use crate::{
config::{AxVMConfig, PhysCpuList, VMInterruptMode},
host::paging::{HostPagingHandler, virt_to_phys},
irq::InterruptFabric,
vcpu::AxArchVCpuImpl,
};

Expand Down Expand Up @@ -72,6 +76,7 @@ struct AxVMInnerConst {
phys_cpu_ls: PhysCpuList,
vcpu_list: Box<[AxVCpuRef]>,
devices: AxVmDevices,
interrupt_fabric: InterruptFabric,
}

unsafe impl Send for AxVMInnerConst {}
Expand Down Expand Up @@ -213,7 +218,19 @@ impl AxVM {

/// Sets up the VM before booting.
pub fn init(&self) -> AxResult {
let mut factories = DeviceFactoryRegistry::new();
register_builtin_factories(&mut factories)?;
self.init_with_factories(&factories, InterruptFabric::new(self.interrupt_mode()))
}

/// Sets up the VM with explicit device factories and an interrupt fabric.
pub fn init_with_factories(
&self,
factories: &DeviceFactoryRegistry,
interrupt_fabric: InterruptFabric,
) -> AxResult {
let mut inner_mut = self.inner_mut.lock();
interrupt_fabric.validate_mode(inner_mut.config.interrupt_mode())?;

let dtb_addr = inner_mut.config.image_config().dtb_load_gpa;
let vcpu_id_pcpu_sets = inner_mut.config.phys_cpu_ls.get_vcpu_affinities_pcpu_ids();
Expand Down Expand Up @@ -327,9 +344,16 @@ impl AxVM {
)?;

#[cfg_attr(not(target_arch = "aarch64"), expect(unused_mut))]
let mut devices = axdevice::AxVmDevices::new(AxVmDeviceConfig {
emu_configs: inner_mut.config.emu_devices().to_vec(),
})?;
let mut devices = {
let build_context = DeviceBuildContext::new(&interrupt_fabric);
axdevice::AxVmDevices::build_with_factories(
AxVmDeviceConfig {
emu_configs: inner_mut.config.emu_devices().to_vec(),
},
factories,
&build_context,
)?
};

#[cfg(target_arch = "aarch64")]
{
Expand Down Expand Up @@ -372,14 +396,8 @@ impl AxVM {
}
}

self.inner_const.call_once(|| AxVMInnerConst {
phys_cpu_ls: inner_mut.config.phys_cpu_ls.clone(),
vcpu_list: vcpu_list.into_boxed_slice(),
devices,
});

// Setup VCpus.
for vcpu in self.vcpu_list() {
for vcpu in &vcpu_list {
#[cfg(target_arch = "aarch64")]
let setup_config = {
let passthrough = inner_mut.config.interrupt_mode() == VMInterruptMode::Passthrough;
Expand Down Expand Up @@ -426,6 +444,14 @@ impl AxVM {
setup_config,
)?;
}

self.inner_const.call_once(|| AxVMInnerConst {
phys_cpu_ls: inner_mut.config.phys_cpu_ls.clone(),
vcpu_list: vcpu_list.into_boxed_slice(),
devices,
interrupt_fabric,
});

info!("VM setup: id={}", self.id());
Ok(())
}
Expand Down Expand Up @@ -558,6 +584,11 @@ impl AxVM {
&self.inner_const().devices
}

/// Returns this VM's interrupt fabric.
pub fn interrupt_fabric(&self) -> &InterruptFabric {
&self.inner_const().interrupt_fabric
}

/// Run a vCPU according to the given vcpu_id.
///
/// ## Arguments
Expand Down
2 changes: 2 additions & 0 deletions virtualization/test_crates/virtualization-tests/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ repository.workspace = true
ax-crate-interface.workspace = true
ax-errno.workspace = true
ax-memory-addr.workspace = true
ax-plat.workspace = true
axdevice.workspace = true
axdevice_base.workspace = true
axvm.workspace = true
axvm-types.workspace = true
x86_vlapic.workspace = true
Loading
Loading