use alloc::vec::Vec;
use axdevice_base::ItsId;
use axvm_types::AccessWidth;
use super::{ControllerState, GicV3Controller};
use crate::{
GicVcpuId, ItsAction, RegisterRegion, VgicError, VgicResult,
register::{
GITS_BASER, GITS_BASER_COUNT, GITS_CBASER, GITS_CREADR, GITS_CTLR, GITS_CWRITER, GITS_IIDR,
GITS_TYPER, GicComponent, component_id,
},
};
impl GicV3Controller {
pub fn read_distributor(&self, offset: u64, width: AccessWidth) -> VgicResult<u64> {
self.inner
.state
.lock()
.distributor
.read(offset, width, &self.inner.config)
}
pub fn write_distributor(&self, offset: u64, width: AccessWidth, value: u64) -> VgicResult {
let (wakes, physical_state_changes) = {
let mut state = self.inner.state.lock();
let physical_snapshot = state.physical_interrupt_snapshot()?;
let write = state
.distributor
.write(offset, width, value, &self.inner.config)?;
let candidates = write.into_candidates();
let mut wakes = Vec::new();
for spi in candidates {
if state.has_software_backing(spi, &self.inner.config)
&& let Some(wake) = state.queue_spi_if_deliverable(spi)?
{
wakes.push(wake);
}
}
let acknowledged_physical_spis = state
.physical_spi_acknowledged
.iter()
.filter_map(|(spi, acknowledged)| acknowledged.then_some(*spi))
.collect::<Vec<_>>();
for spi in acknowledged_physical_spis {
if let Some(wake) = state.queue_acknowledged_physical_spi_if_deliverable(spi)? {
wakes.push(wake);
}
}
let physical_state_changes =
state.physical_interrupt_state_changes(&physical_snapshot)?;
(wakes, physical_state_changes)
};
self.apply_physical_interrupt_state_changes(physical_state_changes)?;
for wake in wakes {
wake.wake()?;
}
Ok(())
}
pub fn read_redistributor(
&self,
vcpu: GicVcpuId,
offset: u64,
width: AccessWidth,
) -> VgicResult<u64> {
self.inner
.state
.lock()
.redistributor(vcpu, "read Redistributor")?
.read(offset, width, &self.inner.config)
}
pub fn write_redistributor(
&self,
vcpu: GicVcpuId,
offset: u64,
width: AccessWidth,
value: u64,
) -> VgicResult {
let wakes = {
let mut state = self.inner.state.lock();
let candidates = state
.redistributor_mut(vcpu, "write Redistributor")?
.write(offset, width, value, &self.inner.config)?;
let mut wakes = Vec::new();
for intid in candidates {
if let Some(wake) = state.queue_local_if_deliverable(vcpu, intid)? {
wakes.push(wake);
}
}
wakes
};
for wake in wakes {
wake.wake()?;
}
Ok(())
}
pub fn read_its(&self, offset: u64, width: AccessWidth) -> VgicResult<u64> {
self.read_its_for(ItsId::new(0), offset, width)
}
pub fn read_its_for(&self, its_id: ItsId, offset: u64, width: AccessWidth) -> VgicResult<u64> {
validate_its_access(self, its_id, offset, width, "read")?;
if let Some(value) = component_id(offset, GicComponent::Its) {
return Ok(value);
}
let state = self.inner.state.lock();
let its = state
.its
.get(&its_id)
.ok_or_else(|| missing_its(its_id, "read ITS register"))?;
if let Some(base) = wide_register_base(offset) {
let value = match base {
GITS_TYPER => its_typer(self.inner.config.lpi_limit()),
GITS_CBASER => its.cbaser(),
GITS_CWRITER => its.cwriter(),
GITS_CREADR => its.creadr(),
_ => its.baser(baser_index(base).ok_or_else(|| VgicError::InvalidAccess {
region: RegisterRegion::Its,
operation: "read",
offset,
width,
detail: "wide register does not belong to an ITS register bank".into(),
})?),
};
return Ok(read_wide_register(value, offset, base, width));
}
match offset {
GITS_CTLR => Ok(u64::from(its.enabled()) | (1 << 31)),
GITS_IIDR => Ok(0x43b),
_ => Ok(0),
}
}
pub fn write_its(&self, offset: u64, width: AccessWidth, value: u64) -> VgicResult {
self.write_its_for(ItsId::new(0), offset, width, value)
}
pub fn write_its_for(
&self,
its_id: ItsId,
offset: u64,
width: AccessWidth,
value: u64,
) -> VgicResult {
validate_its_access(self, its_id, offset, width, "write")?;
let actions = {
let mut state = self.inner.state.lock();
if let Some(base) = wide_register_base(offset) {
let current = match base {
GITS_TYPER => its_typer(self.inner.config.lpi_limit()),
GITS_CBASER => state
.its
.get(&its_id)
.ok_or_else(|| missing_its(its_id, "read CBASER"))?
.cbaser(),
GITS_CWRITER => state
.its
.get(&its_id)
.ok_or_else(|| missing_its(its_id, "read CWRITER"))?
.cwriter(),
GITS_CREADR => state
.its
.get(&its_id)
.ok_or_else(|| missing_its(its_id, "read CREADR"))?
.creadr(),
_ => state
.its
.get(&its_id)
.ok_or_else(|| missing_its(its_id, "read BASER"))?
.baser(baser_index(base).ok_or_else(|| VgicError::InvalidAccess {
region: RegisterRegion::Its,
operation: "write",
offset,
width,
detail: "wide register does not belong to an ITS register bank".into(),
})?),
};
let merged = merge_wide_register(current, offset, base, width, value);
match base {
GITS_TYPER | GITS_CREADR => Vec::new(),
GITS_CBASER => {
let its = state
.its
.get_mut(&its_id)
.ok_or_else(|| missing_its(its_id, "write CBASER"))?;
if !its.enabled() {
its.set_cbaser(merged)?;
}
Vec::new()
}
GITS_CWRITER => {
state
.its
.get_mut(&its_id)
.ok_or_else(|| missing_its(its_id, "write CWRITER"))?
.set_cwriter(merged)?;
process_its_commands(self, &mut state, its_id)?
}
_ => {
let its = state
.its
.get_mut(&its_id)
.ok_or_else(|| missing_its(its_id, "write BASER"))?;
if !its.enabled() {
let index =
baser_index(base).ok_or_else(|| VgicError::InvalidAccess {
region: RegisterRegion::Its,
operation: "write",
offset,
width,
detail: "wide register does not belong to an ITS register bank"
.into(),
})?;
its.set_baser(index, merged);
}
Vec::new()
}
}
} else {
match offset {
GITS_CTLR => {
let enabled = value & 1 != 0;
state
.its
.get_mut(&its_id)
.ok_or_else(|| missing_its(its_id, "write CTLR"))?
.set_enabled(enabled);
if enabled {
process_its_commands(self, &mut state, its_id)?
} else {
Vec::new()
}
}
GITS_IIDR => Vec::new(),
_ => Vec::new(),
}
}
};
self.apply_its_actions(actions)
}
fn apply_its_actions(&self, actions: Vec<ItsAction>) -> VgicResult {
let wakes = {
let mut state = self.inner.state.lock();
let mut wakes = Vec::new();
for action in actions {
match action {
ItsAction::SetPending {
target,
lpi,
pending,
} => {
if let Some(wake) = state.set_lpi_pending(target, lpi, pending)? {
wakes.push(wake);
}
}
}
}
wakes
};
for wake in wakes {
wake.wake()?;
}
Ok(())
}
}
fn process_its_commands(
controller: &GicV3Controller,
state: &mut ControllerState,
its_id: ItsId,
) -> VgicResult<Vec<ItsAction>> {
let its = state
.its
.get(&its_id)
.ok_or_else(|| missing_its(its_id, "process command queue"))?;
if !its.enabled() || !its.has_pending_commands() {
return Ok(Vec::new());
}
let memory =
controller
.inner
.guest_memory
.as_deref()
.ok_or_else(|| VgicError::Unsupported {
operation: "process ITS command queue",
detail: "no guest-memory capability is installed".into(),
})?;
let processor_targets = state.redistributors.keys().copied().collect::<Vec<_>>();
state
.its
.get_mut(&its_id)
.ok_or_else(|| missing_its(its_id, "process command queue"))?
.process_commands(
memory,
controller.inner.config.its_command_budget(),
controller.inner.config.lpi_limit(),
&processor_targets,
)
}
fn validate_its_access(
controller: &GicV3Controller,
its_id: ItsId,
offset: u64,
width: AccessWidth,
operation: &'static str,
) -> VgicResult {
let region = controller
.inner
.config
.its_instances()
.iter()
.find_map(|(id, region)| (*id == its_id).then_some(*region))
.ok_or_else(|| VgicError::Unsupported {
operation: "access guest ITS registers",
detail: alloc::format!("this controller has no ITS {} frame", its_id.value()),
})?;
if offset
.checked_add(width.size() as u64)
.is_none_or(|end| end > region.size())
|| !offset.is_multiple_of(width.size() as u64)
{
return Err(VgicError::InvalidAccess {
region: RegisterRegion::Its,
operation,
offset,
width,
detail: "access is unaligned or outside the ITS frame".into(),
});
}
let valid_width = if matches!(offset, GITS_CTLR | GITS_IIDR)
|| component_id(offset, GicComponent::Its).is_some()
{
width == AccessWidth::Dword
} else if let Some(base) = wide_register_base(offset) {
width == AccessWidth::Dword || (width == AccessWidth::Qword && offset == base)
} else {
true
};
if !valid_width {
return Err(VgicError::InvalidAccess {
region: RegisterRegion::Its,
operation,
offset,
width,
detail: "register requires a Dword half or an aligned Qword access".into(),
});
}
Ok(())
}
fn missing_its(its: ItsId, operation: &'static str) -> VgicError {
VgicError::ResourceNotFound {
resource: alloc::format!("ITS {}", its.value()),
operation,
}
}
fn wide_register_base(offset: u64) -> Option<u64> {
for base in [GITS_TYPER, GITS_CBASER, GITS_CWRITER, GITS_CREADR] {
if (base..base + 8).contains(&offset) {
return Some(base);
}
}
(GITS_BASER..GITS_BASER + GITS_BASER_COUNT as u64 * 8)
.contains(&offset)
.then(|| GITS_BASER + (offset - GITS_BASER) / 8 * 8)
}
fn read_wide_register(value: u64, offset: u64, base: u64, width: AccessWidth) -> u64 {
if width == AccessWidth::Qword {
value
} else if offset == base {
value & u64::from(u32::MAX)
} else {
value >> 32
}
}
fn merge_wide_register(
current: u64,
offset: u64,
base: u64,
width: AccessWidth,
value: u64,
) -> u64 {
if width == AccessWidth::Qword {
value
} else if offset == base {
(current & !u64::from(u32::MAX)) | (value & u64::from(u32::MAX))
} else {
(current & u64::from(u32::MAX)) | ((value & u64::from(u32::MAX)) << 32)
}
}
fn baser_index(offset: u64) -> Option<usize> {
(GITS_BASER..GITS_BASER + GITS_BASER_COUNT as u64 * 8)
.contains(&offset)
.then_some(((offset - GITS_BASER) / 8) as usize)
}
fn its_typer(lpi_limit: u32) -> u64 {
const PHYSICAL_LPIS: u64 = 1;
const ITT_ENTRY_SIZE: u64 = 8;
const DEVICE_ID_BITS: u64 = 16;
let interrupt_id_bits = u64::from(u32::BITS - lpi_limit.leading_zeros());
PHYSICAL_LPIS
| ((ITT_ENTRY_SIZE - 1) << 4)
| ((interrupt_id_bits - 1) << 8)
| ((DEVICE_ID_BITS - 1) << 13)
}