use alloc::vec::Vec;
use axdevice_base::{HostIrqId, InterruptControllerId, InterruptTrigger, ItsId};
use crate::{
GicAffinity, GicV3Config, GicV3MmioRegion, GicV3SpiOwnership, LPI_INTID_MAX, SpiId, VgicError,
VgicResult,
};
pub type VgicMmioRegion = GicV3MmioRegion;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct AssignedSpiConfig {
intid: SpiId,
host_irq: HostIrqId,
target_vcpu: usize,
trigger: InterruptTrigger,
}
impl AssignedSpiConfig {
pub fn new(
intid: SpiId,
host_irq: HostIrqId,
target_vcpu: usize,
trigger: InterruptTrigger,
) -> VgicResult<Self> {
if intid.raw() as usize != host_irq.value() {
return Err(VgicError::InvalidConfig {
detail: alloc::format!(
"physical SPI identity mapping requires guest INTID {} to equal host IRQ {}",
intid.raw(),
host_irq.value()
),
});
}
Ok(Self {
intid,
host_irq,
target_vcpu,
trigger,
})
}
pub const fn intid(self) -> SpiId {
self.intid
}
pub const fn host_irq(self) -> HostIrqId {
self.host_irq
}
pub const fn target_vcpu(self) -> usize {
self.target_vcpu
}
pub const fn trigger(self) -> InterruptTrigger {
self.trigger
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct ItsConfig {
id: ItsId,
registers: VgicMmioRegion,
}
impl ItsConfig {
pub const fn new(id: ItsId, registers: VgicMmioRegion) -> Self {
Self { id, registers }
}
pub const fn id(self) -> ItsId {
self.id
}
pub const fn registers(self) -> VgicMmioRegion {
self.registers
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct VgicV2Config {
controller_id: InterruptControllerId,
distributor: VgicMmioRegion,
cpu_interface: VgicMmioRegion,
vcpu_affinities: Vec<GicAffinity>,
spi_count: usize,
list_register_count: usize,
priority_bits: u8,
assigned_spis: Vec<AssignedSpiConfig>,
}
impl VgicV2Config {
pub fn new(
controller_id: InterruptControllerId,
distributor: VgicMmioRegion,
cpu_interface: VgicMmioRegion,
vcpu_affinities: Vec<GicAffinity>,
) -> VgicResult<Self> {
let config = Self {
controller_id,
distributor,
cpu_interface,
vcpu_affinities,
spi_count: 988,
list_register_count: 4,
priority_bits: 5,
assigned_spis: Vec::new(),
};
config.validate()?;
Ok(config)
}
pub fn with_spi_count(mut self, count: usize) -> VgicResult<Self> {
self.spi_count = count;
self.validate()?;
Ok(self)
}
pub fn with_list_register_count(mut self, count: usize) -> VgicResult<Self> {
self.list_register_count = count;
self.validate()?;
Ok(self)
}
pub fn with_priority_bits(mut self, bits: u8) -> VgicResult<Self> {
self.priority_bits = bits;
self.validate()?;
Ok(self)
}
pub fn with_assigned_spis(mut self, assigned_spis: Vec<AssignedSpiConfig>) -> VgicResult<Self> {
self.assigned_spis = assigned_spis;
self.validate()?;
Ok(self)
}
pub const fn controller_id(&self) -> InterruptControllerId {
self.controller_id
}
pub const fn distributor(&self) -> VgicMmioRegion {
self.distributor
}
pub const fn cpu_interface(&self) -> VgicMmioRegion {
self.cpu_interface
}
pub fn vcpu_affinities(&self) -> &[GicAffinity] {
&self.vcpu_affinities
}
pub fn assigned_spis(&self) -> &[AssignedSpiConfig] {
&self.assigned_spis
}
pub const fn priority_bits(&self) -> u8 {
self.priority_bits
}
pub const fn spi_count(&self) -> usize {
self.spi_count
}
pub const fn list_register_count(&self) -> usize {
self.list_register_count
}
fn validate(&self) -> VgicResult {
if self.vcpu_affinities.is_empty() || self.vcpu_affinities.len() > 8 {
return Err(VgicError::InvalidConfig {
detail: alloc::format!(
"GICv2 requires 1..=8 vCPUs, got {}",
self.vcpu_affinities.len()
),
});
}
validate_unique_affinities(&self.vcpu_affinities)?;
validate_common_capabilities(self.spi_count, self.list_register_count, self.priority_bits)?;
validate_assigned_spis(
&self.assigned_spis,
self.spi_count,
self.vcpu_affinities.len(),
)
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct VgicV3Config {
controller_id: InterruptControllerId,
distributor: VgicMmioRegion,
redistributors: Vec<VgicMmioRegion>,
redistributor_stride: u64,
vcpu_affinities: Vec<GicAffinity>,
spi_count: usize,
lpi_limit: u32,
list_register_count: usize,
priority_bits: u8,
its: Vec<ItsConfig>,
assigned_spis: Vec<AssignedSpiConfig>,
}
impl VgicV3Config {
pub fn new(
controller_id: InterruptControllerId,
distributor: VgicMmioRegion,
redistributors: Vec<VgicMmioRegion>,
redistributor_stride: u64,
vcpu_affinities: Vec<GicAffinity>,
) -> VgicResult<Self> {
let config = Self {
controller_id,
distributor,
redistributors,
redistributor_stride,
vcpu_affinities,
spi_count: 988,
lpi_limit: LPI_INTID_MAX,
list_register_count: 16,
priority_bits: 5,
its: Vec::new(),
assigned_spis: Vec::new(),
};
config.validate()?;
Ok(config)
}
pub fn with_spi_count(mut self, count: usize) -> VgicResult<Self> {
self.spi_count = count;
self.validate()?;
Ok(self)
}
pub fn with_lpi_limit(mut self, limit: u32) -> VgicResult<Self> {
self.lpi_limit = limit;
self.validate()?;
Ok(self)
}
pub fn with_list_register_count(mut self, count: usize) -> VgicResult<Self> {
self.list_register_count = count;
self.validate()?;
Ok(self)
}
pub fn with_priority_bits(mut self, bits: u8) -> VgicResult<Self> {
self.priority_bits = bits;
self.validate()?;
Ok(self)
}
pub fn with_its(mut self, its: Vec<ItsConfig>) -> VgicResult<Self> {
self.its = its;
self.validate()?;
Ok(self)
}
pub fn with_assigned_spis(mut self, assigned_spis: Vec<AssignedSpiConfig>) -> VgicResult<Self> {
self.assigned_spis = assigned_spis;
self.validate()?;
Ok(self)
}
pub const fn controller_id(&self) -> InterruptControllerId {
self.controller_id
}
pub const fn distributor(&self) -> VgicMmioRegion {
self.distributor
}
pub fn redistributors(&self) -> &[VgicMmioRegion] {
&self.redistributors
}
pub const fn redistributor_stride(&self) -> u64 {
self.redistributor_stride
}
pub fn vcpu_affinities(&self) -> &[GicAffinity] {
&self.vcpu_affinities
}
pub fn its(&self) -> &[ItsConfig] {
&self.its
}
pub fn assigned_spis(&self) -> &[AssignedSpiConfig] {
&self.assigned_spis
}
pub const fn priority_bits(&self) -> u8 {
self.priority_bits
}
pub const fn spi_count(&self) -> usize {
self.spi_count
}
pub const fn lpi_limit(&self) -> u32 {
self.lpi_limit
}
pub const fn list_register_count(&self) -> usize {
self.list_register_count
}
fn validate(&self) -> VgicResult {
if self.vcpu_affinities.is_empty() {
return Err(VgicError::InvalidConfig {
detail: "GICv3 requires at least one vCPU".into(),
});
}
validate_unique_affinities(&self.vcpu_affinities)?;
validate_common_capabilities(self.spi_count, self.list_register_count, self.priority_bits)?;
let frames = self
.redistributors
.iter()
.try_fold(0usize, |count, region| {
if !region.base().is_multiple_of(0x1_0000)
|| !region.size().is_multiple_of(0x1_0000)
{
return Err(VgicError::InvalidConfig {
detail: "GICv3 Redistributor regions must be 64-KiB aligned".into(),
});
}
Ok(count + (region.size() / self.redistributor_stride) as usize)
})?;
if frames < self.vcpu_affinities.len() {
return Err(VgicError::InvalidConfig {
detail: alloc::format!(
"{} Redistributor frames are available for {} vCPUs",
frames,
self.vcpu_affinities.len()
),
});
}
for (index, its) in self.its.iter().enumerate() {
if self.its[..index]
.iter()
.any(|existing| existing.id() == its.id())
{
return Err(VgicError::InvalidConfig {
detail: alloc::format!("ITS ID {:?} is duplicated", its.id()),
});
}
}
validate_assigned_spis(
&self.assigned_spis,
self.spi_count,
self.vcpu_affinities.len(),
)?;
self.internal_config().map(|_| ())
}
pub(crate) fn internal_config(&self) -> VgicResult<GicV3Config> {
let mut internal = GicV3Config::new_with_redistributor_regions(
GicV3SpiOwnership::AllGuestOwned,
self.distributor,
self.redistributors.clone(),
self.redistributor_stride,
self.vcpu_affinities.len(),
)?
.with_spi_count(self.spi_count)?
.with_lpi_limit(self.lpi_limit)?
.with_list_register_count(self.list_register_count)?;
if !self.its.is_empty() {
internal = internal.with_its_instances(
self.its
.iter()
.map(|its| (its.id(), its.registers()))
.collect(),
)?;
}
Ok(internal)
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum ArmVgicConfig {
V2(VgicV2Config),
V3(VgicV3Config),
}
impl ArmVgicConfig {
pub const fn controller_id(&self) -> InterruptControllerId {
match self {
Self::V2(config) => config.controller_id(),
Self::V3(config) => config.controller_id(),
}
}
pub fn vcpu_affinities(&self) -> &[GicAffinity] {
match self {
Self::V2(config) => config.vcpu_affinities(),
Self::V3(config) => config.vcpu_affinities(),
}
}
pub fn assigned_spis(&self) -> &[AssignedSpiConfig] {
match self {
Self::V2(config) => config.assigned_spis(),
Self::V3(config) => config.assigned_spis(),
}
}
pub const fn list_register_count(&self) -> usize {
match self {
Self::V2(config) => config.list_register_count,
Self::V3(config) => config.list_register_count,
}
}
pub const fn priority_bits(&self) -> u8 {
match self {
Self::V2(config) => config.priority_bits,
Self::V3(config) => config.priority_bits,
}
}
pub(crate) fn internal_gicv3_config(&self) -> VgicResult<GicV3Config> {
match self {
Self::V3(config) => config.internal_config(),
Self::V2(_) => Err(VgicError::Unsupported {
operation: "construct GICv3 configuration",
detail: "a GICv2 configuration has no GICv3 MMIO layout".into(),
}),
}
}
}
fn validate_common_capabilities(
spi_count: usize,
list_register_count: usize,
priority_bits: u8,
) -> VgicResult {
if spi_count == 0
|| spi_count > 988
|| (spi_count != 988 && !(spi_count + 32).is_multiple_of(32))
{
return Err(VgicError::InvalidConfig {
detail: alloc::format!("invalid implemented SPI count {spi_count}"),
});
}
if !(1..=16).contains(&list_register_count) {
return Err(VgicError::InvalidConfig {
detail: alloc::format!("invalid list-register count {list_register_count}"),
});
}
if !(4..=8).contains(&priority_bits) {
return Err(VgicError::InvalidConfig {
detail: alloc::format!("priority width {priority_bits} is outside 4..=8"),
});
}
Ok(())
}
fn validate_unique_affinities(affinities: &[GicAffinity]) -> VgicResult {
for (index, affinity) in affinities.iter().enumerate() {
if affinities[..index].contains(affinity) {
return Err(VgicError::InvalidConfig {
detail: alloc::format!("vCPU affinity {affinity:?} is duplicated"),
});
}
}
Ok(())
}
fn validate_assigned_spis(
assigned: &[AssignedSpiConfig],
spi_count: usize,
vcpu_count: usize,
) -> VgicResult {
for (index, binding) in assigned.iter().enumerate() {
if binding.intid().raw() >= 32 + spi_count as u32 {
return Err(VgicError::InvalidConfig {
detail: alloc::format!(
"assigned SPI {} exceeds configured SPI capacity",
binding.intid().raw()
),
});
}
if binding.target_vcpu() >= vcpu_count {
return Err(VgicError::InvalidConfig {
detail: alloc::format!(
"assigned SPI {} targets missing vCPU {}",
binding.intid().raw(),
binding.target_vcpu()
),
});
}
if assigned[..index].iter().any(|existing| {
existing.intid() == binding.intid() || existing.host_irq() == binding.host_irq()
}) {
return Err(VgicError::InvalidConfig {
detail: alloc::format!(
"assigned SPI/host IRQ {} is duplicated",
binding.intid().raw()
),
});
}
}
Ok(())
}