#[cfg(any(test, feature = "fakes", target_arch = "aarch64", target_arch = "arm"))]
mod cpu_interface;
mod distributor;
mod redistributor;
pub mod registers;
#[cfg(any(test, feature = "fakes", target_arch = "aarch64", target_arch = "arm"))]
use crate::sysreg::{IccCtlrEl1, write_icc_ctlr_el1};
use crate::{IntId, Trigger};
use core::ptr::NonNull;
#[cfg(any(test, feature = "fakes", target_arch = "aarch64", target_arch = "arm"))]
pub use cpu_interface::GicCpuInterface;
pub use distributor::{GicDistributor, GicDistributorContext};
pub use redistributor::{GicRedistributor, GicRedistributorContext, GicRedistributorIterator};
use registers::{Gicd, GicdCtlr, GicrSgi, GicrTyper, Typer};
use safe_mmio::{UniqueMmioPointer, field_shared, fields::ReadPureWrite};
use thiserror::Error;
use zerocopy::{Immutable, IntoBytes};
#[derive(Error, Debug, Clone, Copy, Eq, PartialEq)]
pub enum GicError {
#[error("redistributor has already been notified that the connected core is awake")]
AlreadyAwake,
#[error("redistributor has already been notified that the connected core is asleep")]
AlreadyAsleep,
#[error("invalid redistributor index {0:?}")]
InvalidRedistributorIndex(usize),
#[error("invalid IntId for the CPU interface {0:?}")]
InvalidGicCpuIntid(IntId),
#[error("invalid IntId for the redistributor {0:?}")]
InvalidGicrIntid(IntId),
#[error("invalid IntId for the distributor {0:?}")]
InvalidGicdIntid(IntId),
#[error("the context size is smaller than the implemented interrupt count: {0:?} > {1:?}")]
InvalidContextSize(usize, usize),
}
pub const HIGHEST_S_PRIORITY: u8 = 0x00;
pub const HIGHEST_NS_PRIORITY: u8 = 0x80;
const fn register_count<T: ?Sized>(int_count: usize, bits_per_int: usize, field: &T) -> usize {
(int_count * bits_per_int).div_ceil(size_of_val(field) * 8)
}
fn set_regs<T, const N: usize>(
mut regs: UniqueMmioPointer<[ReadPureWrite<T>; N]>,
start_offset: usize,
int_count: usize,
bits_per_int: usize,
value: T,
) where
T: Immutable + IntoBytes + Copy,
{
let reg_start = register_count(start_offset, bits_per_int, &value);
let reg_end = register_count(start_offset + int_count, bits_per_int, &value);
for mut reg in regs.get_range(reg_start..reg_end).unwrap() {
reg.write(value);
}
}
#[derive(Debug)]
pub struct GicV3<'a> {
gicd: GicDistributor<'a>,
gicr_base: NonNull<GicrSgi>,
cpu_count: usize,
gicr_frame_count: usize,
}
unsafe fn get_redistributor_frame_count(gicr_base: NonNull<GicrSgi>, gic_v4: bool) -> usize {
if !gic_v4 {
return 1;
}
let first_gicr_window = unsafe { UniqueMmioPointer::new(gicr_base) };
let first_gicr = field_shared!(first_gicr_window, gicr);
if field_shared!(first_gicr, typer)
.read()
.virtual_lpis_supported()
{
2
} else {
1
}
}
impl<'a> GicV3<'a> {
pub unsafe fn new(
gicd: UniqueMmioPointer<'a, Gicd>,
gicr_base: NonNull<GicrSgi>,
cpu_count: usize,
gic_v4: bool,
) -> Self {
Self {
gicd: GicDistributor::new(gicd),
gicr_base,
cpu_count,
gicr_frame_count: unsafe { get_redistributor_frame_count(gicr_base, gic_v4) },
}
}
#[cfg(any(test, feature = "fakes", target_arch = "aarch64", target_arch = "arm"))]
pub fn init_cpu(&mut self, cpu: usize) {
GicCpuInterface::enable_system_register_el1();
let _ = self.redistributor_mark_core_awake(cpu);
write_icc_ctlr_el1(IccCtlrEl1::empty());
}
#[cfg(any(test, feature = "fakes", target_arch = "aarch64", target_arch = "arm"))]
pub fn setup(&mut self, cpu: usize) {
self.init_cpu(cpu);
{
for cpu in 0..self.cpu_count {
self.redistributor(cpu)
.unwrap()
.configure_default_settings();
}
}
self.gicd.configure_default_settings();
GicCpuInterface::enable_group1(true);
}
pub fn enable_interrupt(
&mut self,
intid: IntId,
cpu: Option<usize>,
enable: bool,
) -> Result<(), GicError> {
if intid.is_private() {
self.redistributor(cpu.unwrap())?
.enable_interrupt(intid, enable)
} else {
self.gicd.enable_interrupt(intid, enable)
}
}
pub fn enable_all_interrupts(&mut self, enable: bool) {
self.gicd.enable_all_interrupts(enable);
for cpu in 0..self.cpu_count {
self.redistributor(cpu)
.unwrap()
.enable_all_interrupts(enable);
}
}
pub fn set_interrupt_priority(
&mut self,
intid: IntId,
cpu: Option<usize>,
priority: u8,
) -> Result<(), GicError> {
if intid.is_private() {
self.redistributor(cpu.unwrap())?
.set_interrupt_priority(intid, priority)
} else {
self.gicd.set_interrupt_priority(intid, priority)
}
}
pub fn set_trigger(
&mut self,
intid: IntId,
cpu: Option<usize>,
trigger: Trigger,
) -> Result<(), GicError> {
if intid.is_private() {
self.redistributor(cpu.unwrap())?
.set_trigger(intid, trigger)
} else {
self.gicd.set_trigger(intid, trigger)
}
}
pub fn set_group(
&mut self,
intid: IntId,
cpu: Option<usize>,
group: Group,
) -> Result<(), GicError> {
if intid.is_private() {
self.redistributor(cpu.unwrap())?.set_group(intid, group)
} else {
self.gicd.set_group(intid, group)
}
}
pub fn typer(&self) -> Typer {
self.gicd.typer()
}
pub fn gicr_typer(&mut self, cpu: usize) -> Result<GicrTyper, GicError> {
Ok(self.redistributor(cpu)?.typer())
}
pub fn distributor(&mut self) -> &mut GicDistributor<'a> {
&mut self.gicd
}
fn gicr_sgi_ptr(&mut self, cpu: usize) -> Result<UniqueMmioPointer<'_, GicrSgi>, GicError> {
if cpu >= self.cpu_count {
return Err(GicError::InvalidRedistributorIndex(cpu));
}
Ok(unsafe { UniqueMmioPointer::new(self.gicr_base.add(cpu * self.gicr_frame_count)) })
}
pub fn redistributor(&mut self, cpu: usize) -> Result<GicRedistributor<'_>, GicError> {
Ok(GicRedistributor::new(self.gicr_sgi_ptr(cpu)?))
}
pub fn gicd_barrier(&self) {
self.gicd.wait_for_pending_write();
}
pub fn gicd_clear_control(&mut self, flags: GicdCtlr) {
self.gicd.modify_control(flags, false);
}
pub fn gicd_set_control(&mut self, flags: GicdCtlr) {
self.gicd.modify_control(flags, true);
}
pub fn gicr_barrier(&mut self, cpu: usize) -> Result<(), GicError> {
self.redistributor(cpu)?.wait_for_pending_write();
Ok(())
}
pub fn gicr_power_on(&mut self, cpu: usize) -> Result<(), GicError> {
self.redistributor(cpu)?.power_on();
Ok(())
}
pub fn gicr_power_off(&mut self, cpu: usize) -> Result<(), GicError> {
self.redistributor(cpu)?.power_off();
Ok(())
}
pub fn redistributor_mark_core_awake(&mut self, cpu: usize) -> Result<(), GicError> {
self.redistributor(cpu)?.mark_core_awake()
}
pub fn redistributor_mark_core_asleep(&mut self, cpu: usize) -> Result<(), GicError> {
self.redistributor(cpu)?.mark_core_asleep()
}
}
unsafe impl Send for GicV3<'_> {}
unsafe impl Sync for GicV3<'_> {}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum Group {
Secure(SecureIntGroup),
Group1NS,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum SecureIntGroup {
Group1S,
Group0,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum SgiTarget {
All,
List {
affinity3: u8,
affinity2: u8,
affinity1: u8,
target_list: u16,
},
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum SgiTargetGroup {
Group0,
CurrentGroup1,
OtherGroup1,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::sysreg::{IccIgrpen1El1, IccSreEl1};
use arm_sysregs::fake::SYSREGS;
use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout, transmute_mut};
#[derive(Clone, Eq, FromBytes, Immutable, IntoBytes, KnownLayout, PartialEq)]
#[repr(C, align(8))]
struct FakeRegisters {
regs: [u32; Self::REG_COUNT],
}
impl FakeRegisters {
const REG_COUNT: usize = 64 * 1024 / 4;
pub const fn new() -> Self {
Self {
regs: [0u32; Self::REG_COUNT],
}
}
}
struct FakeGic {
dist_regs: FakeRegisters,
redist_regs: [FakeRegisters; Self::CORE_COUNT * 2],
}
impl FakeGic {
const CORE_COUNT: usize = 4;
pub fn new() -> Self {
Self {
dist_regs: FakeRegisters::new(),
redist_regs: [const { FakeRegisters::new() }; Self::CORE_COUNT * 2],
}
}
pub fn dist_regs_write(&mut self, offset: usize, value: u32) {
self.dist_regs.regs[offset / 4] = value;
}
pub fn dist_reg_read(&self, offset: usize) -> u32 {
self.dist_regs.regs[offset / 4]
}
pub fn redist_regs_write(&mut self, cpu_index: usize, offset: usize, value: u32) {
self.redist_regs[cpu_index * 2].regs[offset / 4] = value;
}
pub fn sgi_reg_read(&self, cpu_index: usize, offset: usize) -> u32 {
self.redist_regs[cpu_index * 2 + 1].regs[offset / 4]
}
pub fn instance_for_test(&mut self) -> GicV3<'_> {
let gicd = UniqueMmioPointer::from(transmute_mut!(&mut self.dist_regs));
let gicr_base: &mut [GicrSgi; Self::CORE_COUNT] = transmute_mut!(&mut self.redist_regs);
unsafe {
GicV3::new(
gicd,
NonNull::new(gicr_base.as_mut_ptr()).unwrap(),
Self::CORE_COUNT,
false,
)
}
}
}
#[test]
fn setup() {
let mut regs = FakeGic::new();
regs.dist_regs_write(0x0004, 0x0002);
{
let mut gic = regs.instance_for_test();
gic.setup(0);
}
for cpu_index in 0..FakeGic::CORE_COUNT {
let redist_expectations = [
(0x0080..=0x0080, 0xffff_ffff), (0x0180..=0x0180, 0xffff_ffff), (0x0400..=0x041c, 0x8080_8080), (0x0c00..=0x0c00, 0x0000_0000), ];
for (range, value) in redist_expectations {
for offset in range {
assert_eq!(
value,
regs.sgi_reg_read(cpu_index, offset),
"offset {offset:#x}",
);
}
}
}
let dist_expectation = [
(0x0000..=0x0000, 0x0000_0012), (0x0084..=0x0084, 0xffff_ffff), (0x0420..=0x043c, 0x8080_8080), (0x0c04..=0x0c04, 0x0000_0000), ];
for (range, value) in dist_expectation {
for offset in range {
assert_eq!(value, regs.dist_reg_read(offset), "offset {offset:#x}",);
}
}
let sysregs = SYSREGS.lock().unwrap();
assert_eq!(IccCtlrEl1::empty(), sysregs.icc_ctlr_el1);
assert_eq!(IccSreEl1::SRE, sysregs.icc_sre_el1);
assert_eq!(IccIgrpen1El1::ENABLE, sysregs.icc_igrpen1_el1);
}
#[test]
fn enable_all_interrupts() {
let mut regs = FakeGic::new();
regs.dist_regs_write(0x0004, 0x0002);
{
let mut gic = regs.instance_for_test();
gic.enable_all_interrupts(true);
}
for cpu_index in 0..FakeGic::CORE_COUNT {
assert_eq!(0xffff_ffff, regs.sgi_reg_read(cpu_index, 0x0100));
}
assert_eq!(0xffff_ffff, regs.dist_reg_read(0x0104));
}
#[test]
fn enable_interrupt() {
let mut regs = FakeGic::new();
regs.dist_regs_write(0x0004, 0x0002);
{
let mut gic = regs.instance_for_test();
assert_eq!(Ok(()), gic.enable_interrupt(IntId::ppi(0), Some(1), true));
assert_eq!(Ok(()), gic.enable_interrupt(IntId::spi(0), None, true));
}
assert_eq!(0x0001_0000, regs.sgi_reg_read(1, 0x0100));
assert_eq!(0x0000_0001, regs.dist_reg_read(0x0104));
}
#[test]
fn set_priority() {
let mut regs = FakeGic::new();
regs.dist_regs_write(0x0004, 0x0002);
{
let mut gic = regs.instance_for_test();
assert_eq!(
Ok(()),
gic.set_interrupt_priority(IntId::ppi(0), Some(2), 0xab)
);
assert_eq!(
Ok(()),
gic.set_interrupt_priority(IntId::spi(0), None, 0xcd)
);
}
assert_eq!(0x0000_00ab, regs.sgi_reg_read(2, 0x0410));
assert_eq!(0x0000_00cd, regs.dist_reg_read(0x420));
}
#[test]
fn set_trigger() {
let mut regs = FakeGic::new();
regs.dist_regs_write(0x0004, 0x0002);
{
let mut gic = regs.instance_for_test();
assert_eq!(
Ok(()),
gic.set_trigger(IntId::ppi(0), Some(2), Trigger::Edge)
);
assert_eq!(Ok(()), gic.set_trigger(IntId::spi(0), None, Trigger::Edge));
}
assert_eq!(0x0000_0002, regs.sgi_reg_read(2, 0x0c04));
assert_eq!(0x0000_0002, regs.dist_reg_read(0x0c08));
}
#[test]
fn set_group() {
let mut regs = FakeGic::new();
regs.dist_regs_write(0x0004, 0x0002);
{
let mut gic = regs.instance_for_test();
assert_eq!(
Ok(()),
gic.set_group(IntId::ppi(0), Some(2), Group::Group1NS)
);
assert_eq!(
Ok(()),
gic.set_group(IntId::spi(0), None, Group::Secure(SecureIntGroup::Group1S))
);
}
assert_eq!(0x0001_0000, regs.sgi_reg_read(2, 0x0080));
assert_eq!(0x0000_0001, regs.dist_reg_read(0x0D04));
}
#[test]
fn typer() {
let mut regs = FakeGic::new();
regs.dist_regs_write(0x0004, 0x0000_0081);
let mut gic = regs.instance_for_test();
let distributor = gic.distributor();
let typer = distributor.typer();
assert_eq!(32, typer.num_spis());
assert_eq!(4, typer.num_cpus());
let typer = gic.typer();
assert_eq!(32, typer.num_spis());
assert_eq!(4, typer.num_cpus());
}
#[test]
fn gicr_typer() {
let mut regs = FakeGic::new();
regs.redist_regs_write(3, 0x0008, 1 << 27);
regs.redist_regs_write(3, 0x000c, 0xabcd_ef12);
let mut gic = regs.instance_for_test();
let typer = gic.gicr_typer(3).unwrap();
assert_eq!(0x0000_00ab_00cd_ef12, typer.core_mpidr());
assert_eq!(32, typer.max_eppi_count());
let redist = gic.redistributor(3).unwrap();
let typer = redist.typer();
assert_eq!(0x0000_00ab_00cd_ef12, typer.core_mpidr());
assert_eq!(32, typer.max_eppi_count());
assert!(gic.redistributor(FakeGic::CORE_COUNT).is_err());
}
#[test]
fn gicd_control() {
let mut regs = FakeGic::new();
{
let mut gic = regs.instance_for_test();
gic.gicd_set_control(GicdCtlr::EnableGrp1NS | GicdCtlr::EnableGrp1S);
}
assert_eq!(0x0000_0006, regs.dist_reg_read(0x0000));
{
let mut gic = regs.instance_for_test();
gic.gicd_clear_control(GicdCtlr::EnableGrp1NS);
}
assert_eq!(0x0000_0004, regs.dist_reg_read(0x0000));
}
}