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 + } + } + } +}