use ax_sync::SpinLockIrqSaveGuard;
use super::{ControllerState, GicV3Controller, state::DeliveryRetirement};
use crate::{CpuInterfaceState, GicVcpuId, IntId, VgicError, VgicResult, backend_result};
#[must_use = "dropping the binding detaches the vCPU from its Redistributor"]
pub struct GicV3VcpuBinding {
controller: GicV3Controller,
vcpu: GicVcpuId,
}
impl core::fmt::Debug for GicV3VcpuBinding {
fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
formatter
.debug_struct("GicV3VcpuBinding")
.field("vcpu", &self.vcpu)
.field(
"spi_ownership",
&self.controller.inner.config.spi_ownership(),
)
.finish_non_exhaustive()
}
}
impl Drop for GicV3VcpuBinding {
fn drop(&mut self) {
let mut state = self.controller_state();
state.active_vcpus.remove(&self.vcpu);
state.redistributors.remove(&self.vcpu);
}
}
impl GicV3VcpuBinding {
pub(super) const fn new(controller: GicV3Controller, vcpu: GicVcpuId) -> Self {
Self { controller, vcpu }
}
pub const fn vcpu(&self) -> GicVcpuId {
self.vcpu
}
fn controller_state(&self) -> SpinLockIrqSaveGuard<'_, ControllerState> {
self.controller.inner.state.lock_irqsave()
}
pub fn load(&self) -> VgicResult {
let state = {
let mut controller = self.controller_state();
controller.redistributor(self.vcpu, "load CPU interface")?;
if !controller.active_vcpus.insert(self.vcpu) {
return Err(VgicError::ResourceConflict {
resource: "vCPU interrupt binding",
detail: alloc::format!("vCPU {} is already loaded", self.vcpu.raw()),
});
}
match controller.refill_cpu_interface(self.vcpu) {
Ok(state) => state,
Err(error) => {
let rollback = controller.rollback_cpu_interface_load(self.vcpu);
controller.active_vcpus.remove(&self.vcpu);
rollback?;
return Err(error);
}
}
};
if let Err(error) = backend_result(
self.controller
.inner
.backend
.load_cpu_interface(self.vcpu, &state),
) {
let mut controller = self.controller_state();
let rollback = controller.rollback_cpu_interface_load(self.vcpu);
controller.active_vcpus.remove(&self.vcpu);
rollback?;
return Err(error);
}
Ok(())
}
pub fn save(&self) -> VgicResult {
let mut saved = self.cpu_interface_snapshot()?;
let save_result = backend_result(
self.controller
.inner
.backend
.save_cpu_interface(self.vcpu, &mut saved),
);
let merge_result = match save_result {
Ok(()) => self
.merge_saved_state(saved, false)
.and_then(|retirements| self.apply_retirements(retirements)),
Err(error) => Err(error),
};
self.controller_state().active_vcpus.remove(&self.vcpu);
merge_result
}
pub fn synchronize(&self) -> VgicResult {
let mut saved = self.cpu_interface_snapshot()?;
backend_result(
self.controller
.inner
.backend
.save_cpu_interface(self.vcpu, &mut saved),
)?;
let retirements = self.merge_saved_state(saved, true)?;
let state = self.cpu_interface_snapshot()?;
backend_result(
self.controller
.inner
.backend
.load_cpu_interface(self.vcpu, &state),
)?;
self.apply_retirements(retirements)
}
pub fn deactivate(&self, intid: IntId) -> VgicResult {
let mut saved = {
let controller = self.controller_state();
if !controller.active_vcpus.contains(&self.vcpu) {
return Err(VgicError::InvalidStateTransition {
intid,
operation: "deactivate virtual interrupt",
detail: alloc::format!("vCPU {} is not loaded", self.vcpu.raw()),
});
}
controller
.redistributor(self.vcpu, "deactivate virtual interrupt")?
.cpu_interface()
.clone()
};
backend_result(
self.controller
.inner
.backend
.save_cpu_interface(self.vcpu, &mut saved),
)?;
let (retirements, state) = {
let mut controller = self.controller_state();
let mut retirements = controller.merge_cpu_interface(self.vcpu, saved, false)?;
if let Some(retirement) = controller.deactivate_interrupt(self.vcpu, intid)? {
retirements.push(retirement);
}
let state = controller.refill_cpu_interface(self.vcpu)?;
(retirements, state)
};
backend_result(
self.controller
.inner
.backend
.load_cpu_interface(self.vcpu, &state),
)?;
self.apply_retirements(retirements)
}
pub fn deactivate_saved(&self, intid: IntId) -> VgicResult {
let retirements = {
let mut controller = self.controller_state();
if controller.active_vcpus.contains(&self.vcpu) {
return Err(VgicError::InvalidStateTransition {
intid,
operation: "deactivate saved virtual interrupt",
detail: alloc::format!("vCPU {} is still loaded", self.vcpu.raw()),
});
}
let mut retirements = alloc::vec::Vec::new();
if let Some(retirement) = controller.deactivate_interrupt(self.vcpu, intid)? {
retirements.push(retirement);
}
controller.refill_cpu_interface(self.vcpu)?;
retirements
};
self.apply_retirements(retirements)
}
pub fn write_sgi1r(&self, value: u64) -> VgicResult {
self.controller.write_sgi1r(self.vcpu, value)
}
pub fn read_icc_control(&self) -> VgicResult<u64> {
Ok(self
.controller_state()
.redistributor(self.vcpu, "read ICC_CTLR_EL1")?
.cpu_interface()
.icc_control())
}
pub fn write_icc_control(&self, value: u64) -> VgicResult {
self.controller_state()
.redistributor_mut(self.vcpu, "write ICC_CTLR_EL1")?
.cpu_interface_mut()
.set_icc_control(value);
Ok(())
}
pub fn read_icc_priority_mask(&self) -> VgicResult<u64> {
Ok(u64::from(
self.controller_state()
.redistributor(self.vcpu, "read ICC_PMR_EL1")?
.cpu_interface()
.icc_priority_mask(),
))
}
pub fn write_icc_priority_mask(&self, value: u64) -> VgicResult {
self.controller_state()
.redistributor_mut(self.vcpu, "write ICC_PMR_EL1")?
.cpu_interface_mut()
.set_icc_priority_mask(value as u8);
Ok(())
}
pub fn read_icc_running_priority(&self) -> VgicResult<u64> {
Ok(u64::from(
self.controller_state()
.redistributor(self.vcpu, "read ICC_RPR_EL1")?
.cpu_interface()
.icc_running_priority()
.raw(),
))
}
pub fn cpu_interface_snapshot(&self) -> VgicResult<CpuInterfaceState> {
Ok(self
.controller_state()
.redistributor(self.vcpu, "snapshot CPU interface")?
.cpu_interface()
.clone())
}
pub fn has_pending_interrupt(&self) -> VgicResult<bool> {
self.controller.has_pending_interrupt(self.vcpu)
}
fn merge_saved_state(
&self,
saved: CpuInterfaceState,
refill: bool,
) -> VgicResult<alloc::vec::Vec<DeliveryRetirement>> {
self.controller_state()
.merge_cpu_interface(self.vcpu, saved, refill)
}
fn apply_retirements(
&self,
retirements: impl IntoIterator<Item = DeliveryRetirement>,
) -> VgicResult {
let mut first_error = None;
for retirement in retirements {
let result = match retirement {
DeliveryRetirement::Emulated { intid } => backend_result(
self.controller
.inner
.backend
.retire_emulated_interrupt(self.vcpu, intid),
),
DeliveryRetirement::Physical { binding } => {
self.controller.complete_physical_spi(self.vcpu, binding)
}
};
if let Err(error) = result
&& first_error.is_none()
{
first_error = Some(error);
}
}
match first_error {
Some(error) => Err(error),
None => Ok(()),
}
}
}
#[cfg(test)]
mod tests {
use alloc::sync::Arc;
use super::*;
use crate::{
GicAffinity, GicV3Config, GicV3MmioRegion, GicV3SpiOwnership, SoftwareGicV3Backend,
};
struct NoopWake;
impl crate::GicV3VcpuWake for NoopWake {
fn wake(&self) -> VgicResult {
Ok(())
}
}
#[test]
fn controller_state_access_requires_irq_save_guard() {
let config = GicV3Config::new(
GicV3SpiOwnership::AllGuestOwned,
GicV3MmioRegion::new(0x0800_0000, 0x1_0000).unwrap(),
GicV3MmioRegion::new(0x080a_0000, 0x2_0000).unwrap(),
0x2_0000,
1,
)
.unwrap();
let controller = GicV3Controller::new(config, Arc::new(SoftwareGicV3Backend)).unwrap();
let binding = controller
.attach_vcpu(
GicVcpuId::new(0),
GicAffinity::new(0, 0, 0, 0),
Arc::new(NoopWake),
)
.unwrap();
let guard = binding.controller_state();
let irq_state = ax_sync::irq_save_and_disable();
unsafe { ax_sync::irq_restore(irq_state) };
drop(guard);
assert_eq!(irq_state, 0, "controller state access left IRQs enabled");
}
}