| // SPDX-FileCopyrightText: Copyright 2023-2025 Arm Limited and/or its affiliates <open-source-office@arm.com> |
| // SPDX-License-Identifier: MIT OR Apache-2.0 |
| |
| //! # Arm Generic Interrupt Controller driver |
| //! |
| //! The driver implementation is based on [Arm Generic Interrupt Controller Architecture |
| //! Specification GIC architecture version 3 and version 4](https://developer.arm.com/documentation/ihi0069/hb/). |
| |
| #![no_std] |
| |
| use bitflags::bitflags; |
| use core::ops::Range; |
| use safe_mmio::{ |
| field, |
| fields::{ReadOnly, ReadPure, ReadPureWrite, ReadWrite, WriteOnly}, |
| UniqueMmioPointer, |
| }; |
| use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout}; |
| |
| #[repr(transparent)] |
| #[derive(Copy, Clone, Debug, Eq, FromBytes, Immutable, IntoBytes, KnownLayout, PartialEq)] |
| |
| struct DistributorControlRegister(u32); |
| |
| bitflags! { |
| // Distributor Control Register |
| impl DistributorControlRegister : u32 { |
| /// Register Write Pending |
| const RWP = 1 << 31; |
| /// Enable 1 of N Wakeup Functionality |
| const E1NWF = 1 << 7; |
| /// Disable Security |
| const DS = 1 << 6; |
| /// Affinity Routing Enable, Non-secure state |
| const ARE_NS = 1 << 5; |
| /// Affinity Routing Enable, Secure state |
| const ARE_S = 1 << 4; |
| /// Enable Secure Group 1 interrupts |
| const EnableGrp1S = 1 << 2; |
| /// Enable Non-secure Group 1 interrupts |
| const EnableGrp1NS = 1 << 1; |
| /// Enable Group 0 interrupts |
| const EnableGrp0 = 1 << 0; |
| } |
| } |
| |
| /// GIC Distributor register map |
| #[derive(Clone, Eq, FromBytes, Immutable, IntoBytes, KnownLayout, PartialEq)] |
| #[repr(C, align(4))] |
| pub struct GICDRegisters { |
| /// 0x0000 |
| ctlr: ReadPureWrite<DistributorControlRegister>, |
| /// 0x0004 |
| typer: ReadPure<u32>, |
| /// 0x0008 |
| iidr: ReadPure<u32>, |
| /// 0x000c |
| typer2: ReadPure<u32>, |
| /// 0x0010 |
| statusr: ReadPureWrite<u32>, |
| /// 0x0014-0x001c |
| reserved_0014: [u32; 3], |
| /// 0x0020-0x003c |
| imdef0: [ReadWrite<u32>; 8], |
| /// 0x0040 |
| setspi_nsr: WriteOnly<u32>, |
| /// 0x0044 |
| reserved_0044: u32, |
| /// 0x0048 |
| clrspi_nsr: WriteOnly<u32>, |
| /// 0x004c |
| reserved_004c: u32, |
| /// 0x0050 |
| setspi_sr: WriteOnly<u32>, |
| /// 0x0054 |
| reserved_0054: u32, |
| /// 0x0058 |
| clrspi_sr: WriteOnly<u32>, |
| /// 0x005c-0x007c |
| reserved_005c: [u32; 9], |
| /// 0x0080-0x00fc |
| igroupr: [ReadPureWrite<u32>; 32], |
| /// 0x0100-0x017c |
| isenabler: [ReadPureWrite<u32>; 32], |
| /// 0x0180-0x01fc |
| icenabler: [ReadPureWrite<u32>; 32], |
| // 0x0200-0x027c |
| ispendr: [ReadPureWrite<u32>; 32], |
| /// 0x0280-0x02fc |
| icpendr: [ReadPureWrite<u32>; 32], |
| /// 0x0300-0x037c |
| isactiver: [ReadPureWrite<u32>; 32], |
| /// 0x0380-0x03fc |
| icactiver: [ReadPureWrite<u32>; 32], |
| /// 0x0400-0x07f8 |
| ipriorityr: [ReadPureWrite<u32>; 256], |
| /// 0x0800-0x0bfc |
| itargetsr: [ReadOnly<u32>; 256], |
| /// 0x0c00-0x0cfc |
| icfgr: [ReadPureWrite<u32>; 64], |
| /// 0x0d00-0x0d7c |
| igrpmodr: [ReadPureWrite<u32>; 32], |
| /// 0x0d80-0x0dfc |
| reserved_0d80: [u32; 32], |
| /// 0x0e00-0x0efc |
| nsacr: [ReadPureWrite<u32>; 64], |
| /// 0x0f00 |
| sgir: WriteOnly<u32>, |
| /// 0x0f04-0x0f0c |
| reserved_0f04: [u32; 3], |
| /// 0x0f10-0x0f1c |
| cpendsgir: [ReadPureWrite<u32>; 4], |
| /// 0x0f20-0x0f2c |
| spendsgir: [ReadPureWrite<u32>; 4], |
| /// 0x0f30-0x0f7c |
| reserved_0f30: [u32; 20], |
| /// 0x0f80-0x0ffc |
| inmir: [ReadPureWrite<u32>; 32], |
| /// 0x1000-0x107c |
| igroupr_e: [ReadPureWrite<u32>; 32], |
| /// 0x1080-0x1fc |
| reserved_1080: [u32; 96], |
| /// 0x1200-0x127c |
| isenabler_e: [ReadPureWrite<u32>; 32], |
| /// 0x1280-0x13fc |
| reserved_1280: [u32; 96], |
| /// 0x1400-0x147c |
| icenabler_e: [ReadPureWrite<u32>; 32], |
| /// 0x1480-0x15fc |
| reserved_1480: [u32; 96], |
| /// 0x1600-0x167c |
| ispendr_e: [ReadPureWrite<u32>; 32], |
| /// 0x1680-0x17fc |
| reserved_1680: [u32; 96], |
| /// 0x1800-0x187c |
| icpendr_e: [ReadPureWrite<u32>; 32], |
| /// 0x1880-0x19fc |
| reserved_1880: [u32; 96], |
| /// 0x1a00-0x1a7c |
| isactive_e: [ReadPureWrite<u32>; 32], |
| /// 0x1a80-0x1bfc |
| reserved_1a80: [u32; 96], |
| /// 0x1c00-0x1c7c |
| icactive_e: [ReadPureWrite<u32>; 32], |
| /// 0x1c80-0x1ffc |
| reserved_1c80: [u32; 224], |
| /// 0x2000-0x23fc |
| ipriorityr_e: [ReadPureWrite<u32>; 256], |
| /// 0x2400-0x2ffc |
| reserved_2400: [u32; 768], |
| /// 0x3000-0x30fc |
| icfgr_e: [ReadPureWrite<u32>; 64], |
| /// 0x3100-0x33fc |
| reserved_3100: [u32; 192], |
| /// 0x3400-0x347c |
| igrpmodr_e: [ReadPureWrite<u32>; 32], |
| /// 0x3480-0x35fc |
| reserved_3480: [u32; 96], |
| /// 0x3600-0x367c |
| nsacr_e: [ReadPureWrite<u32>; 32], |
| /// 0x3680-0x3afc |
| reserved_3680: [u32; 256], |
| /// 0x3b00-0x3bfc |
| inmir_e: [ReadPureWrite<u32>; 64], |
| /// 0x3b80-0x60fc |
| reserved_3b80: [u32; 2400], |
| /// 0x6100-0x7fd8 |
| irouter: [ReadPureWrite<u32>; 1975], |
| /// 0x7fdc-0x7ffc |
| reserved_7fdc: [u32; 9], |
| /// 0x8000-0x9ffc |
| irouter_e: [ReadPureWrite<u32>; 2048], |
| /// 0xa000-0xffcc |
| reserved_a000: [u32; 6132], |
| /// 0xffd0-0xfffc |
| id_registers: [ReadOnly<u32>; 12], |
| } |
| |
| /// # GIC Distributor error type |
| #[derive(Debug)] |
| pub enum GicDistributorError { |
| InvalidParameter, |
| } |
| |
| /// # GIC Distributor |
| /// |
| /// The Distributor performs interrupt prioritization and distribution of SPIs and SGIs to the |
| /// Redistributors and CPU interfaces that are connected to the PEs in the system. |
| pub struct GicDistributor<'a> { |
| gicd: UniqueMmioPointer<'a, GICDRegisters>, |
| } |
| |
| impl<'a> GicDistributor<'a> { |
| const MAX_IT: usize = 32; |
| |
| /// Create GIC Distributor instance |
| pub fn new(mut gicd: UniqueMmioPointer<'a, GICDRegisters>) -> Self { |
| for n in 0..Self::MAX_IT { |
| field!(gicd, icenabler).split()[n].write(0xffff_ffff); |
| field!(gicd, icpendr).split()[n].write(0xffff_ffff); |
| field!(gicd, igroupr).split()[n].write(0xffff_ffff); |
| } |
| |
| Self { gicd } |
| } |
| |
| /// Enable Group 0 interrupts |
| pub fn enable_group0(&mut self) { |
| let value = field!(self.gicd, ctlr).read(); |
| field!(self.gicd, ctlr).write(value | DistributorControlRegister::EnableGrp0); |
| } |
| |
| /// Enable Secure Group 1 interrupts |
| pub fn enable_secure_group1(&mut self) { |
| let value = field!(self.gicd, ctlr).read(); |
| field!(self.gicd, ctlr).write(value | DistributorControlRegister::EnableGrp1S); |
| } |
| |
| /// Enable Non-secure Group 1 interrupts |
| pub fn enable_non_secure_group1(&mut self) { |
| let value = field!(self.gicd, ctlr).read(); |
| field!(self.gicd, ctlr).write(value | DistributorControlRegister::EnableGrp1NS); |
| } |
| |
| /// Select a range of interrupts for group 0 |
| pub fn select_group0_range(&mut self, range: Range<usize>) -> Result<(), GicDistributorError> { |
| self.modify_group_range(range, |v, mask| v & !mask) |
| } |
| |
| /// Select a range of interrupts for group 1 |
| pub fn select_group1_range(&mut self, range: Range<usize>) -> Result<(), GicDistributorError> { |
| self.modify_group_range(range, |v, mask| v | mask) |
| } |
| |
| fn modify_group_range<F>( |
| &mut self, |
| range: Range<usize>, |
| f: F, |
| ) -> Result<(), GicDistributorError> |
| where |
| F: Fn(u32, u32) -> u32, |
| { |
| let max_it_index: usize = field!(self.gicd, igroupr).split().len() * 32; |
| |
| if range.start > max_it_index || range.end > max_it_index { |
| return Err(GicDistributorError::InvalidParameter); |
| } |
| |
| let (start_reg, start_bit) = Self::index_to_reg_and_bit(range.start); |
| let (end_reg, end_bit) = Self::index_to_reg_and_bit(range.end); |
| |
| let mut modify_igroupr = |index: usize, mask| { |
| let value = field!(self.gicd, igroupr).split()[index].read(); |
| field!(self.gicd, igroupr).split()[index].write(f(value, mask)); |
| }; |
| |
| if start_reg == end_reg { |
| let mask = Self::create_mask(start_bit..end_bit); |
| modify_igroupr(start_reg, mask); |
| } else { |
| let start_mask = Self::create_mask(start_bit..32); |
| modify_igroupr(start_reg, start_mask); |
| |
| for reg_index in (start_reg + 1)..end_reg { |
| modify_igroupr(reg_index, 0xffff_ffff); |
| } |
| |
| if end_reg < Self::MAX_IT { |
| let end_mask = Self::create_mask(0..end_bit); |
| modify_igroupr(end_reg, end_mask); |
| } |
| } |
| |
| Ok(()) |
| } |
| |
| fn index_to_reg_and_bit(index: usize) -> (usize, usize) { |
| (index >> 5, index & 0x1f) |
| } |
| |
| fn create_mask(range: Range<usize>) -> u32 { |
| if range.start <= 31 && range.end > 0 { |
| (0xffffffff << range.start) & (0xffffffff >> (32 - range.end)) |
| } else { |
| 0 |
| } |
| } |
| } |
| |
| /// # GIC CPU Interface |
| /// |
| /// The GIC architecture supports a CPU interface that provides a register interface to a PE in |
| /// the system. |
| #[cfg(target_arch = "aarch64")] |
| pub struct GicCpuInterface; |
| |
| #[cfg(target_arch = "aarch64")] |
| impl GicCpuInterface { |
| const IGRPEN1_ENABLE: u64 = 0x1; |
| |
| /// Provides an interrupt priority filter. Only interrupts with a higher priority than the value |
| /// in this register are signaled to the PE. |
| pub fn set_interrupt_priority_mask(mask: u8) { |
| // SAFETY: The ICC_PMR_EL1 register is available if FEAT_GICv3 is supported. |
| unsafe { |
| core::arch::asm!("msr ICC_PMR_EL1, {pmr}", pmr = in(reg) mask as u64); |
| } |
| } |
| |
| /// Enable Group 1 interrupts for current security state |
| pub fn enable_group1() { |
| // SAFETY: The ICC_IGRPEN1_EL1 register is available if FEAT_GICv3 is supported. |
| unsafe { |
| core::arch::asm!("msr ICC_IGRPEN1_EL1, {igrpen1}", igrpen1 = in(reg)Self::IGRPEN1_ENABLE); |
| } |
| } |
| |
| /// Disable Group 1 interrupts for current security state |
| pub fn disable_group1() { |
| // SAFETY: The ICC_IGRPEN1_EL1 register is available if FEAT_GICv3 is supported. |
| unsafe { |
| core::arch::asm!("msr ICC_IGRPEN1_EL1, {igrpen1}", igrpen1 = in(reg)0u64); |
| } |
| } |
| } |
| |
| // SAFETY: It is safe to access GicDistributor from any core/thread. |
| unsafe impl Sync for GicDistributor<'_> {} |
| |
| #[cfg(test)] |
| mod tests { |
| use super::*; |
| |
| #[test] |
| fn test_gicd_size() { |
| assert_eq!(core::mem::size_of::<GICDRegisters>(), 0x1_0000); |
| } |
| |
| #[test] |
| fn test_bitmask() { |
| assert_eq!(0x0000_0000, GicDistributor::create_mask(0..0)); |
| assert_eq!(0x0000_0001, GicDistributor::create_mask(0..1)); |
| assert_eq!(0x8000_0000, GicDistributor::create_mask(31..32)); |
| assert_eq!(0x0000_00ff, GicDistributor::create_mask(0..8)); |
| assert_eq!(0xffff_ffff, GicDistributor::create_mask(0..32)); |
| } |
| |
| fn check_igroup(expected: &[u32], mut registers: UniqueMmioPointer<GICDRegisters>) { |
| for (index, expected) in expected.iter().enumerate() { |
| let actual = field!(registers, igroupr).split()[index].read(); |
| assert_eq!(*expected, actual); |
| } |
| } |
| |
| #[test] |
| fn test_gicd() { |
| let area: [u32; 0x4000] = [0; 0x4000]; |
| let mut registers: GICDRegisters = unsafe { core::mem::transmute(area) }; |
| |
| { |
| let mut gic_distributor = GicDistributor::new((&mut registers).into()); |
| gic_distributor.select_group0_range(8..16).unwrap(); |
| } |
| |
| let mut expected = [0xffff_ffffu32; 32]; |
| expected[0] = 0xffff_00ff; |
| check_igroup(&expected, (&mut registers).into()); |
| |
| let mut gic_distributor = GicDistributor::new((&mut registers).into()); |
| gic_distributor.select_group0_range(8..40).unwrap(); |
| let mut expected = [0xffff_ffffu32; 32]; |
| expected[0] = 0x0000_00ff; |
| expected[1] = 0xffff_ff00; |
| check_igroup(&expected, (&mut registers).into()); |
| |
| let mut gic_distributor = GicDistributor::new((&mut registers).into()); |
| gic_distributor.select_group0_range(8..72).unwrap(); |
| let mut expected = [0xffff_ffffu32; 32]; |
| expected[0] = 0x0000_00ff; |
| expected[1] = 0x0000_0000; |
| expected[2] = 0xffff_ff00; |
| check_igroup(&expected, (&mut registers).into()); |
| |
| let mut gic_distributor = GicDistributor::new((&mut registers).into()); |
| gic_distributor.select_group0_range(8..64).unwrap(); |
| let mut expected = [0xffff_ffffu32; 32]; |
| expected[0] = 0x0000_00ff; |
| expected[1] = 0x0000_0000; |
| check_igroup(&expected, (&mut registers).into()); |
| |
| let mut gic_distributor = GicDistributor::new((&mut registers).into()); |
| gic_distributor.select_group0_range(1..1023).unwrap(); |
| let mut expected = [0x0000_0000u32; 32]; |
| expected[0] = 0x0000_0001; |
| expected[31] = 0x8000_0000; |
| check_igroup(&expected, (&mut registers).into()); |
| |
| let mut gic_distributor = GicDistributor::new((&mut registers).into()); |
| gic_distributor.select_group0_range(0..1024).unwrap(); |
| let expected = [0x0000_0000u32; 32]; |
| check_igroup(&expected, (&mut registers).into()); |
| |
| let mut gic_distributor = GicDistributor::new((&mut registers).into()); |
| assert!(gic_distributor.select_group0_range(0..2048).is_err()); |
| let expected = [0xffff_ffffu32; 32]; |
| check_igroup(&expected, (&mut registers).into()); |
| } |
| } |