arm_vcpu 0.5.21

OS-neutral AArch64 vCPU core
Documentation
// 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.

use core::mem;

use aarch64_cpu::registers::*;

use super::host::ArmHostOps;
use crate::{
    ArmVcpuResult,
    enable::{EL2_ENABLE_STEPS, El2EnableStep},
};

/// Per-CPU AArch64 virtualization state.
#[repr(C)]
#[repr(align(4096))]
pub struct ArmPerCpu {
    /// per cpu id
    pub cpu_id: usize,
    /// The original value of `VBAR_EL2` (exception vector base) before enabling
    /// the virtualization.
    pub original_vbar_el2: u64,
    timer_frequency_hz: u64,
}

unsafe extern "C" {
    fn exception_vector_base_vcpu();
}

impl ArmPerCpu {
    /// Creates per-CPU virtualization state.
    pub fn new(cpu_id: usize) -> ArmVcpuResult<Self> {
        let timer_frequency_hz = CNTFRQ_EL0.get();
        if timer_frequency_hz == 0 {
            return Err(crate::ArmVcpuError::Unsupported);
        }
        Ok(Self {
            cpu_id,
            original_vbar_el2: 0,
            timer_frequency_hz,
        })
    }

    /// Returns whether AArch64 virtualization is enabled on the current CPU.
    pub fn is_enabled(&self) -> bool {
        HCR_EL2.is_set(HCR_EL2::VM)
    }

    /// Enables AArch64 virtualization on the current CPU.
    pub fn hardware_enable<H: ArmHostOps>(&mut self) -> ArmVcpuResult {
        // First we save origin `exception_vector_base`.
        // Safety:
        // Todo: take care of `preemption`
        self.original_vbar_el2 = VBAR_EL2.get();

        for step in EL2_ENABLE_STEPS {
            match step {
                El2EnableStep::InstallCurrentElIrqHandler => {
                    super::host::install_current_el_irq_handler::<H>();
                }
                El2EnableStep::InstallExceptionVector => {
                    VBAR_EL2.set(exception_vector_base_vcpu as *const () as usize as _);
                }
                El2EnableStep::SynchronizeContext => synchronize_context(),
                El2EnableStep::EnableVirtualization => HCR_EL2.modify(
                    HCR_EL2::VM::Enable
                        + HCR_EL2::RW::EL1IsAarch64
                        + HCR_EL2::TSC::EnableTrapEl1SmcToEl2,
                ),
            }
        }

        // Note that `ICH_HCR_EL2` is not the same as `HCR_EL2`.
        //
        // `ICH_HCR_EL2[0]` controls the virtual CPU interface operation.
        //
        // We leave it for the virtual GIC implementations to decide whether to enable it or not.
        //
        // unsafe {
        //     core::arch::asm! {
        //         "msr ich_hcr_el2, {value:x}",
        //         value = in(reg) 0,
        //     }
        // }

        Ok(())
    }

    /// Disables AArch64 virtualization on the current CPU.
    pub fn hardware_disable(&mut self) -> ArmVcpuResult {
        // Reset `VBAR_EL2` into previous value.
        // Safety:
        // Todo: take care of `preemption`
        VBAR_EL2.set(mem::take(&mut self.original_vbar_el2));

        HCR_EL2.set(HCR_EL2::VM::Disable.into());
        super::host::clear_current_el_irq_handler();
        Ok(())
    }

    /// Returns the maximum guest page table levels supported by this CPU.
    pub fn max_guest_page_table_levels(&self) -> usize {
        super::vcpu::max_gpt_level(super::vcpu::pa_bits())
    }

    /// Returns the guest physical address width supported by this CPU.
    pub fn guest_phys_addr_bits(&self) -> usize {
        super::vcpu::pa_bits()
    }

    /// Returns the architectural counter frequency recorded on this CPU.
    pub const fn timer_frequency_hz(&self) -> u64 {
        self.timer_frequency_hz
    }
}

fn synchronize_context() {
    // SAFETY: `isb` only synchronizes subsequent instruction execution on the
    // current CPU after the system-register updates performed here.
    unsafe {
        core::arch::asm!("isb", options(nostack, preserves_flags));
    }
}