diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..531ddd1
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,55 @@
+name: CI
+
+on: [push, pull_request]
+
+jobs:
+ ci:
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ rust-toolchain: [nightly]
+ targets: [x86_64-unknown-linux-gnu, x86_64-unknown-none, riscv64gc-unknown-none-elf, aarch64-unknown-none-softfloat]
+ steps:
+ - uses: actions/checkout@v4
+ - uses: dtolnay/rust-toolchain@nightly
+ with:
+ toolchain: ${{ matrix.rust-toolchain }}
+ components: rust-src, clippy, rustfmt
+ targets: ${{ matrix.targets }}
+ - name: Check rust version
+ run: rustc --version --verbose
+ - name: Check code format
+ run: cargo fmt --all -- --check
+ - name: Clippy
+ run: cargo clippy --target ${{ matrix.targets }} --all-features -- -A clippy::new_without_default
+ - name: Build
+ run: cargo build --target ${{ matrix.targets }} --all-features
+ - name: Unit test
+ if: ${{ matrix.targets == 'x86_64-unknown-linux-gnu' }}
+ run: cargo test --target ${{ matrix.targets }} -- --nocapture
+
+ doc:
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ permissions:
+ contents: write
+ env:
+ default-branch: ${{ format('refs/heads/{0}', github.event.repository.default_branch) }}
+ RUSTDOCFLAGS: -D rustdoc::broken_intra_doc_links -D missing-docs
+ steps:
+ - uses: actions/checkout@v4
+ - uses: dtolnay/rust-toolchain@nightly
+ - name: Build docs
+ continue-on-error: ${{ github.ref != env.default-branch && github.event_name != 'pull_request' }}
+ run: |
+ cargo doc --no-deps --all-features
+ printf '' $(cargo tree | head -1 | cut -d' ' -f1) > target/doc/index.html
+ - name: Deploy to Github Pages
+ if: ${{ github.ref == env.default-branch }}
+ uses: JamesIves/github-pages-deploy-action@v4
+ with:
+ single-commit: true
+ branch: gh-pages
+ folder: target/doc
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..ff78c42
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,4 @@
+/target
+/.vscode
+.DS_Store
+Cargo.lock
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..19161fd
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,13 @@
+[package]
+name = "arm_gic"
+version = "0.1.0"
+edition = "2021"
+authors = ["Yuekai Jia "]
+description = "ARM Generic Interrupt Controller (GIC) register definitions and basic operations"
+license = "GPL-3.0-or-later OR Apache-2.0 OR MulanPSL-2.0"
+homepage = "https://github.com/arceos-org/arceos"
+repository = "https://github.com/arceos-org/arm_gic"
+documentation = "https://arceos-org.github.io/arm_gic"
+
+[dependencies]
+tock-registers = "0.8"
diff --git a/src/gic_v2.rs b/src/gic_v2.rs
new file mode 100644
index 0000000..9db18f8
--- /dev/null
+++ b/src/gic_v2.rs
@@ -0,0 +1,275 @@
+//! Types and definitions for GICv2.
+//!
+//! The official documentation:
+
+use core::ptr::NonNull;
+
+use crate::{TriggerMode, GIC_MAX_IRQ, SPI_RANGE};
+use tock_registers::interfaces::{Readable, Writeable};
+use tock_registers::register_structs;
+use tock_registers::registers::{ReadOnly, ReadWrite, WriteOnly};
+
+register_structs! {
+ /// GIC Distributor registers.
+ #[allow(non_snake_case)]
+ GicDistributorRegs {
+ /// Distributor Control Register.
+ (0x0000 => CTLR: ReadWrite),
+ /// Interrupt Controller Type Register.
+ (0x0004 => TYPER: ReadOnly),
+ /// Distributor Implementer Identification Register.
+ (0x0008 => IIDR: ReadOnly),
+ (0x000c => _reserved_0),
+ /// Interrupt Group Registers.
+ (0x0080 => IGROUPR: [ReadWrite; 0x20]),
+ /// Interrupt Set-Enable Registers.
+ (0x0100 => ISENABLER: [ReadWrite; 0x20]),
+ /// Interrupt Clear-Enable Registers.
+ (0x0180 => ICENABLER: [ReadWrite; 0x20]),
+ /// Interrupt Set-Pending Registers.
+ (0x0200 => ISPENDR: [ReadWrite; 0x20]),
+ /// Interrupt Clear-Pending Registers.
+ (0x0280 => ICPENDR: [ReadWrite; 0x20]),
+ /// Interrupt Set-Active Registers.
+ (0x0300 => ISACTIVER: [ReadWrite; 0x20]),
+ /// Interrupt Clear-Active Registers.
+ (0x0380 => ICACTIVER: [ReadWrite; 0x20]),
+ /// Interrupt Priority Registers.
+ (0x0400 => IPRIORITYR: [ReadWrite; 0x100]),
+ /// Interrupt Processor Targets Registers.
+ (0x0800 => ITARGETSR: [ReadWrite; 0x100]),
+ /// Interrupt Configuration Registers.
+ (0x0c00 => ICFGR: [ReadWrite; 0x40]),
+ (0x0d00 => _reserved_1),
+ /// Software Generated Interrupt Register.
+ (0x0f00 => SGIR: WriteOnly),
+ (0x0f04 => @END),
+ }
+}
+
+register_structs! {
+ /// GIC CPU Interface registers.
+ #[allow(non_snake_case)]
+ GicCpuInterfaceRegs {
+ /// CPU Interface Control Register.
+ (0x0000 => CTLR: ReadWrite),
+ /// Interrupt Priority Mask Register.
+ (0x0004 => PMR: ReadWrite),
+ /// Binary Point Register.
+ (0x0008 => BPR: ReadWrite),
+ /// Interrupt Acknowledge Register.
+ (0x000c => IAR: ReadOnly),
+ /// End of Interrupt Register.
+ (0x0010 => EOIR: WriteOnly),
+ /// Running Priority Register.
+ (0x0014 => RPR: ReadOnly),
+ /// Highest Priority Pending Interrupt Register.
+ (0x0018 => HPPIR: ReadOnly),
+ (0x001c => _reserved_1),
+ /// CPU Interface Identification Register.
+ (0x00fc => IIDR: ReadOnly),
+ (0x0100 => _reserved_2),
+ /// Deactivate Interrupt Register.
+ (0x1000 => DIR: WriteOnly),
+ (0x1004 => @END),
+ }
+}
+
+/// The GIC distributor.
+///
+/// The Distributor block performs interrupt prioritization and distribution
+/// to the CPU interface blocks that connect to the processors in the system.
+///
+/// The Distributor provides a programming interface for:
+/// - Globally enabling the forwarding of interrupts to the CPU interfaces.
+/// - Enabling or disabling each interrupt.
+/// - Setting the priority level of each interrupt.
+/// - Setting the target processor list of each interrupt.
+/// - Setting each peripheral interrupt to be level-sensitive or edge-triggered.
+/// - Setting each interrupt as either Group 0 or Group 1.
+/// - Forwarding an SGI to one or more target processors.
+///
+/// In addition, the Distributor provides:
+/// - visibility of the state of each interrupt
+/// - a mechanism for software to set or clear the pending state of a peripheral
+/// interrupt.
+pub struct GicDistributor {
+ base: NonNull,
+ max_irqs: usize,
+}
+
+/// The GIC CPU interface.
+///
+/// Each CPU interface block performs priority masking and preemption
+/// handling for a connected processor in the system.
+///
+/// Each CPU interface provides a programming interface for:
+///
+/// - enabling the signaling of interrupt requests to the processor
+/// - acknowledging an interrupt
+/// - indicating completion of the processing of an interrupt
+/// - setting an interrupt priority mask for the processor
+/// - defining the preemption policy for the processor
+/// - determining the highest priority pending interrupt for the processor.
+pub struct GicCpuInterface {
+ base: NonNull,
+}
+
+unsafe impl Send for GicDistributor {}
+unsafe impl Sync for GicDistributor {}
+
+unsafe impl Send for GicCpuInterface {}
+unsafe impl Sync for GicCpuInterface {}
+
+impl GicDistributor {
+ /// Construct a new GIC distributor instance from the base address.
+ pub const fn new(base: *mut u8) -> Self {
+ Self {
+ base: NonNull::new(base).unwrap().cast(),
+ max_irqs: GIC_MAX_IRQ,
+ }
+ }
+
+ const fn regs(&self) -> &GicDistributorRegs {
+ unsafe { self.base.as_ref() }
+ }
+
+ /// The number of implemented CPU interfaces.
+ pub fn cpu_num(&self) -> usize {
+ ((self.regs().TYPER.get() as usize >> 5) & 0b111) + 1
+ }
+
+ /// The maximum number of interrupts that the GIC supports
+ pub fn max_irqs(&self) -> usize {
+ ((self.regs().TYPER.get() as usize & 0b11111) + 1) * 32
+ }
+
+ /// Configures the trigger mode for the given interrupt.
+ pub fn configure_interrupt(&mut self, vector: usize, tm: TriggerMode) {
+ // Only configurable for SPI interrupts
+ if vector >= self.max_irqs || vector < SPI_RANGE.start {
+ return;
+ }
+
+ // type is encoded with two bits, MSB of the two determine type
+ // 16 irqs encoded per ICFGR register
+ let reg_idx = vector >> 4;
+ let bit_shift = ((vector & 0xf) << 1) + 1;
+ let mut reg_val = self.regs().ICFGR[reg_idx].get();
+ match tm {
+ TriggerMode::Edge => reg_val |= 1 << bit_shift,
+ TriggerMode::Level => reg_val &= !(1 << bit_shift),
+ }
+ self.regs().ICFGR[reg_idx].set(reg_val);
+ }
+
+ /// Enables or disables the given interrupt.
+ pub fn set_enable(&mut self, vector: usize, enable: bool) {
+ if vector >= self.max_irqs {
+ return;
+ }
+ let reg = vector / 32;
+ let mask = 1 << (vector % 32);
+ if enable {
+ self.regs().ISENABLER[reg].set(mask);
+ } else {
+ self.regs().ICENABLER[reg].set(mask);
+ }
+ }
+
+ /// Initializes the GIC distributor.
+ ///
+ /// It disables all interrupts, sets the target of all SPIs to CPU 0,
+ /// configures all SPIs to be edge-triggered, and finally enables the GICD.
+ ///
+ /// This function should be called only once.
+ pub fn init(&mut self) {
+ let max_irqs = self.max_irqs();
+ assert!(max_irqs <= GIC_MAX_IRQ);
+ self.max_irqs = max_irqs;
+
+ // Disable all interrupts
+ for i in (0..max_irqs).step_by(32) {
+ self.regs().ICENABLER[i / 32].set(u32::MAX);
+ self.regs().ICPENDR[i / 32].set(u32::MAX);
+ }
+ if self.cpu_num() > 1 {
+ for i in (SPI_RANGE.start..max_irqs).step_by(4) {
+ // Set external interrupts to target cpu 0
+ self.regs().ITARGETSR[i / 4].set(0x01_01_01_01);
+ }
+ }
+ // Initialize all the SPIs to edge triggered
+ for i in SPI_RANGE.start..max_irqs {
+ self.configure_interrupt(i, TriggerMode::Edge);
+ }
+
+ // enable GIC0
+ self.regs().CTLR.set(1);
+ }
+}
+
+impl GicCpuInterface {
+ /// Construct a new GIC CPU interface instance from the base address.
+ pub const fn new(base: *mut u8) -> Self {
+ Self {
+ base: NonNull::new(base).unwrap().cast(),
+ }
+ }
+
+ const fn regs(&self) -> &GicCpuInterfaceRegs {
+ unsafe { self.base.as_ref() }
+ }
+
+ /// Returns the interrupt ID of the highest priority pending interrupt for
+ /// the CPU interface. (read GICC_IAR)
+ ///
+ /// The read returns a spurious interrupt ID of `1023` if the distributor
+ /// or the CPU interface are disabled, or there is no pending interrupt on
+ /// the CPU interface.
+ pub fn iar(&self) -> u32 {
+ self.regs().IAR.get()
+ }
+
+ /// Informs the CPU interface that it has completed the processing of the
+ /// specified interrupt. (write GICC_EOIR)
+ ///
+ /// The value written must be the value returns from [`Self::iar`].
+ pub fn eoi(&self, iar: u32) {
+ self.regs().EOIR.set(iar);
+ }
+
+ /// handles the signaled interrupt.
+ ///
+ /// It first reads GICC_IAR to obtain the pending interrupt ID and then
+ /// calls the given handler. After the handler returns, it writes GICC_EOIR
+ /// to acknowledge the interrupt.
+ ///
+ /// If read GICC_IAR returns a spurious interrupt ID of `1023`, it does
+ /// nothing.
+ pub fn handle_irq(&self, handler: F)
+ where
+ F: FnOnce(u32),
+ {
+ let iar = self.iar();
+ let vector = iar & 0x3ff;
+ if vector < 1020 {
+ handler(vector);
+ self.eoi(iar);
+ } else {
+ // spurious
+ }
+ }
+
+ /// Initializes the GIC CPU interface.
+ ///
+ /// It unmask interrupts at all priority levels and enables the GICC.
+ ///
+ /// This function should be called only once.
+ pub fn init(&self) {
+ // enable GIC0
+ self.regs().CTLR.set(1);
+ // unmask interrupts at all priority levels
+ self.regs().PMR.set(0xff);
+ }
+}
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644
index 0000000..56e3578
--- /dev/null
+++ b/src/lib.rs
@@ -0,0 +1,91 @@
+//! ARM Generic Interrupt Controller (GIC) register definitions and basic
+//! operations.
+
+#![no_std]
+#![feature(const_ptr_as_ref)]
+#![feature(const_option)]
+#![feature(const_nonnull_new)]
+
+pub mod gic_v2;
+
+use core::ops::Range;
+
+/// Interrupt ID 0-15 are used for SGIs (Software-generated interrupt).
+///
+/// SGI is an interrupt generated by software writing to a GICD_SGIR register in
+/// the GIC. The system uses SGIs for interprocessor communication.
+pub const SGI_RANGE: Range = 0..16;
+
+/// Interrupt ID 16-31 are used for PPIs (Private Peripheral Interrupt).
+///
+/// PPI is a peripheral interrupt that is specific to a single processor.
+pub const PPI_RANGE: Range = 16..32;
+
+/// Interrupt ID 32-1019 are used for SPIs (Shared Peripheral Interrupt).
+///
+/// SPI is a peripheral interrupt that the Distributor can route to any of a
+/// specified combination of processors.
+pub const SPI_RANGE: Range = 32..1020;
+
+/// Maximum number of interrupts supported by the GIC.
+pub const GIC_MAX_IRQ: usize = 1024;
+
+/// Interrupt trigger mode.
+pub enum TriggerMode {
+ /// Edge-triggered.
+ ///
+ /// This is an interrupt that is asserted on detection of a rising edge of
+ /// an interrupt signal and then, regardless of the state of the signal,
+ /// remains asserted until it is cleared by the conditions defined by this
+ /// specification.
+ Edge = 0,
+ /// Level-sensitive.
+ ///
+ /// This is an interrupt that is asserted whenever the interrupt signal
+ /// level is active, and deasserted whenever the level is not active.
+ Level = 1,
+}
+
+/// Different types of interrupt that the GIC handles.
+pub enum InterruptType {
+ /// Software-generated interrupt.
+ ///
+ /// SGIs are typically used for inter-processor communication and are
+ /// generated by a write to an SGI register in the GIC.
+ SGI,
+ /// Private Peripheral Interrupt.
+ ///
+ /// Peripheral interrupts that are private to one core.
+ PPI,
+ /// Shared Peripheral Interrupt.
+ ///
+ /// Peripheral interrupts that can delivered to any connected core.
+ SPI,
+}
+
+/// Translate an interrupt of a given type to a GIC INTID.
+pub const fn translate_irq(id: usize, int_type: InterruptType) -> Option {
+ match int_type {
+ InterruptType::SGI => {
+ if id < SGI_RANGE.end {
+ Some(id)
+ } else {
+ None
+ }
+ }
+ InterruptType::PPI => {
+ if id < PPI_RANGE.end - PPI_RANGE.start {
+ Some(id + PPI_RANGE.start)
+ } else {
+ None
+ }
+ }
+ InterruptType::SPI => {
+ if id < SPI_RANGE.end - SPI_RANGE.start {
+ Some(id + SPI_RANGE.start)
+ } else {
+ None
+ }
+ }
+ }
+}