axvm 0.5.18

Virtual Machine resource management crate for ArceOS's hypervisor variant.
use alloc::vec::Vec;

use ax_crate_interface::impl_interface;
use ax_errno::AxResult;
use ax_memory_addr::{PhysAddr, VirtAddr};
use riscv_vcpu::{GprIndex as RiscvGprIndex, host::RiscvVcpuHostIf};
use riscv_vplic::host::RiscvVplicHostIf;

use super::{ArchOps, VcpuCreateContext, VcpuSetupContext, default_vcpu_affinities};
use crate::host::{HostMemory, default_host};

pub(crate) struct Riscv64Arch;

impl ArchOps for Riscv64Arch {
    type VCpu = riscv_vcpu::RISCVVCpu;
    type PerCpu = riscv_vcpu::RISCVPerCpu;
    type VcpuCreateState = ();

    fn has_hardware_support() -> bool {
        riscv_vcpu::has_hardware_support()
    }

    fn new_vcpu_create_state(
        _vcpu_mappings: &[(usize, Option<usize>, usize)],
    ) -> AxResult<Self::VcpuCreateState> {
        Ok(())
    }

    fn build_vcpu_create_config(
        _state: &Self::VcpuCreateState,
        ctx: VcpuCreateContext,
    ) -> AxResult<<Self::VCpu as axvm_types::VmArchVcpuOps>::CreateConfig> {
        Ok(riscv_vcpu::RISCVVCpuCreateConfig {
            hart_id: ctx.vcpu_id,
            dtb_addr: ctx.dtb_addr.unwrap_or_default().as_usize(),
        })
    }

    fn build_vcpu_setup_config(
        _ctx: VcpuSetupContext<'_>,
    ) -> AxResult<<Self::VCpu as axvm_types::VmArchVcpuOps>::SetupConfig> {
        Ok(Default::default())
    }

    fn register_platform_irq_injector() {
        register_platform_irq_injector();
    }

    fn vcpu_affinities(
        cpu_num: usize,
        phys_cpu_ids: Option<&[usize]>,
        phys_cpu_sets: Option<&[usize]>,
    ) -> Vec<(usize, Option<usize>, usize)> {
        let mut vcpus = default_vcpu_affinities(cpu_num, phys_cpu_ids, phys_cpu_sets);
        if phys_cpu_sets.is_none() {
            for (_, mask, phys_id) in &mut vcpus {
                *mask = Some(1 << *phys_id);
            }
        }
        vcpus
    }

    fn set_vcpu_on_args(vcpu: &crate::vm::AxVCpuRef, vcpu_id: usize, arg: usize) {
        vcpu.set_gpr(RiscvGprIndex::A0 as usize, vcpu_id);
        vcpu.set_gpr(RiscvGprIndex::A1 as usize, arg);
    }

    fn set_cpu_up_success(vcpu: &crate::vm::AxVCpuRef) {
        vcpu.set_gpr(RiscvGprIndex::A0 as usize, 0);
    }

    fn set_io_read_result(vcpu: &crate::vm::AxVCpuRef, val: usize) {
        vcpu.set_gpr(RiscvGprIndex::A0 as usize, val);
    }

    fn after_external_interrupt(_vm: &crate::AxVMRef, vcpu: &crate::vm::AxVCpuRef, vector: usize) {
        vcpu.with_current_cpu_set(|| {
            crate::host::arceos::dispatch_host_irq(vector);
            vcpu.get_arch_vcpu().latch_hvip_from_hw();
        });
        crate::check_timer_events();
    }
}

struct RiscvVcpuHostIfImpl;

#[impl_interface]
impl RiscvVcpuHostIf for RiscvVcpuHostIfImpl {
    fn virt_to_phys(vaddr: VirtAddr) -> PhysAddr {
        default_host().virt_to_phys(vaddr)
    }
}

struct RiscvVplicHostIfImpl;

#[impl_interface]
impl RiscvVplicHostIf for RiscvVplicHostIfImpl {
    fn phys_to_virt(paddr: PhysAddr) -> VirtAddr {
        default_host().phys_to_virt(paddr)
    }
}

#[cfg(feature = "plat-dyn")]
fn register_platform_irq_injector() {
    axplat_dyn::register_virtual_irq_injector(inject_virtual_irq);
}

#[cfg(not(feature = "plat-dyn"))]
compile_error!("riscv64 Axvisor requires the plat-dyn feature");

#[cfg(feature = "plat-dyn")]
fn inject_virtual_irq(irq_id: usize) -> bool {
    debug!("injecting RISC-V virtual IRQ id: {irq_id}");

    let Some(vm_id) = crate::current_vm_id() else {
        warn!("cannot inject RISC-V virtual IRQ without current VM context");
        return false;
    };

    let Some(injected) = crate::manager::with_vm(vm_id, |vm| {
        if let Err(err) = vm.pulse_interrupt(irq_id) {
            warn!("failed to inject RISC-V virtual IRQ {irq_id}: {err:?}");
            return false;
        }
        true
    }) else {
        warn!("cannot inject RISC-V virtual IRQ {irq_id}: VM[{vm_id}] not found");
        return false;
    };

    injected
}