use alloc::vec::Vec;
use axdevice_base::ItsId;
use crate::{LPI_INTID_BASE, LPI_INTID_MAX, VgicError, VgicResult};
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum GicV3SpiOwnership {
AllGuestOwned,
Explicit,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct GicV3HardwareCapabilities {
spi_count: usize,
affinity_level_3: bool,
range_selector: bool,
}
impl GicV3HardwareCapabilities {
pub fn from_distributor_typer(typer: u32) -> VgicResult<Self> {
let implemented_intids = ((typer & 0x1f) as usize + 1) * 32;
let spi_count = implemented_intids
.min(1020)
.checked_sub(32)
.filter(|count| *count != 0)
.ok_or_else(|| VgicError::InvalidConfig {
detail: alloc::format!("GICD_TYPER {typer:#x} exposes no SPIs"),
})?;
Ok(Self {
spi_count,
affinity_level_3: typer & (1 << 24) != 0,
range_selector: typer & (1 << 26) != 0,
})
}
pub const fn spi_count(self) -> usize {
self.spi_count
}
pub const fn affinity_level_3(self) -> bool {
self.affinity_level_3
}
pub const fn range_selector(self) -> bool {
self.range_selector
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct GicV3MmioRegion {
base: u64,
size: u64,
}
impl GicV3MmioRegion {
pub fn new(base: u64, size: u64) -> VgicResult<Self> {
if size == 0 {
return Err(VgicError::InvalidConfig {
detail: "GICv3 MMIO region must not be empty".into(),
});
}
if base.checked_add(size).is_none() {
return Err(VgicError::InvalidConfig {
detail: alloc::format!(
"GICv3 MMIO region [{base:#x}, +{size:#x}) wraps the address space"
),
});
}
Ok(Self { base, size })
}
pub const fn base(self) -> u64 {
self.base
}
pub const fn size(self) -> u64 {
self.size
}
pub fn contains(self, address: u64, length: usize) -> bool {
address >= self.base
&& address
.checked_add(length as u64)
.is_some_and(|end| end <= self.base + self.size)
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct GicV3Config {
spi_ownership: GicV3SpiOwnership,
distributor: GicV3MmioRegion,
redistributors: Vec<GicV3MmioRegion>,
redistributor_stride: u64,
vcpu_count: usize,
its: Vec<(ItsId, GicV3MmioRegion)>,
spi_count: usize,
affinity_level_3: bool,
range_selector: bool,
lpi_limit: u32,
list_register_count: usize,
its_command_budget: usize,
}
impl GicV3Config {
pub fn new(
spi_ownership: GicV3SpiOwnership,
distributor: GicV3MmioRegion,
redistributors: GicV3MmioRegion,
redistributor_stride: u64,
vcpu_count: usize,
) -> VgicResult<Self> {
Self::new_with_redistributor_regions(
spi_ownership,
distributor,
alloc::vec![redistributors],
redistributor_stride,
vcpu_count,
)
}
pub fn new_with_redistributor_regions(
spi_ownership: GicV3SpiOwnership,
distributor: GicV3MmioRegion,
redistributors: Vec<GicV3MmioRegion>,
redistributor_stride: u64,
vcpu_count: usize,
) -> VgicResult<Self> {
let config = Self {
spi_ownership,
distributor,
redistributors,
redistributor_stride,
vcpu_count,
its: Vec::new(),
spi_count: 988,
affinity_level_3: true,
range_selector: true,
lpi_limit: LPI_INTID_MAX,
list_register_count: 16,
its_command_budget: 256,
};
config.validate()?;
Ok(config)
}
pub fn with_its(mut self, its: GicV3MmioRegion) -> VgicResult<Self> {
self.its = alloc::vec![(ItsId::new(0), its)];
self.validate()?;
Ok(self)
}
pub fn with_its_instances(mut self, its: Vec<(ItsId, GicV3MmioRegion)>) -> VgicResult<Self> {
self.its = its;
self.validate()?;
Ok(self)
}
pub fn with_spi_count(mut self, spi_count: usize) -> VgicResult<Self> {
self.spi_count = spi_count;
self.validate()?;
Ok(self)
}
pub fn with_hardware_capabilities(
mut self,
capabilities: GicV3HardwareCapabilities,
) -> VgicResult<Self> {
self.spi_count = capabilities.spi_count();
self.affinity_level_3 = capabilities.affinity_level_3();
self.range_selector = capabilities.range_selector();
self.validate()?;
Ok(self)
}
pub fn with_lpi_limit(mut self, lpi_limit: u32) -> VgicResult<Self> {
self.lpi_limit = lpi_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_its_command_budget(mut self, budget: usize) -> VgicResult<Self> {
self.its_command_budget = budget;
self.validate()?;
Ok(self)
}
pub const fn spi_ownership(&self) -> GicV3SpiOwnership {
self.spi_ownership
}
pub const fn distributor(&self) -> GicV3MmioRegion {
self.distributor
}
pub fn redistributors(&self) -> &[GicV3MmioRegion] {
&self.redistributors
}
pub const fn redistributor_stride(&self) -> u64 {
self.redistributor_stride
}
pub const fn vcpu_count(&self) -> usize {
self.vcpu_count
}
pub fn its(&self) -> Option<GicV3MmioRegion> {
self.its.first().map(|(_, region)| *region)
}
pub fn its_instances(&self) -> &[(ItsId, GicV3MmioRegion)] {
&self.its
}
pub const fn spi_count(&self) -> usize {
self.spi_count
}
pub const fn affinity_level_3(&self) -> bool {
self.affinity_level_3
}
pub const fn range_selector(&self) -> bool {
self.range_selector
}
pub const fn spi_limit(&self) -> u32 {
32 + self.spi_count as u32
}
pub const fn lpi_limit(&self) -> u32 {
self.lpi_limit
}
pub const fn list_register_count(&self) -> usize {
self.list_register_count
}
pub const fn its_command_budget(&self) -> usize {
self.its_command_budget
}
fn validate(&self) -> VgicResult {
const GICD_MIN_SIZE: u64 = 0x1_0000;
const GIC_FRAME_ALIGNMENT: u64 = 0x1_0000;
const GICR_MIN_STRIDE: u64 = 0x2_0000;
validate_frame_alignment("Distributor", self.distributor, GIC_FRAME_ALIGNMENT)?;
if self.distributor.size() < GICD_MIN_SIZE {
return Err(VgicError::InvalidConfig {
detail: alloc::format!(
"Distributor frame must be at least {GICD_MIN_SIZE:#x} bytes"
),
});
}
if self.vcpu_count == 0 {
return Err(VgicError::InvalidConfig {
detail: "GICv3 requires at least one vCPU".into(),
});
}
if self.vcpu_count > u16::MAX as usize + 1 {
return Err(VgicError::InvalidConfig {
detail: alloc::format!(
"GICv3 vCPU count {} exceeds the 16-bit Processor_Number namespace",
self.vcpu_count
),
});
}
if self.redistributor_stride < GICR_MIN_STRIDE
|| !self.redistributor_stride.is_multiple_of(0x1_0000)
{
return Err(VgicError::InvalidConfig {
detail: alloc::format!(
"Redistributor stride {:#x} must be a 64-KiB-aligned value of at least \
{GICR_MIN_STRIDE:#x}",
self.redistributor_stride
),
});
}
if self.redistributors.is_empty() {
return Err(VgicError::InvalidConfig {
detail: "GICv3 requires at least one Redistributor region".into(),
});
}
let mut redistributor_frames = 0usize;
for (index, redistributor) in self.redistributors.iter().copied().enumerate() {
validate_frame_alignment("Redistributor", redistributor, GIC_FRAME_ALIGNMENT)?;
if !redistributor
.size()
.is_multiple_of(self.redistributor_stride)
{
return Err(VgicError::InvalidConfig {
detail: alloc::format!(
"Redistributor region {index} size {:#x} is not a multiple of stride {:#x}",
redistributor.size(),
self.redistributor_stride
),
});
}
if regions_overlap(self.distributor, redistributor) {
return Err(VgicError::InvalidConfig {
detail: alloc::format!("Distributor and Redistributor region {index} overlap"),
});
}
if self.redistributors[..index]
.iter()
.any(|existing| regions_overlap(*existing, redistributor))
{
return Err(VgicError::InvalidConfig {
detail: alloc::format!(
"Redistributor region {index} overlaps another Redistributor region"
),
});
}
redistributor_frames = redistributor_frames
.checked_add((redistributor.size() / self.redistributor_stride) as usize)
.ok_or_else(|| VgicError::InvalidConfig {
detail: "Redistributor frame count overflows".into(),
})?;
}
if redistributor_frames < self.vcpu_count {
return Err(VgicError::InvalidConfig {
detail: alloc::format!(
"{redistributor_frames} Redistributor frames are available for {} vCPUs",
self.vcpu_count
),
});
}
for (index, (id, its)) in self.its.iter().copied().enumerate() {
if self.its[..index]
.iter()
.any(|(existing, _)| *existing == id)
{
return Err(VgicError::InvalidConfig {
detail: alloc::format!("ITS ID {id:?} is duplicated"),
});
}
validate_frame_alignment("ITS", its, GIC_FRAME_ALIGNMENT)?;
if its.size() < GICD_MIN_SIZE {
return Err(VgicError::InvalidConfig {
detail: alloc::format!("ITS frame must be at least {GICD_MIN_SIZE:#x} bytes"),
});
}
if regions_overlap(its, self.distributor)
|| self
.redistributors
.iter()
.any(|redistributor| regions_overlap(its, *redistributor))
{
return Err(VgicError::InvalidConfig {
detail: "ITS MMIO region overlaps another GICv3 frame".into(),
});
}
if self.its[..index]
.iter()
.any(|(_, existing)| regions_overlap(*existing, its))
{
return Err(VgicError::InvalidConfig {
detail: alloc::format!("ITS {id:?} MMIO region overlaps another ITS"),
});
}
}
if self.spi_count == 0
|| self.spi_count > 988
|| (self.spi_count != 988 && !(self.spi_count + 32).is_multiple_of(32))
{
return Err(VgicError::InvalidConfig {
detail: alloc::format!(
"SPI count {} must be a non-zero multiple of 32, or the architectural maximum \
988",
self.spi_count
),
});
}
if !(LPI_INTID_BASE..=LPI_INTID_MAX).contains(&self.lpi_limit) {
return Err(VgicError::InvalidConfig {
detail: alloc::format!("invalid LPI limit {}", self.lpi_limit),
});
}
if !(1..=16).contains(&self.list_register_count) {
return Err(VgicError::InvalidConfig {
detail: alloc::format!(
"list-register count {} must be in 1..=16",
self.list_register_count
),
});
}
if self.its_command_budget == 0 {
return Err(VgicError::InvalidConfig {
detail: "ITS command budget must be non-zero".into(),
});
}
Ok(())
}
}
fn validate_frame_alignment(
name: &'static str,
region: GicV3MmioRegion,
alignment: u64,
) -> VgicResult {
if region.base().is_multiple_of(alignment) && region.size().is_multiple_of(alignment) {
Ok(())
} else {
Err(VgicError::InvalidConfig {
detail: alloc::format!(
"{name} MMIO base {:#x} and size {:#x} must be {alignment:#x}-byte aligned",
region.base(),
region.size()
),
})
}
}
fn regions_overlap(left: GicV3MmioRegion, right: GicV3MmioRegion) -> bool {
left.base() < right.base() + right.size() && right.base() < left.base() + left.size()
}