diff --git a/riscv-peripheral/.github/workflows/clippy.yml b/riscv-peripheral/.github/workflows/clippy.yml new file mode 100644 index 00000000..44d91413 --- /dev/null +++ b/riscv-peripheral/.github/workflows/clippy.yml @@ -0,0 +1,44 @@ +on: + push: + branches: [ main ] + pull_request: + merge_group: + +name: Lints compliance check + +env: + CLIPPY_PARAMS: -W clippy::all -W clippy::pedantic -W clippy::nursery -W clippy::cargo + +jobs: + clippy: + strategy: + matrix: + toolchain: [ stable, nightly ] + cargo_flags: [ --all-features, --no-default-features ] + include: + # Nightly is only for reference and allowed to fail + - toolchain: nightly + experimental: true + # async traits are still not supported in stable + - toolchain: stable + cargo_flags: --all-features + experimental: true + runs-on: ubuntu-latest + continue-on-error: ${{ matrix.experimental || false }} + steps: + - uses: actions/checkout@v3 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ matrix.toolchain }} + components: clippy + - name: Run clippy + run: cargo clippy --all ${{ matrix.cargo_flags }} -- -D warnings + + # Job to check that all the lint checks succeeded + clippy-check: + needs: + - clippy + runs-on: ubuntu-latest + if: always() + steps: + - run: jq --exit-status 'all(.result == "success")' <<< '${{ toJson(needs) }}' diff --git a/riscv-peripheral/.github/workflows/rust.yml b/riscv-peripheral/.github/workflows/rust.yml new file mode 100644 index 00000000..31000a27 --- /dev/null +++ b/riscv-peripheral/.github/workflows/rust.yml @@ -0,0 +1,22 @@ +name: Rust + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +env: + CARGO_TERM_COLOR: always + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - name: Build + run: cargo build --verbose + - name: Run tests + run: cargo test --verbose diff --git a/riscv-peripheral/.github/workflows/rustfmt.yml b/riscv-peripheral/.github/workflows/rustfmt.yml new file mode 100644 index 00000000..3d792dc7 --- /dev/null +++ b/riscv-peripheral/.github/workflows/rustfmt.yml @@ -0,0 +1,18 @@ +on: + push: + branches: [ main ] + pull_request: + merge_group: + +name: Code formatting check + +jobs: + rustfmt: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt + - name: Run Rustfmt + run: cargo fmt --all -- --check --verbose diff --git a/riscv-peripheral/.gitignore b/riscv-peripheral/.gitignore new file mode 100644 index 00000000..6cfa27d8 --- /dev/null +++ b/riscv-peripheral/.gitignore @@ -0,0 +1,17 @@ +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb + +.DS_Store +.vscode/ diff --git a/riscv-peripheral/Cargo.toml b/riscv-peripheral/Cargo.toml new file mode 100644 index 00000000..1eabf062 --- /dev/null +++ b/riscv-peripheral/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "riscv-peripheral" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +embedded-hal = "1.0.0-rc.2" +# embedded-hal-async = { version = "1.0.0-rc.1", optional = true } +riscv = { git = "https://github.com/rust-embedded/riscv", branch = "master" } + +[features] +# hal-async = ["embedded-hal-async"] + +[package.metadata.docs.rs] +default-target = "riscv64imac-unknown-none-elf" +targets = [ + "riscv32i-unknown-none-elf", "riscv32imc-unknown-none-elf", "riscv32imac-unknown-none-elf", + "riscv64imac-unknown-none-elf", "riscv64gc-unknown-none-elf", +] diff --git a/riscv-peripheral/README.md b/riscv-peripheral/README.md new file mode 100644 index 00000000..fffd8329 --- /dev/null +++ b/riscv-peripheral/README.md @@ -0,0 +1,8 @@ +# `riscv-peripheral` + +> Standard RISC-V peripherals for embedded systems written in Rust + +## Minimum Supported Rust Version (MSRV) + +This crate is guaranteed to compile on stable Rust 1.61 and up. It *might* +compile with older versions but that may change in any new patch release. diff --git a/riscv-peripheral/examples/e310x.rs b/riscv-peripheral/examples/e310x.rs new file mode 100644 index 00000000..df21cf11 --- /dev/null +++ b/riscv-peripheral/examples/e310x.rs @@ -0,0 +1,165 @@ +use riscv_peripheral::{ + aclint::HartIdNumber, + plic::{ContextNumber, InterruptNumber, PriorityNumber}, +}; + +#[repr(u16)] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum HartId { + H0 = 0, +} + +unsafe impl HartIdNumber for HartId { + const MAX_HART_ID_NUMBER: u16 = 0; + + #[inline] + fn number(self) -> u16 { + self as _ + } + + #[inline] + fn from_number(number: u16) -> Result { + if number > Self::MAX_HART_ID_NUMBER { + Err(number) + } else { + // SAFETY: valid context number + Ok(unsafe { core::mem::transmute(number) }) + } + } +} + +unsafe impl ContextNumber for HartId { + const MAX_CONTEXT_NUMBER: u16 = 0; + + #[inline] + fn number(self) -> u16 { + self as _ + } + + #[inline] + fn from_number(number: u16) -> Result { + if number > Self::MAX_CONTEXT_NUMBER { + Err(number) + } else { + // SAFETY: valid context number + Ok(unsafe { core::mem::transmute(number) }) + } + } +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[repr(u16)] +pub enum Interrupt { + WATCHDOG = 1, + RTC = 2, + UART0 = 3, + UART1 = 4, + QSPI0 = 5, + QSPI1 = 6, + QSPI2 = 7, + GPIO0 = 8, + GPIO1 = 9, + GPIO2 = 10, + GPIO3 = 11, + GPIO4 = 12, + GPIO5 = 13, + GPIO6 = 14, + GPIO7 = 15, + GPIO8 = 16, + GPIO9 = 17, + GPIO10 = 18, + GPIO11 = 19, + GPIO12 = 20, + GPIO13 = 21, + GPIO14 = 22, + GPIO15 = 23, + GPIO16 = 24, + GPIO17 = 25, + GPIO18 = 26, + GPIO19 = 27, + GPIO20 = 28, + GPIO21 = 29, + GPIO22 = 30, + GPIO23 = 31, + GPIO24 = 32, + GPIO25 = 33, + GPIO26 = 34, + GPIO27 = 35, + GPIO28 = 36, + GPIO29 = 37, + GPIO30 = 38, + GPIO31 = 39, + PWM0CMP0 = 40, + PWM0CMP1 = 41, + PWM0CMP2 = 42, + PWM0CMP3 = 43, + PWM1CMP0 = 44, + PWM1CMP1 = 45, + PWM1CMP2 = 46, + PWM1CMP3 = 47, + PWM2CMP0 = 48, + PWM2CMP1 = 49, + PWM2CMP2 = 50, + PWM2CMP3 = 51, + I2C0 = 52, +} + +unsafe impl InterruptNumber for Interrupt { + const MAX_INTERRUPT_NUMBER: u16 = 52; + + #[inline] + fn number(self) -> u16 { + self as _ + } + + #[inline] + fn from_number(number: u16) -> Result { + if number == 0 || number > Self::MAX_INTERRUPT_NUMBER { + Err(number) + } else { + // SAFETY: valid interrupt number + Ok(unsafe { core::mem::transmute(number) }) + } + } +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[repr(u8)] +pub enum Priority { + P0 = 0, + P1 = 1, + P2 = 2, + P3 = 3, + P4 = 4, + P5 = 5, + P6 = 6, + P7 = 7, +} + +unsafe impl PriorityNumber for Priority { + const MAX_PRIORITY_NUMBER: u8 = 7; + + #[inline] + fn number(self) -> u8 { + self as _ + } + + #[inline] + fn from_number(number: u8) -> Result { + if number > Self::MAX_PRIORITY_NUMBER { + Err(number) + } else { + // SAFETY: valid priority number + Ok(unsafe { core::mem::transmute(number) }) + } + } +} + +riscv_peripheral::clint_codegen!( + base 0x0200_0000, + freq 32_768, + mtimecmps [mtimecmp0=(HartId::H0,"`H0`")], + msips [msip0=(HartId::H0,"`H0`")], +); + +fn main() {} diff --git a/riscv-peripheral/src/aclint.rs b/riscv-peripheral/src/aclint.rs new file mode 100644 index 00000000..c19b6446 --- /dev/null +++ b/riscv-peripheral/src/aclint.rs @@ -0,0 +1,166 @@ +//! Devices for the Core Local Interruptor (CLINT) and Advanced CLINT (ACLINT) peripherals. +//! +//! CLINT pecification: +//! ACLINT Specification: + +pub mod mswi; +pub mod mtimer; +pub mod sswi; + +/// Trait for enums of HART IDs in (A)CLINT peripherals. +/// +/// # Note +/// +/// If your target only has one HART (HART ID 0), you don't need to implement this trait. +/// Instead, you can access directly to the base registers through the `(A)CLINT` structs. +/// +/// # Safety +/// +/// * This trait must only be implemented on a PAC of a target with a PLIC peripheral. +/// * This trait must only be implemented on enums of HART IDs. +/// * Each enum variant must represent a distinct value (no duplicates are permitted). +/// * Each enum variant must always return the same value (do not change at runtime). +/// * All the HART ID numbers must be less than or equal to `MAX_HART_ID_NUMBER`. +/// * `MAX_HART_ID_NUMBER` must coincide with the highest allowed HART ID number. +pub unsafe trait HartIdNumber: Copy { + /// Highest number assigned to a HART ID. + const MAX_HART_ID_NUMBER: u16; + + /// Converts a HART Id to its corresponding number. + fn number(self) -> u16; + + /// Tries to convert a number to a valid HART ID. + /// If the conversion fails, it returns an error with the number back. + fn from_number(value: u16) -> Result; +} + +/// Trait for a CLINT peripheral. +/// +/// # Safety +/// +/// * This trait must only be implemented on a PAC of a target with a CLINT peripheral. +/// * The CLINT peripheral base address `BASE` must be valid for the target device. +pub unsafe trait Clint: Copy { + /// Base address of the CLINT peripheral. + const BASE: usize; +} + +/// Interface for a CLINT peripheral. +/// +/// The RISC-V standard does not specify a fixed location for the CLINT. +/// Thus, each platform must specify the base address of the CLINT on the platform. +/// The base address, as well as all the associated types, are defined in the [`Clint`] trait. +/// +/// The CLINT standard allows up to 4_095 different HARTs connected to the CLINT. +/// Each HART has an assigned index starting from 0 to up to 4_094. +/// In this way, each HART's timer and software interrupts can be independently configured. +#[allow(clippy::upper_case_acronyms)] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct CLINT { + _marker: core::marker::PhantomData, +} + +impl CLINT { + const MTIMECMP_OFFSET: usize = 0x4000; + + const MTIME_OFFSET: usize = 0xBFF8; + + /// Returns the `MSWI` peripheral. + #[inline] + pub const fn mswi() -> mswi::MSWI { + // SAFETY: valid base address + unsafe { mswi::MSWI::new(C::BASE) } + } + + /// Returns the `MTIMER` peripheral. + #[inline] + pub const fn mtimer() -> mtimer::MTIMER { + // SAFETY: valid base address + unsafe { + mtimer::MTIMER::new( + C::BASE + Self::MTIMECMP_OFFSET, + C::BASE + Self::MTIME_OFFSET, + ) + } + } +} + +#[cfg(test)] +pub(crate) mod test { + use super::HartIdNumber; + + #[derive(Clone, Copy, Debug, Eq, PartialEq)] + #[repr(u16)] + pub(crate) enum HartId { + H0 = 0, + H1 = 1, + H2 = 2, + } + + unsafe impl HartIdNumber for HartId { + const MAX_HART_ID_NUMBER: u16 = 2; + + #[inline] + fn number(self) -> u16 { + self as _ + } + + #[inline] + fn from_number(number: u16) -> Result { + if number > Self::MAX_HART_ID_NUMBER { + Err(number) + } else { + // SAFETY: valid context number + Ok(unsafe { core::mem::transmute(number) }) + } + } + } + + #[test] + fn check_hart_id_enum() { + assert_eq!(HartId::H0.number(), 0); + assert_eq!(HartId::H1.number(), 1); + assert_eq!(HartId::H2.number(), 2); + + assert_eq!(HartId::from_number(0), Ok(HartId::H0)); + assert_eq!(HartId::from_number(1), Ok(HartId::H1)); + assert_eq!(HartId::from_number(2), Ok(HartId::H2)); + + assert_eq!(HartId::from_number(3), Err(3)); + } + + #[allow(dead_code)] + #[test] + fn check_clint() { + // Call CLINT macro with a base address and a list of mtimecmps for easing access to per-HART mtimecmp regs. + crate::clint_codegen!( + base 0x0200_0000, + mtimecmps [mtimecmp0=(HartId::H0,"`H0`"), mtimecmp1=(HartId::H1,"`H1`"), mtimecmp2=(HartId::H2,"`H2`")], + msips [msip0=(HartId::H0,"`H0`"), msip1=(HartId::H1,"`H1`"), msip2=(HartId::H2,"`H2`")], + ); + + let mswi = CLINT::mswi(); + let mtimer = CLINT::mtimer(); + + assert_eq!(mswi.msip0.get_ptr() as usize, 0x0200_0000); + assert_eq!(mtimer.mtimecmp0.get_ptr() as usize, 0x0200_4000); + assert_eq!(mtimer.mtime.get_ptr() as usize, 0x0200_bff8); + + let mtimecmp0 = mtimer.mtimecmp(HartId::H0); + let mtimecmp1 = mtimer.mtimecmp(HartId::H1); + let mtimecmp2 = mtimer.mtimecmp(HartId::H2); + + assert_eq!(mtimecmp0.get_ptr() as usize, 0x0200_4000); + assert_eq!(mtimecmp1.get_ptr() as usize, 0x0200_4000 + 8); // 8 bytes per register + assert_eq!(mtimecmp2.get_ptr() as usize, 0x0200_4000 + 2 * 8); + + assert_eq!(CLINT::mtime(), mtimer.mtime); + assert_eq!(CLINT::mtimecmp0(), mtimer.mtimecmp(HartId::H0)); + assert_eq!(CLINT::mtimecmp1(), mtimer.mtimecmp(HartId::H1)); + assert_eq!(CLINT::mtimecmp2(), mtimer.mtimecmp(HartId::H2)); + + assert_eq!(CLINT::msip0(), mswi.msip(HartId::H0)); + assert_eq!(CLINT::msip1(), mswi.msip(HartId::H1)); + assert_eq!(CLINT::msip2(), mswi.msip(HartId::H2)); + } +} diff --git a/riscv-peripheral/src/aclint/mswi.rs b/riscv-peripheral/src/aclint/mswi.rs new file mode 100644 index 00000000..3af4af75 --- /dev/null +++ b/riscv-peripheral/src/aclint/mswi.rs @@ -0,0 +1,87 @@ +//! Machine-level Software Interrupt Device. + +pub use super::HartIdNumber; +use crate::common::unsafe_peripheral; + +/// MSWI peripheral. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[repr(transparent)] +pub struct MSWI { + /// `MSIP` register for HART ID 0. In multi-HART architectures, + /// use [`MSWI::msip`] for accessing the `MSIP` of other HARTs. + pub msip0: MSIP, +} + +impl MSWI { + /// Creates a new `MSWI` peripheral from a base address. + /// + /// # Safety + /// + /// The base address must point to a valid `MSWI` peripheral. + #[inline] + pub const unsafe fn new(address: usize) -> Self { + Self { + msip0: MSIP::new(address), + } + } + + /// Returns the `MSIP` register for the HART which ID is `hart_id`. + /// + /// # Note + /// + /// For HART ID 0, you can simply use [`MSWI::msip0`]. + #[inline] + pub fn msip(&self, hart_id: H) -> MSIP { + // SAFETY: `hart_id` is valid for the target + unsafe { MSIP::new(self.msip0.get_ptr().offset(hart_id.number() as _) as _) } + } +} + +unsafe_peripheral!(MSIP, u32, RW); + +impl MSIP { + /// Returns `true` if a machine software interrupt is pending. + #[inline] + pub fn is_pending(self) -> bool { + self.register.read() != 0 + } + + /// Writes to the register to trigger a machine software interrupt. + #[inline] + pub fn pend(self) { + self.register.write(1); + } + + /// Clears the register to unpend a machine software interrupt. + #[inline] + pub fn unpend(self) { + self.register.write(0); + } +} + +#[cfg(test)] +mod test { + use super::super::test::HartId; + use super::*; + + #[test] + fn test_mswi() { + // slice to emulate the interrupt pendings register + let raw_reg = [0u32; HartId::MAX_HART_ID_NUMBER as usize + 1]; + // SAFETY: valid memory address + let mswi = unsafe { MSWI::new(raw_reg.as_ptr() as _) }; + + for i in 0..=HartId::MAX_HART_ID_NUMBER { + let hart_id = HartId::from_number(i).unwrap(); + let msip = mswi.msip(hart_id); + assert!(!msip.is_pending()); + assert_eq!(raw_reg[i as usize], 0); + msip.pend(); + assert!(msip.is_pending()); + assert_ne!(raw_reg[i as usize], 0); + msip.unpend(); + assert!(!msip.is_pending()); + assert_eq!(raw_reg[i as usize], 0); + } + } +} diff --git a/riscv-peripheral/src/aclint/mtimer.rs b/riscv-peripheral/src/aclint/mtimer.rs new file mode 100644 index 00000000..71445675 --- /dev/null +++ b/riscv-peripheral/src/aclint/mtimer.rs @@ -0,0 +1,79 @@ +//! Machine-level Timer Device. + +pub use super::HartIdNumber; +use crate::common::safe_peripheral; + +/// MTIMER peripheral. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct MTIMER { + /// `MTIMECMP` register for HART ID 0. In multi-HART architectures, + /// use [`MTIMER::mtimecmp`] for accessing the `MTIMECMP` of other HARTs. + pub mtimecmp0: MTIMECMP, + /// The `MTIME` register is shared among all the HARTs. + pub mtime: MTIME, +} + +impl MTIMER { + /// Creates a new `MTIMER` peripheral from a base address. + /// + /// # Safety + /// + /// The base addresses must point to valid `MTIMECMP` and `MTIME` peripherals. + #[inline] + pub const unsafe fn new(mtimecmp: usize, mtime: usize) -> Self { + Self { + mtimecmp0: MTIMECMP::new(mtimecmp), + mtime: MTIME::new(mtime), + } + } + + /// Returns the `MTIMECMP` register for the HART which ID is `hart_id`. + /// + /// # Note + /// + /// For HART ID 0, you can simply use [`MTIMER::mtimecmp0`]. + #[inline] + pub fn mtimecmp(&self, hart_id: H) -> MTIMECMP { + // SAFETY: `hart_id` is valid for the target + unsafe { MTIMECMP::new(self.mtimecmp0.get_ptr().offset(hart_id.number() as _) as _) } + } +} + +// MTIMECMP register. +safe_peripheral!(MTIMECMP, u64, RW); + +// MTIME register. +safe_peripheral!(MTIME, u64, RW); + +#[cfg(test)] +mod test { + use super::super::test::HartId; + use super::*; + + #[test] + fn check_mtimer() { + // slice to emulate the mtimecmp registers + let raw_mtimecmp = [0u64; HartId::MAX_HART_ID_NUMBER as usize + 1]; + let raw_mtime = 0u64; + // SAFETY: valid memory addresses + let mtimer = + unsafe { MTIMER::new(raw_mtimecmp.as_ptr() as _, &raw_mtime as *const u64 as _) }; + + assert_eq!( + mtimer.mtimecmp(HartId::H0).get_ptr() as usize, + raw_mtimecmp.as_ptr() as usize + ); + assert_eq!(mtimer.mtimecmp(HartId::H1).get_ptr() as usize, unsafe { + raw_mtimecmp.as_ptr().offset(1) + } + as usize); + assert_eq!(mtimer.mtimecmp(HartId::H2).get_ptr() as usize, unsafe { + raw_mtimecmp.as_ptr().offset(2) + } + as usize); + assert_eq!( + mtimer.mtime.get_ptr() as usize, + &raw_mtime as *const u64 as _ + ); + } +} diff --git a/riscv-peripheral/src/aclint/sswi.rs b/riscv-peripheral/src/aclint/sswi.rs new file mode 100644 index 00000000..51072d66 --- /dev/null +++ b/riscv-peripheral/src/aclint/sswi.rs @@ -0,0 +1,118 @@ +//! Supervisor-level Software Interrupt Device. + +pub use super::HartIdNumber; +use crate::common::unsafe_peripheral; + +/// SSWI peripheral. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[repr(transparent)] +pub struct SSWI { + /// `SETSSIP` register for HART ID 0. In multi-HART architectures, + /// use [`SSWI::setssip`] for accessing the `SETSSIP` of other HARTs. + pub setssip0: SETSSIP, +} + +impl SSWI { + /// Creates a new `SSWI` peripheral from a base address. + /// + /// # Safety + /// + /// The base address must point to a valid `SSWI` peripheral. + #[inline] + pub const unsafe fn new(address: usize) -> Self { + Self { + setssip0: SETSSIP::new(address), + } + } + + /// Returns `true` if a supervisor software interrupt is pending. + #[inline] + pub fn is_interrupting() -> bool { + riscv::register::sip::read().ssoft() + } + + /// Returns `true` if Supervisor Software Interrupts are enabled. + #[inline] + pub fn is_enabled() -> bool { + riscv::register::mie::read().ssoft() + } + + /// Sets the Supervisor Software Interrupt bit of the `mie` CSR. + /// This bit must be set for the `SSWI` to trigger supervisor software interrupts. + /// + /// # Safety + /// + /// Enabling the `SSWI` may break mask-based critical sections. + #[inline] + pub unsafe fn enable() { + riscv::register::mie::set_ssoft(); + } + + /// Clears the Supervisor Software Interrupt bit of the `mie` CSR. + /// When cleared, the `SSWI` cannot trigger supervisor software interrupts. + #[inline] + pub fn disable() { + // SAFETY: it is safe to disable interrupts + unsafe { riscv::register::mie::clear_ssoft() }; + } + + /// Returns the `SETSSIP` register for the HART which ID is `hart_id`. + /// + /// # Note + /// + /// For HART ID 0, you can simply use [`SSWI::setssip0`]. + #[inline] + pub fn setssip(&self, hart_id: H) -> SETSSIP { + // SAFETY: `hart_id` is valid for the target + unsafe { SETSSIP::new(self.setssip0.get_ptr().offset(hart_id.number() as _) as _) } + } +} + +unsafe_peripheral!(SETSSIP, u32, RW); + +impl SETSSIP { + /// Returns `true` if a supervisor software interrupt is pending. + #[inline] + pub fn is_pending(self) -> bool { + self.register.read() != 0 + } + + /// Writes to the register to trigger a supervisor software interrupt. + #[inline] + pub fn pend(self) { + self.register.write(1); + } + + /// Clears the register to unpend a supervisor software interrupt. + #[inline] + pub fn unpend(self) { + self.register.write(0); + } +} + +#[cfg(test)] +mod test { + use super::super::test::HartId; + use super::*; + + #[test] + fn test_sswi() { + // slice to emulate the interrupt pendings register + let raw_reg = [0u32; HartId::MAX_HART_ID_NUMBER as usize + 1]; + // SAFETY: valid memory address + let mswi = unsafe { SSWI::new(raw_reg.as_ptr() as _) }; + + for i in 0..=HartId::MAX_HART_ID_NUMBER { + let hart_id = HartId::from_number(i).unwrap(); + let setssip = mswi.setssip(hart_id); + assert!(!setssip.is_pending()); + assert_eq!(raw_reg[i as usize], 0); + setssip.pend(); + assert!(setssip.is_pending()); + assert_ne!(raw_reg[i as usize], 0); + setssip.unpend(); + assert!(!setssip.is_pending()); + assert_eq!(raw_reg[i as usize], 0); + } + } +} diff --git a/riscv-peripheral/src/common.rs b/riscv-peripheral/src/common.rs new file mode 100644 index 00000000..ceca166c --- /dev/null +++ b/riscv-peripheral/src/common.rs @@ -0,0 +1,407 @@ +//! Common definitions for all the peripheral registers. + +/// Read-only type state for `A` in [`Reg`]. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct RO; + +/// Write-only type state for `A` in [`Reg`]. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct WO; + +/// Read-write type state for `A` in [`Reg`]. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct RW; + +/// Generic trait for all the peripheral registers. +/// This trait is sealed and cannot be implemented by any external crate. +pub trait Access: sealed::Access + Copy {} +impl Access for RO {} +impl Access for WO {} +impl Access for RW {} + +/// Trait for readable registers. +pub trait Read: Access {} +impl Read for RO {} +impl Read for RW {} + +/// Trait for writable registers. +pub trait Write: Access {} +impl Write for WO {} +impl Write for RW {} + +/// Generic register structure. `T` refers to the data type of the register. +/// Alternatively, `A` corresponds to the access level (e.g., read-only, read-write...). +/// +/// # Note +/// +/// This structure assumes that it points to a valid peripheral register. +/// If so, it is safe to read from or write to the register. +/// However, keep in mind that read-modify-write operations may lead to **wrong** behavior. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[repr(transparent)] +pub struct Reg { + ptr: *mut T, + phantom: core::marker::PhantomData, +} + +unsafe impl Send for Reg {} +unsafe impl Sync for Reg {} + +impl Reg { + /// Creates a new register from a pointer. + /// + /// # Safety + /// + /// The pointer must be valid and must be correctly aligned. + #[inline] + pub const unsafe fn new(ptr: *mut T) -> Self { + Self { + ptr, + phantom: core::marker::PhantomData, + } + } + + /// Returns a pointer to the register. + #[inline] + pub const fn get_ptr(self) -> *mut T { + self.ptr + } +} + +impl Reg { + /// Performs a volatile read of the peripheral register with no side effects. + /// + /// # Note + /// + /// If you want to perform a read-modify-write operation, use [`Reg::modify`] instead. + #[inline] + pub fn read(self) -> T { + // SAFETY: valid address and register is readable + unsafe { self.ptr.read_volatile() } + } +} + +impl Reg { + /// Performs a volatile write of the peripheral register. + /// + /// # Note + /// + /// If you want to perform a read-modify-write operation, use [`Reg::modify`] instead. + #[inline] + pub fn write(self, val: T) { + // SAFETY: valid address and register is writable + unsafe { self.ptr.write_volatile(val) } + } +} + +impl Reg { + /// It modifies the value of the register according to a given function `f`. + /// After writing the new value to the register, it returns the value returned by `f`. + /// + /// # Note + /// + /// It performs a non-atomic read-modify-write operation, which may lead to **wrong** behavior. + #[inline] + pub fn modify(self, f: impl FnOnce(&mut T) -> R) -> R { + let mut val = self.read(); + let res = f(&mut val); + self.write(val); + res + } +} + +/// Macro to provide bit-wise operations to integer number registers. +macro_rules! bitwise_reg { + ($TYPE: ty) => { + impl Reg<$TYPE, A> { + /// Reads the `n`th bit of the register. + #[inline] + pub fn read_bit(self, n: usize) -> bool { + let mask = 1 << n; + let val = self.read(); + val & mask == mask + } + + /// Reads a range of bits of the register specified by the `start` and `end` indexes, both included. + #[inline] + pub fn read_bits(self, start: usize, end: usize) -> $TYPE { + let n_bits = end - start + 1; + let mask = ((1 << n_bits) - 1) << start; + let val = self.read(); + (val & mask) >> start + } + } + + impl Reg<$TYPE, A> { + /// Clears the `n`th bit of the register. + /// + /// # Note + /// + /// It performs a non-atomic read-modify-write operation, which may lead to **wrong** behavior. + #[inline] + pub fn clear_bit(self, n: usize) { + self.modify(|val| *val &= !(1 << n)); + } + + /// Sets the nth bit of the register. + /// + /// # Note + /// + /// It performs a non-atomic read-modify-write operation, which may lead to **wrong** behavior. + #[inline] + pub fn set_bit(self, n: usize) { + self.modify(|val| *val |= 1 << n); + } + + /// Writes a range of bits of the register specified by the `start` and `end` indexes, both included. + #[inline] + pub fn write_bits(self, start: usize, end: usize, val: $TYPE) { + let n_bits = end - start + 1; + let mask = ((1 << n_bits) - 1) << start; + self.modify(|v| *v = (*v & !mask) | ((val << start) & mask)); + } + } + }; +} +bitwise_reg!(u8); +bitwise_reg!(u16); +bitwise_reg!(u32); +bitwise_reg!(u64); +bitwise_reg!(u128); +bitwise_reg!(usize); +bitwise_reg!(i8); +bitwise_reg!(i16); +bitwise_reg!(i32); +bitwise_reg!(i64); +bitwise_reg!(i128); +bitwise_reg!(isize); + +/// Macro to provide atomic bit-wise operations to integer number registers. +macro_rules! bitwise_atomic_reg { + ($TYPE: ty, $ATOMIC: ty) => { + impl Reg<$TYPE, A> { + /// Creates a new atomic reference to the register. + /// + /// # Safety + /// + /// * Register must be properly aligned **for atomic operations**. + /// * The register must not be accessed through non-atomic operations for the whole lifetime `'a`. + pub unsafe fn as_atomic<'a>(&self) -> &'a $ATOMIC { + // SAFETY: guaranteed by the caller + unsafe { &*self.ptr.cast() } + } + + /// Clears the `n`th bit of the register atomically. + /// + /// # Safety + /// + /// * Register must be properly aligned **for atomic operations**. + /// * The register must not be accessed through non-atomic operations until this function returns. + #[inline] + pub unsafe fn atomic_clear_bit(&self, n: usize, order: core::sync::atomic::Ordering) { + // SAFETY: guaranteed by the caller + unsafe { self.as_atomic() }.fetch_and(!(1 << n), order); + } + + /// Sets the `n`th bit of the register atomically. + /// + /// # Safety + /// + /// * Register must be properly aligned **for atomic operations**. + /// * The register must not be accessed through non-atomic operations until this function returns. + #[inline] + pub unsafe fn atomic_set_bit(&self, n: usize, order: core::sync::atomic::Ordering) { + // SAFETY: guaranteed by the caller + unsafe { self.as_atomic() }.fetch_or(1 << n, order); + } + } + }; +} + +#[cfg(target_has_atomic = "8")] +bitwise_atomic_reg!(u8, core::sync::atomic::AtomicU8); +#[cfg(target_has_atomic = "16")] +bitwise_atomic_reg!(u16, core::sync::atomic::AtomicU16); +#[cfg(target_has_atomic = "32")] +bitwise_atomic_reg!(u32, core::sync::atomic::AtomicU32); +#[cfg(target_has_atomic = "64")] +bitwise_atomic_reg!(u64, core::sync::atomic::AtomicU64); +#[cfg(target_has_atomic = "ptr")] +bitwise_atomic_reg!(usize, core::sync::atomic::AtomicUsize); +#[cfg(target_has_atomic = "8")] +bitwise_atomic_reg!(i8, core::sync::atomic::AtomicI8); +#[cfg(target_has_atomic = "16")] +bitwise_atomic_reg!(i16, core::sync::atomic::AtomicI16); +#[cfg(target_has_atomic = "32")] +bitwise_atomic_reg!(i32, core::sync::atomic::AtomicI32); +#[cfg(target_has_atomic = "64")] +bitwise_atomic_reg!(i64, core::sync::atomic::AtomicI64); +#[cfg(target_has_atomic = "ptr")] +bitwise_atomic_reg!(isize, core::sync::atomic::AtomicIsize); + +/// Macro to define the archetypal behavior of registers. +macro_rules! peripheral { + ($REGISTER: ident, $TYPE: ty, $ACCESS: ident) => { + /// Peripheral register. + #[derive(Clone, Copy, Debug, Eq, PartialEq)] + #[repr(transparent)] + pub struct $REGISTER { + register: $crate::common::Reg<$TYPE, $crate::common::$ACCESS>, + } + + impl $REGISTER { + /// Creates a new register from an address. + /// + /// # Safety + /// + /// The address assigned must be valid and must be correctly aligned. + #[inline] + pub const unsafe fn new(address: usize) -> Self { + Self { + register: $crate::common::Reg::new(address as _), + } + } + } + }; + ($REGISTER: ident, $TYPE: ty, $ACCESS: ident, $GENERIC: ident) => { + /// Peripheral register. + #[derive(Clone, Copy, Debug, Eq, PartialEq)] + #[repr(transparent)] + pub struct $REGISTER<$GENERIC> { + register: $crate::common::Reg<$TYPE, $crate::common::$ACCESS>, + _marker: core::marker::PhantomData<$GENERIC>, + } + + impl<$GENERIC> $REGISTER<$GENERIC> { + /// Creates a new register from an address. + /// + /// # Safety + /// + /// The address assigned must be valid and must be correctly aligned. + #[inline] + pub const unsafe fn new(address: usize) -> Self { + Self { + register: $crate::common::Reg::new(address as _), + _marker: core::marker::PhantomData, + } + } + } + }; +} + +/// Macro to define the archetypal behavior of *safe* registers. +/// You must specify the register name, its data type, and its access level. +/// +/// # Note +/// +/// Safe peripheral registers implement [`core::ops::Deref`] to [`Reg`]. +/// You can safely use the dereferenced [`Reg::read`], [`Reg::write`], and/or [`Reg::modify`] methods. +macro_rules! safe_peripheral { + ($REGISTER: ident, $TYPE: ty, $ACCESS: ident) => { + $crate::common::peripheral!($REGISTER, $TYPE, $ACCESS); + + impl $REGISTER { + /// Returns the underlying raw register. + #[inline] + pub const fn get_register(self) -> $crate::common::Reg<$TYPE, $crate::common::$ACCESS> { + self.register + } + } + + impl core::ops::Deref for $REGISTER { + type Target = $crate::common::Reg<$TYPE, $crate::common::$ACCESS>; + + fn deref(&self) -> &Self::Target { + &self.register + } + } + }; + ($REGISTER: ident, $TYPE: ty, $ACCESS: ident, $GENERIC: ident) => { + $crate::common::peripheral!($REGISTER, $TYPE, $ACCESS, $GENERIC); + + impl $REGISTER { + /// Returns the underlying raw register. + #[inline] + pub const fn get_register(self) -> $crate::common::Reg<$TYPE, $crate::common::$ACCESS> { + self.register + } + } + + impl<$GENERIC> core::ops::Deref for $REGISTER<$GENERIC> { + type Target = $crate::common::Reg<$TYPE, $crate::common::$ACCESS>; + + fn deref(&self) -> &Self::Target { + &self.register + } + } + }; +} + +/// Macro to define the archetypal behavior of *unsafe* registers. +/// You must specify the register name, its data type, and its access level. +/// +/// # Note +/// +/// Unsafe peripheral registers need special care when reading and/or writing. +/// They usually provide additional methods to perform safe (or unsafe) operations. +/// Nevertheless, you can still access the underlying register using the `unsafe get_register(self)` method. +macro_rules! unsafe_peripheral { + ($REGISTER: ident, $TYPE: ty, $ACCESS: ident) => { + $crate::common::peripheral!($REGISTER, $TYPE, $ACCESS); + + impl $REGISTER { + /// Returns a raw pointer to the register. + #[inline] + pub const fn get_ptr(self) -> *mut $TYPE { + self.register.get_ptr() + } + + /// Returns the underlying raw register. + /// + /// # Safety + /// + /// This register is not supposed to be used directly. + /// Use the other provided methods instead. Otherwise, use this method at your own risk. + #[inline] + pub const unsafe fn get_register( + self, + ) -> $crate::common::Reg<$TYPE, $crate::common::$ACCESS> { + self.register + } + } + }; + ($REGISTER: ident, $TYPE: ty, $ACCESS: ident, $GENERIC: ident) => { + $crate::common::peripheral!($REGISTER, $TYPE, $ACCESS, $GENERIC); + + impl<$GENERIC> $REGISTER<$GENERIC> { + /// Returns a raw pointer to the register. + #[inline] + pub const fn get_ptr(self) -> *mut $TYPE { + self.register.get_ptr() + } + + /// Returns the underlying register. + /// + /// # Safety + /// + /// This register is not supposed to be used directly. + /// Use the other provided methods instead. Otherwise, use this method at your own risk. + #[inline] + pub const unsafe fn get_register( + self, + ) -> $crate::common::Reg<$TYPE, $crate::common::$ACCESS> { + self.register + } + } + }; +} + +pub(crate) use {peripheral, safe_peripheral, unsafe_peripheral}; + +mod sealed { + use super::*; + pub trait Access {} + impl Access for RO {} + impl Access for WO {} + impl Access for RW {} +} diff --git a/riscv-peripheral/src/hal.rs b/riscv-peripheral/src/hal.rs new file mode 100644 index 00000000..8d16ffe0 --- /dev/null +++ b/riscv-peripheral/src/hal.rs @@ -0,0 +1,5 @@ +//! trait implementations for embedded-hal + +pub use embedded_hal::*; // re-export embedded-hal to allow macros to use it + +pub mod aclint; // ACLINT and CLINT peripherals diff --git a/riscv-peripheral/src/hal/aclint.rs b/riscv-peripheral/src/hal/aclint.rs new file mode 100644 index 00000000..143fe852 --- /dev/null +++ b/riscv-peripheral/src/hal/aclint.rs @@ -0,0 +1,46 @@ +//! Delay trait implementation for (A)CLINT peripherals + +use crate::aclint::mtimer::MTIME; +pub use crate::hal::delay::DelayNs; + +/// Delay implementation for (A)CLINT peripherals. +pub struct Delay { + mtime: MTIME, + freq: usize, +} + +impl Delay { + /// Creates a new `Delay` instance. + #[inline] + pub const fn new(mtime: MTIME, freq: usize) -> Self { + Self { mtime, freq } + } + + /// Returns the frequency of the `MTIME` register. + #[inline] + pub const fn get_freq(&self) -> usize { + self.freq + } + + /// Sets the frequency of the `MTIME` register. + #[inline] + pub fn set_freq(&mut self, freq: usize) { + self.freq = freq; + } + + /// Returns the `MTIME` register. + #[inline] + pub const fn get_mtime(&self) -> MTIME { + self.mtime + } +} + +impl DelayNs for Delay { + #[inline] + fn delay_ns(&mut self, ns: u32) { + let t0 = self.mtime.read(); + let ns_64: u64 = ns.into(); + let n_ticks = ns_64 * self.freq as u64 / 1_000_000_000; + while self.mtime.read().wrapping_sub(t0) < n_ticks {} + } +} diff --git a/riscv-peripheral/src/hal_async.rs b/riscv-peripheral/src/hal_async.rs new file mode 100644 index 00000000..5cd093c8 --- /dev/null +++ b/riscv-peripheral/src/hal_async.rs @@ -0,0 +1,5 @@ +//! async trait implementations for embedded-hal + +pub use embedded_hal_async::*; // re-export embedded-hal-async to allow macros to use it + +pub mod aclint; // ACLINT and CLINT peripherals diff --git a/riscv-peripheral/src/hal_async/aclint.rs b/riscv-peripheral/src/hal_async/aclint.rs new file mode 100644 index 00000000..03af1250 --- /dev/null +++ b/riscv-peripheral/src/hal_async/aclint.rs @@ -0,0 +1,49 @@ +//! Asynchronous delay implementation for the (A)CLINT peripheral. + +use crate::aclint::mtimer::MTIME; +pub use crate::hal::aclint::Delay; +pub use crate::hal_async::delay::DelayUs; +use core::{ + future::Future, + pin::Pin, + task::{Context, Poll}, +}; + +struct DelayAsync { + mtime: MTIME, + t0: u64, + n_ticks: u64, +} + +impl DelayAsync { + pub fn new(mtime: MTIME, n_ticks: u64) -> Self { + let t0 = mtime.read(); + Self { mtime, t0, n_ticks } + } +} + +impl Future for DelayAsync { + type Output = (); + + #[inline] + fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll { + match self.mtime.read().wrapping_sub(self.t0) < self.n_ticks { + true => Poll::Pending, + false => Poll::Ready(()), + } + } +} + +impl DelayUs for Delay { + #[inline] + async fn delay_us(&mut self, us: u32) { + let n_ticks = us as u64 * self.get_freq() as u64 / 1_000_000; + DelayAsync::new(self.get_mtime(), n_ticks).await; + } + + #[inline] + async fn delay_ms(&mut self, ms: u32) { + let n_ticks = ms as u64 * self.get_freq() as u64 / 1_000; + DelayAsync::new(self.get_mtime(), n_ticks).await; + } +} diff --git a/riscv-peripheral/src/lib.rs b/riscv-peripheral/src/lib.rs new file mode 100644 index 00000000..c0ada40f --- /dev/null +++ b/riscv-peripheral/src/lib.rs @@ -0,0 +1,15 @@ +//! Standard RISC-V peripherals for embedded systems written in Rust + +#![deny(missing_docs)] +#![no_std] + +pub use riscv; // re-export riscv crate to allow macros to use it + +pub mod common; // common definitions for all peripherals +pub mod hal; // trait implementations for embedded-hal + // #[cfg(feature = "hal-async")] + // pub mod hal_async; // async trait implementations for embedded-hal +pub mod macros; // macros for easing the definition of peripherals in PACs + +pub mod aclint; // ACLINT and CLINT peripherals +pub mod plic; // PLIC peripheral diff --git a/riscv-peripheral/src/macros.rs b/riscv-peripheral/src/macros.rs new file mode 100644 index 00000000..38571dc2 --- /dev/null +++ b/riscv-peripheral/src/macros.rs @@ -0,0 +1,328 @@ +//! Utility macros for generating standard peripherals-related code in RISC-V PACs. + +/// Macro to create interfaces to CLINT peripherals in PACs. +/// The resulting struct will be named `CLINT`, and will provide safe access to the CLINT registers. +/// +/// This macro expects 4 different argument types: +/// +/// - Base address (**MANDATORY**): base address of the CLINT peripheral of the target. +/// - Frequency (**OPTIONAL**): clock frequency (in Hz) of the `MTIME` register. It enables the `delay` method of the `CLINT` struct. +/// - Per-HART mtimecmp registers (**OPTIONAL**): a list of `mtimecmp` registers for easing access to per-HART mtimecmp regs. +/// - Per-HART msip registers (**OPTIONAL**): a list of `msip` registers for easing access to per-HART msip regs. +/// +/// Check the examples below for more details about the usage and syntax of this macro. +/// +/// # Example +/// +/// ## Base address only +/// +/// ``` +/// use riscv_peripheral::clint_codegen; +/// +/// clint_codegen!(base 0x0200_0000, freq 32_768,); // do not forget the ending comma! +/// +/// let mswi = CLINT::mswi(); // MSWI peripheral +/// let mtimer = CLINT::mtimer(); // MTIMER peripheral +/// let delay = CLINT::delay(); // For the `embedded_hal::delay::DelayUs` and `embedded_hal_async::delay::DelayUs` traits +/// ``` +/// +/// ## Base address and per-HART mtimecmp registers +/// +/// ``` +/// use riscv_peripheral::clint_codegen; +/// +/// /// HART IDs for the target CLINT peripheral +/// #[derive(Clone, Copy, Debug, Eq, PartialEq)] +/// #[repr(u16)] +/// pub enum HartId { H0 = 0, H1 = 1, H2 = 2 } +/// +/// // Implement `HartIdNumber` for `HartId` +/// unsafe impl riscv_peripheral::aclint::HartIdNumber for HartId { +/// const MAX_HART_ID_NUMBER: u16 = 2; +/// fn number(self) -> u16 { self as _ } +/// fn from_number(number: u16) -> Result { +/// if number > Self::MAX_HART_ID_NUMBER { +/// Err(number) +/// } else { +/// // SAFETY: valid context number +/// Ok(unsafe { core::mem::transmute(number) }) +/// } +/// } +/// } +/// +/// clint_codegen!( +/// base 0x0200_0000, +/// mtimecmps [mtimecmp0 = (HartId::H0, "`H0`"), mtimecmp1 = (HartId::H1, "`H1`"), mtimecmp2 = (HartId::H2, "`H2`")], +/// msips [msip0=(HartId::H0,"`H0`"), msip1=(HartId::H1,"`H1`"), msip2=(HartId::H2,"`H2`")], // do not forget the ending comma! +/// ); +/// +/// let mswi = CLINT::mswi(); // MSWI peripheral +/// let mtimer = CLINT::mtimer(); // MTIMER peripheral +/// +/// let mtimecmp0 = CLINT::mtimecmp0(); // mtimecmp register for HART 0 +/// let mtimecmp1 = CLINT::mtimecmp1(); // mtimecmp register for HART 1 +/// let mtimecmp2 = CLINT::mtimecmp2(); // mtimecmp register for HART 2 +/// +/// let msip0 = CLINT::msip0(); // msip register for HART 0 +/// let msip1 = CLINT::msip1(); // msip register for HART 1 +/// let msip2 = CLINT::msip2(); // msip register for HART 2 +/// ``` +#[macro_export] +macro_rules! clint_codegen { + () => { + #[allow(unused_imports)] + use CLINT as _; // assert that the CLINT struct is defined + }; + (base $addr:literal, $($tail:tt)*) => { + /// CLINT peripheral + #[allow(clippy::upper_case_acronyms)] + #[derive(Clone, Copy, Debug, Eq, PartialEq)] + pub struct CLINT; + + unsafe impl $crate::aclint::Clint for CLINT { + const BASE: usize = $addr; + } + + impl CLINT { + /// Returns `true` if a machine timer **OR** software interrupt is pending. + #[inline] + pub fn is_interrupting() -> bool { + Self::mswi_is_interrupting() || Self::mtimer_is_interrupting() + } + + /// Returns `true` if machine timer **OR** software interrupts are enabled. + pub fn is_enabled() -> bool { + Self::mswi_is_enabled() || Self::mtimer_is_enabled() + } + + /// Enables machine timer **AND** software interrupts to allow the CLINT to trigger interrupts. + /// + /// # Safety + /// + /// Enabling the `CLINT` may break mask-based critical sections. + #[inline] + pub unsafe fn enable() { + Self::mswi_enable(); + Self::mtimer_enable(); + } + + /// Disables machine timer **AND** software interrupts to prevent the CLINT from triggering interrupts. + #[inline] + pub fn disable() { + Self::mswi_disable(); + Self::mtimer_disable(); + } + + /// Returns `true` if a machine software interrupt is pending. + #[inline] + pub fn mswi_is_interrupting() -> bool { + $crate::riscv::register::mip::read().msoft() + } + + /// Returns `true` if Machine Software Interrupts are enabled. + #[inline] + pub fn mswi_is_enabled() -> bool { + $crate::riscv::register::mie::read().msoft() + } + + /// Enables the `MSWI` peripheral. + /// + /// # Safety + /// + /// Enabling the `MSWI` may break mask-based critical sections. + #[inline] + pub unsafe fn mswi_enable() { + $crate::riscv::register::mie::set_msoft(); + } + + /// Disables the `MSWI` peripheral. + #[inline] + pub fn mswi_disable() { + // SAFETY: it is safe to disable interrupts + unsafe { $crate::riscv::register::mie::clear_msoft() }; + } + + /// Returns the `MSWI` peripheral. + #[inline] + pub const fn mswi() -> $crate::aclint::mswi::MSWI { + $crate::aclint::CLINT::::mswi() + } + + /// Returns `true` if a machine timer interrupt is pending. + #[inline] + pub fn mtimer_is_interrupting() -> bool { + $crate::riscv::register::mip::read().mtimer() + } + + /// Returns `true` if Machine Timer Interrupts are enabled. + #[inline] + pub fn mtimer_is_enabled() -> bool { + $crate::riscv::register::mie::read().mtimer() + } + + /// Sets the Machine Timer Interrupt bit of the `mie` CSR. + /// This bit must be set for the `MTIMER` to trigger machine timer interrupts. + /// + /// # Safety + /// + /// Enabling the `MTIMER` may break mask-based critical sections. + #[inline] + pub unsafe fn mtimer_enable() { + $crate::riscv::register::mie::set_mtimer(); + } + + /// Clears the Machine Timer Interrupt bit of the `mie` CSR. + /// When cleared, the `MTIMER` cannot trigger machine timer interrupts. + #[inline] + pub fn mtimer_disable() { + // SAFETY: it is safe to disable interrupts + unsafe { $crate::riscv::register::mie::clear_mtimer() }; + } + + /// Returns the `MTIMER` peripheral. + #[inline] + pub const fn mtimer() -> $crate::aclint::mtimer::MTIMER { + $crate::aclint::CLINT::::mtimer() + } + + /// Returns the `MTIME` register of the `MTIMER` peripheral. + #[inline] + pub const fn mtime() -> $crate::aclint::mtimer::MTIME { + Self::mtimer().mtime + } + } + $crate::clint_codegen!($($tail)*); + }; + (freq $freq:literal, $($tail:tt)*) => { + impl CLINT { + /// Returns the frequency of the `MTIME` register. + #[inline] + pub const fn freq() -> usize { + $freq + } + + /// Delay implementation for CLINT peripherals. + /// + /// # Note + /// + /// You must export the `riscv_peripheral::hal::delay::DelayUs` trait in order to use delay methods. + /// You must export the `riscv_peripheral::hal_async::delay::DelayUs` trait in order to use async delay methods. + #[inline] + pub const fn delay() -> $crate::hal::aclint::Delay { + $crate::hal::aclint::Delay::new(Self::mtime(), Self::freq()) + } + } + $crate::clint_codegen!($($tail)*); + }; + (msips [$($fn:ident = ($hart:expr , $shart:expr)),+], $($tail:tt)*) => { + impl CLINT { + $( + #[doc = "Returns the `msip` register for HART "] + #[doc = $shart] + #[doc = "."] + #[inline] + pub fn $fn() -> $crate::aclint::mswi::MSIP { + Self::mswi().msip($hart) + } + )* + } + $crate::clint_codegen!($($tail)*); + }; + (mtimecmps [$($fn:ident = ($hart:expr , $shart:expr)),+], $($tail:tt)*) => { + impl CLINT { + $( + #[doc = "Returns the `mtimecmp` register for HART "] + #[doc = $shart] + #[doc = "."] + #[inline] + pub fn $fn() -> $crate::aclint::mtimer::MTIMECMP { + Self::mtimer().mtimecmp($hart) + } + )* + } + $crate::clint_codegen!($($tail)*); + }; +} + +/// Macro to create interfaces to PLIC peripherals in PACs. +#[macro_export] +macro_rules! plic_codegen { + () => { + #[allow(unused_imports)] + use PLIC as _; // assert that the PLIC struct is defined + }; + (base $addr:literal, $($tail:tt)*) => { + /// PLIC peripheral + #[allow(clippy::upper_case_acronyms)] + #[derive(Clone, Copy, Debug, Eq, PartialEq)] + pub struct PLIC; + + unsafe impl $crate::plic::Plic for PLIC { + const BASE: usize = $addr; + } + + impl PLIC { + /// Returns `true` if a machine external interrupt is pending. + #[inline] + pub fn is_interrupting() -> bool { + $crate::riscv::register::mip::read().mext() + } + + /// Returns true if Machine External Interrupts are enabled. + #[inline] + pub fn is_enabled() -> bool { + $crate::riscv::register::mie::read().mext() + } + + /// Enables machine external interrupts to allow the PLIC to trigger interrupts. + /// + /// # Safety + /// + /// Enabling the `PLIC` may break mask-based critical sections. + #[inline] + pub unsafe fn enable() { + $crate::riscv::register::mie::set_mext(); + } + + /// Disables machine external interrupts to prevent the PLIC from triggering interrupts. + #[inline] + pub fn disable() { + // SAFETY: it is safe to disable interrupts + unsafe { $crate::riscv::register::mie::clear_mext() }; + } + + /// Returns the priorities register of the PLIC. + #[inline] + pub fn priorities() -> $crate::plic::priorities::PRIORITIES { + $crate::plic::PLIC::::priorities() + } + + /// Returns the pendings register of the PLIC. + #[inline] + pub fn pendings() -> $crate::plic::pendings::PENDINGS { + $crate::plic::PLIC::::pendings() + } + + /// Returns the context proxy of a given PLIC context. + #[inline] + pub fn ctx(context: C) -> $crate::plic::CTX { + $crate::plic::PLIC::::ctx(context) + } + } + $crate::plic_codegen!($($tail)*); + }; + (ctxs [$($fn:ident = ($ctx:expr , $sctx:expr)),+], $($tail:tt)*) => { + impl PLIC { + $( + #[doc = "Returns a PLIC context proxy for context "] + #[doc = $sctx] + #[doc = "."] + #[inline] + pub fn $fn() -> $crate::plic::CTX { + Self::ctx($ctx) + } + )* + } + $crate::plic_codegen!($($tail)*); + }; +} diff --git a/riscv-peripheral/src/plic.rs b/riscv-peripheral/src/plic.rs new file mode 100644 index 00000000..3185a77e --- /dev/null +++ b/riscv-peripheral/src/plic.rs @@ -0,0 +1,383 @@ +//! Platform-Level Interrupt Controller (PLIC) peripheral. +//! +//! Specification: + +pub mod claim; +pub mod enables; +pub mod pendings; +pub mod priorities; +pub mod threshold; + +/// Trait for enums of interrupt numbers. +/// +/// This trait should be implemented by a peripheral access crate (PAC) +/// on its enum of available external interrupts for a specific device. +/// Each variant must convert to a `u16` of its interrupt number. +/// +/// # Note +/// +/// Recall that the interrupt number `0` is reserved as "no interrupt". +/// +/// # Safety +/// +/// * This trait must only be implemented on a PAC of a target with a PLIC peripheral. +/// * This trait must only be implemented on enums of external interrupts. +/// * Each enum variant must represent a distinct value (no duplicates are permitted), +/// * Each enum variant must always return the same value (do not change at runtime). +/// * All the interrupt numbers must be less than or equal to `MAX_INTERRUPT_NUMBER`. +/// * `MAX_INTERRUPT_NUMBER` must coincide with the highest allowed interrupt number. +pub unsafe trait InterruptNumber: Copy { + /// Highest number assigned to an interrupt source. + const MAX_INTERRUPT_NUMBER: u16; + + /// Converts an interrupt source to its corresponding number. + fn number(self) -> u16; + + /// Tries to convert a number to a valid interrupt source. + /// If the conversion fails, it returns an error with the number back. + fn from_number(value: u16) -> Result; +} + +/// Trait for enums of priority levels. +/// +/// This trait should be implemented by a peripheral access crate (PAC) +/// on its enum of available priority numbers for a specific device. +/// Each variant must convert to a `u8` of its priority level. +/// +/// # Note +/// +/// Recall that the priority number `0` is reserved as "never interrupt". +/// +/// # Safety +/// +/// * This trait must only be implemented on a PAC of a target with a PLIC peripheral. +/// * This trait must only be implemented on enums of priority levels. +/// * Each enum variant must represent a distinct value (no duplicates are permitted). +/// * Each enum variant must always return the same value (do not change at runtime). +/// * There must be a valid priority number set to 0 (i.e., never interrupt). +/// * All the priority level numbers must be less than or equal to `MAX_PRIORITY_NUMBER`. +/// * `MAX_PRIORITY_NUMBER` must coincide with the highest allowed priority number. +pub unsafe trait PriorityNumber: Copy { + /// Number assigned to the highest priority level. + const MAX_PRIORITY_NUMBER: u8; + + /// Converts a priority level to its corresponding number. + fn number(self) -> u8; + + /// Tries to convert a number to a valid priority level. + /// If the conversion fails, it returns an error with the number back. + fn from_number(value: u8) -> Result; +} + +/// Trait for enums of PLIC contexts. +/// +/// This trait should be implemented by a peripheral access crate (PAC) +/// on its enum of available contexts for a specific device. +/// Each variant must convert to a `u16` of its context number. +/// +/// # Safety +/// +/// * This trait must only be implemented on a PAC of a target with a PLIC peripheral. +/// * This trait must only be implemented on enums of contexts. +/// * Each enum variant must represent a distinct value (no duplicates are permitted), +/// * Each anum variant must always return the same value (do not change at runtime). +/// * All the context numbers must be less than or equal to `MAX_CONTEXT_NUMBER`. +/// * `MAX_CONTEXT_NUMBER` must coincide with the highest allowed context number. +pub unsafe trait ContextNumber: Copy { + /// Highest number assigned to a context. + const MAX_CONTEXT_NUMBER: u16; + + /// Converts an context to its corresponding number. + fn number(self) -> u16; + + /// Tries to convert a number to a valid context. + /// If the conversion fails, it returns an error with the number back. + fn from_number(value: u16) -> Result; +} + +/// Trait for a PLIC peripheral. +/// +/// # Safety +/// +/// * This trait must only be implemented on a PAC of a target with a PLIC peripheral. +/// * The PLIC peripheral base address `BASE` must be valid for the target device. +pub unsafe trait Plic: Copy { + /// Base address of the PLIC peripheral. + const BASE: usize; +} + +/// Platform-Level Interrupt Controler (PLIC) peripheral. +/// +/// The RISC-V standard does not specify a fixed location for the PLIC. +/// Thus, each platform must specify the base address of the PLIC on the platform. +/// The base address, as well as all the associated types, are defined in the [`Plic`] trait. +/// +/// The PLIC standard allows up to 15_872 different contexts for interfacing the PLIC. +/// Each context has an assigned index starting from 0 to up to 15_871. +/// Usually, each HART uses a dedicated context. In this way, they do not interfere +/// with each other when attending to external interruptions. +#[allow(clippy::upper_case_acronyms)] +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] +pub struct PLIC { + _marker: core::marker::PhantomData

, +} + +impl PLIC

{ + const PRIORITIES_OFFSET: usize = 0; + + const PENDINGS_OFFSET: usize = 0x1000; + + /// Returns the priorities register of the PLIC. + /// This register allows to set the priority level of each interrupt source. + /// The priority level of each interrupt source is shared among all the contexts. + #[inline] + pub fn priorities() -> priorities::PRIORITIES { + // SAFETY: valid address + unsafe { priorities::PRIORITIES::new(P::BASE + Self::PRIORITIES_OFFSET) } + } + + /// Returns the pendings register of the PLIC. + /// This register allows to check if a particular interrupt source is pending. + #[inline] + pub fn pendings() -> pendings::PENDINGS { + // SAFETY: valid address + unsafe { pendings::PENDINGS::new(P::BASE + Self::PENDINGS_OFFSET) } + } + + /// Returns a proxy to access to all the PLIC registers of a given context. + #[inline] + pub fn ctx(context: C) -> CTX

{ + // SAFETY: valid context number + unsafe { CTX::new(context.number()) } + } +} + +/// PLIC context proxy. It provides access to the PLIC registers of a given context. +#[allow(clippy::upper_case_acronyms)] +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] +pub struct CTX { + context: usize, + _marker: core::marker::PhantomData

, +} + +impl CTX

{ + const ENABLES_OFFSET: usize = 0x2000; + const ENABLES_SEPARATION: usize = 0x80; + + const THRESHOLDS_OFFSET: usize = 0x20_0000; + const THRESHOLDS_SEPARATION: usize = 0x1000; + + const CLAIMS_OFFSET: usize = 0x20_0004; + const CLAIMS_SEPARATION: usize = 0x1000; + + /// Creates a new PLIC context proxy + /// + /// # Safety + /// + /// The context number must be valid for the target device. + #[inline] + pub(crate) unsafe fn new(context: u16) -> Self { + Self { + context: context as _, + _marker: core::marker::PhantomData, + } + } + + /// Returns the context number of this proxy. + #[inline] + pub const fn context(self) -> u16 { + self.context as _ + } + + /// Returns the interrupts enable register of the context. + #[inline] + pub const fn enables(self) -> enables::ENABLES { + let addr = P::BASE + Self::ENABLES_OFFSET + self.context * Self::ENABLES_SEPARATION; + // SAFETY: valid address + unsafe { enables::ENABLES::new(addr) } + } + + /// Returns the interrupt threshold register of the context. + #[inline] + pub const fn threshold(self) -> threshold::THRESHOLD { + let addr = P::BASE + Self::THRESHOLDS_OFFSET + self.context * Self::THRESHOLDS_SEPARATION; + // SAFETY: valid address + unsafe { threshold::THRESHOLD::new(addr) } + } + + /// Returns the interrupt claim/complete register of the context. + #[inline] + pub const fn claim(self) -> claim::CLAIM { + let addr = P::BASE + Self::CLAIMS_OFFSET + self.context * Self::CLAIMS_SEPARATION; + // SAFETY: valid address + unsafe { claim::CLAIM::new(addr) } + } +} + +#[cfg(test)] +pub(crate) mod test { + use super::{ContextNumber, InterruptNumber, PriorityNumber}; + + #[derive(Clone, Copy, Debug, Eq, PartialEq)] + #[repr(u16)] + pub(crate) enum Interrupt { + I1 = 1, + I2 = 2, + I3 = 3, + I4 = 4, + } + + #[derive(Clone, Copy, Debug, Eq, PartialEq)] + #[repr(u8)] + pub(crate) enum Priority { + P0 = 0, + P1 = 1, + P2 = 2, + P3 = 3, + } + + #[derive(Clone, Copy, Debug, Eq, PartialEq)] + #[repr(u16)] + pub(crate) enum Context { + C0 = 0, + C1 = 1, + C2 = 2, + } + + unsafe impl InterruptNumber for Interrupt { + const MAX_INTERRUPT_NUMBER: u16 = 4; + + #[inline] + fn number(self) -> u16 { + self as _ + } + + #[inline] + fn from_number(number: u16) -> Result { + if number > Self::MAX_INTERRUPT_NUMBER || number == 0 { + Err(number) + } else { + // SAFETY: valid interrupt number + Ok(unsafe { core::mem::transmute(number) }) + } + } + } + + unsafe impl PriorityNumber for Priority { + const MAX_PRIORITY_NUMBER: u8 = 3; + + #[inline] + fn number(self) -> u8 { + self as _ + } + + #[inline] + fn from_number(number: u8) -> Result { + if number > Self::MAX_PRIORITY_NUMBER { + Err(number) + } else { + // SAFETY: valid priority number + Ok(unsafe { core::mem::transmute(number) }) + } + } + } + + unsafe impl ContextNumber for Context { + const MAX_CONTEXT_NUMBER: u16 = 2; + + #[inline] + fn number(self) -> u16 { + self as _ + } + + #[inline] + fn from_number(number: u16) -> Result { + if number > Self::MAX_CONTEXT_NUMBER { + Err(number) + } else { + // SAFETY: valid context number + Ok(unsafe { core::mem::transmute(number) }) + } + } + } + + #[test] + fn check_interrupt_enum() { + assert_eq!(Interrupt::I1.number(), 1); + assert_eq!(Interrupt::I2.number(), 2); + assert_eq!(Interrupt::I3.number(), 3); + assert_eq!(Interrupt::I4.number(), 4); + + assert_eq!(Interrupt::from_number(1), Ok(Interrupt::I1)); + assert_eq!(Interrupt::from_number(2), Ok(Interrupt::I2)); + assert_eq!(Interrupt::from_number(3), Ok(Interrupt::I3)); + assert_eq!(Interrupt::from_number(4), Ok(Interrupt::I4)); + + assert_eq!(Interrupt::from_number(0), Err(0)); + assert_eq!(Interrupt::from_number(5), Err(5)); + } + + #[test] + fn check_priority_enum() { + assert_eq!(Priority::P0.number(), 0); + assert_eq!(Priority::P1.number(), 1); + assert_eq!(Priority::P2.number(), 2); + assert_eq!(Priority::P3.number(), 3); + + assert_eq!(Priority::from_number(0), Ok(Priority::P0)); + assert_eq!(Priority::from_number(1), Ok(Priority::P1)); + assert_eq!(Priority::from_number(2), Ok(Priority::P2)); + assert_eq!(Priority::from_number(3), Ok(Priority::P3)); + + assert_eq!(Priority::from_number(4), Err(4)); + } + + #[test] + fn check_context_enum() { + assert_eq!(Context::C0.number(), 0); + assert_eq!(Context::C1.number(), 1); + assert_eq!(Context::C2.number(), 2); + + assert_eq!(Context::from_number(0), Ok(Context::C0)); + assert_eq!(Context::from_number(1), Ok(Context::C1)); + assert_eq!(Context::from_number(2), Ok(Context::C2)); + + assert_eq!(Context::from_number(3), Err(3)); + } + + #[allow(dead_code)] + #[test] + fn check_plic() { + crate::plic_codegen!( + base 0x0C00_0000, + ctxs [ctx0 = (Context::C0, "`C0`"), ctx1 = (Context::C1, "`C1`"), ctx2 = (Context::C2, "`C2`")], + ); + + let priorities = PLIC::priorities(); + let pendings = PLIC::pendings(); + + assert_eq!(priorities.address(), 0x0C00_0000); + assert_eq!(pendings.address(), 0x0C00_1000); + + for i in 0..=Context::MAX_CONTEXT_NUMBER { + let context = Context::from_number(i).unwrap(); + let i = i as usize; + + let ctx = PLIC::ctx(context); + + assert_eq!(ctx.enables().address(), 0x0C00_0000 + 0x2000 + i * 0x80); + assert_eq!( + ctx.threshold().get_ptr() as usize, + 0x0C00_0000 + 0x20_0000 + i * 0x1000 + ); + assert_eq!( + ctx.claim().get_ptr() as usize, + 0x0C00_0000 + 0x20_0004 + i * 0x1000 + ); + } + + assert_eq!(PLIC::ctx0(), PLIC::ctx(Context::C0)); + assert_eq!(PLIC::ctx1(), PLIC::ctx(Context::C1)); + assert_eq!(PLIC::ctx2(), PLIC::ctx(Context::C2)); + } +} diff --git a/riscv-peripheral/src/plic/claim.rs b/riscv-peripheral/src/plic/claim.rs new file mode 100644 index 00000000..dbf5b378 --- /dev/null +++ b/riscv-peripheral/src/plic/claim.rs @@ -0,0 +1,49 @@ +//! Interrupt claim/complete register + +use crate::{common::unsafe_peripheral, plic::InterruptNumber}; + +unsafe_peripheral!(CLAIM, u32, RW); + +impl CLAIM { + /// Claims the number of a pending interrupt for for the PLIC context. + /// If no interrupt is pending for this context, it returns [`None`]. + #[inline] + pub fn claim(self) -> Option { + match self.register.read() { + 0 => None, + i => Some(I::from_number(i as _).unwrap()), + } + } + + /// Marks a pending interrupt as complete for the PLIC context. + /// + /// # Note + /// + /// If the source ID does not match an interrupt source that is + /// currently enabled for the target, the completion is silently ignored. + #[inline] + pub fn complete(self, source: I) { + self.register.write(source.number() as _) + } +} + +#[cfg(test)] +mod test { + use super::super::test::Interrupt; + use super::*; + + #[test] + fn test_claim() { + let mut raw_reg = 0u32; + // SAFETY: valid memory address + let claim = unsafe { CLAIM::new(&mut raw_reg as *mut _ as _) }; + + assert_eq!(claim.claim::(), None); + + for i in 1..=Interrupt::MAX_INTERRUPT_NUMBER { + let interrupt = Interrupt::from_number(i).unwrap(); + claim.complete(interrupt); + assert_eq!(claim.claim(), Some(interrupt)); + } + } +} diff --git a/riscv-peripheral/src/plic/enables.rs b/riscv-peripheral/src/plic/enables.rs new file mode 100644 index 00000000..03ab9a35 --- /dev/null +++ b/riscv-peripheral/src/plic/enables.rs @@ -0,0 +1,238 @@ +//! Interrupt enables register of a PLIC context. + +use crate::{ + common::{Reg, RW}, + plic::InterruptNumber, +}; + +/// Enables register of a PLIC context. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[repr(transparent)] +pub struct ENABLES { + ptr: *mut u32, +} + +impl ENABLES { + /// Creates a new Interrupts enables register from a base address. + /// + /// # Safety + /// + /// The base address must point to a valid Interrupts enables register. + #[inline] + pub(crate) const unsafe fn new(address: usize) -> Self { + Self { ptr: address as _ } + } + + #[cfg(test)] + #[inline] + pub(crate) fn address(self) -> usize { + self.ptr as _ + } + + /// Checks if an interrupt source is enabled for the PLIC context. + #[inline] + pub fn is_enabled(self, source: I) -> bool { + let source = source.number() as usize; + let offset = (source / u32::BITS as usize) as _; + // SAFETY: valid interrupt number + let reg: Reg = unsafe { Reg::new(self.ptr.offset(offset)) }; + reg.read_bit(source % u32::BITS as usize) + } + + /// Enables an interrupt source for the PLIC context. + /// + /// # Note + /// + /// It performs non-atomic read-modify-write operations, which may lead to **wrong** behavior. + /// + /// # Safety + /// + /// * Enabling an interrupt source can break mask-based critical sections. + #[inline] + pub unsafe fn enable(self, source: I) { + let source = source.number() as usize; + let offset = (source / u32::BITS as usize) as _; + // SAFETY: valid interrupt number + let reg: Reg = unsafe { Reg::new(self.ptr.offset(offset)) }; + reg.set_bit(source % u32::BITS as usize); + } + + #[cfg(target_has_atomic = "32")] + /// Enables an interrupt source for the PLIC context atomically. + /// + /// # Note + /// + /// This method is only available on targets that support atomic operations on 32-bit registers. + /// + /// # Safety + /// + /// * Enabling an interrupt source can break mask-based critical sections. + /// * Register must be properly aligned **for atomic operations**. + /// * The register must not be accessed through non-atomic operations until this function returns. + #[inline] + pub unsafe fn atomic_enable( + self, + source: I, + order: core::sync::atomic::Ordering, + ) { + let source = source.number() as usize; + let offset = (source / u32::BITS as usize) as _; + // SAFETY: valid interrupt number + let reg: Reg = unsafe { Reg::new(self.ptr.offset(offset)) }; + reg.atomic_set_bit(source % u32::BITS as usize, order); + } + + /// Disables an interrupt source for the PLIC context. + /// + /// # Note + /// + /// It performs non-atomic read-modify-write operations, which may lead to **wrong** behavior. + #[inline] + pub fn disable(self, source: I) { + let source = source.number() as usize; + let offset = (source / u32::BITS as usize) as _; + // SAFETY: valid interrupt number + let reg: Reg = unsafe { Reg::new(self.ptr.offset(offset)) }; + reg.clear_bit(source % u32::BITS as usize); + } + + #[cfg(target_has_atomic = "32")] + /// Disables an interrupt source for the PLIC context atomically. + /// + /// # Note + /// + /// This method is only available on targets that support atomic operations on 32-bit registers. + /// + /// # Safety + /// + /// * Register must be properly aligned **for atomic operations**. + /// * The register must not be accessed through non-atomic operations until this function returns. + #[inline] + pub unsafe fn atomic_disable( + self, + source: I, + order: core::sync::atomic::Ordering, + ) { + let source = source.number() as usize; + let offset = (source / u32::BITS as usize) as _; + // SAFETY: valid interrupt number + let reg: Reg = unsafe { Reg::new(self.ptr.offset(offset)) }; + reg.atomic_clear_bit(source % u32::BITS as usize, order); + } + + /// Enables all the external interrupt sources for the PLIC context. + /// + /// # Safety + /// + ///* Enabling all interrupt sources can break mask-based critical sections. + #[inline] + pub unsafe fn enable_all(self) { + for offset in 0..=(I::MAX_INTERRUPT_NUMBER as u32 / u32::BITS) as isize { + // SAFETY: valid offset + let reg: Reg = unsafe { Reg::new(self.ptr.offset(offset)) }; + reg.write(0xFFFF_FFFF); + } + } + + /// Disables all the external interrupt sources for the PLIC context. + #[inline] + pub fn disable_all(self) { + for offset in 0..=(I::MAX_INTERRUPT_NUMBER as u32 / u32::BITS) as _ { + // SAFETY: valid offset + let reg: Reg = unsafe { Reg::new(self.ptr.offset(offset)) }; + reg.write(0); + } + } +} + +#[cfg(test)] +mod test { + use super::super::test::Interrupt; + use super::*; + + #[test] + fn test_enables() { + // slice to emulate the interrupt enables register + let mut raw_reg = [0u32; 32]; + // SAFETY: valid memory address + let enables = unsafe { ENABLES::new(raw_reg.as_mut_ptr() as _) }; + + for i in 0..255 { + if i & 0x2 != 0 { + unsafe { enables.enable(Interrupt::I1) }; + } else { + enables.disable(Interrupt::I1); + } + if i & 0x4 != 0 { + unsafe { enables.enable(Interrupt::I2) }; + } else { + enables.disable(Interrupt::I2); + } + if i & 0x8 != 0 { + unsafe { enables.enable(Interrupt::I3) }; + } else { + enables.disable(Interrupt::I3); + } + if i & 0x10 != 0 { + unsafe { enables.enable(Interrupt::I4) }; + } else { + enables.disable(Interrupt::I4); + } + + assert_eq!(enables.is_enabled(Interrupt::I1), i & 0x2 != 0); + assert_eq!(enables.is_enabled(Interrupt::I2), i & 0x4 != 0); + assert_eq!(enables.is_enabled(Interrupt::I3), i & 0x8 != 0); + assert_eq!(enables.is_enabled(Interrupt::I4), i & 0x10 != 0); + + enables.disable_all::(); + assert!(!enables.is_enabled(Interrupt::I1)); + assert!(!enables.is_enabled(Interrupt::I2)); + assert!(!enables.is_enabled(Interrupt::I3)); + assert!(!enables.is_enabled(Interrupt::I4)); + + unsafe { enables.enable_all::() }; + assert!(enables.is_enabled(Interrupt::I1)); + assert!(enables.is_enabled(Interrupt::I2)); + assert!(enables.is_enabled(Interrupt::I3)); + assert!(enables.is_enabled(Interrupt::I4)); + } + } + + #[cfg(target_has_atomic = "32")] + #[test] + fn test_atomic_enables() { + // slice to emulate the interrupt enables register + use core::sync::atomic::Ordering; + let mut raw_reg = [0u32; 32]; + // SAFETY: valid memory address + let enables = unsafe { ENABLES::new(raw_reg.as_mut_ptr() as _) }; + + for i in 0..255 { + if i & 0x2 != 0 { + unsafe { enables.atomic_enable(Interrupt::I1, Ordering::Relaxed) }; + } else { + unsafe { enables.atomic_disable(Interrupt::I1, Ordering::Relaxed) }; + } + if i & 0x4 != 0 { + unsafe { enables.atomic_enable(Interrupt::I2, Ordering::Relaxed) }; + } else { + unsafe { enables.atomic_disable(Interrupt::I2, Ordering::Relaxed) }; + } + if i & 0x8 != 0 { + unsafe { enables.atomic_enable(Interrupt::I3, Ordering::Relaxed) }; + } else { + unsafe { enables.atomic_disable(Interrupt::I3, Ordering::Relaxed) }; + } + if i & 0x10 != 0 { + unsafe { enables.atomic_enable(Interrupt::I4, Ordering::Relaxed) }; + } else { + unsafe { enables.atomic_disable(Interrupt::I4, Ordering::Relaxed) }; + } + + assert_eq!(enables.is_enabled(Interrupt::I1), i & 0x2 != 0); + assert_eq!(enables.is_enabled(Interrupt::I2), i & 0x4 != 0); + assert_eq!(enables.is_enabled(Interrupt::I3), i & 0x8 != 0); + assert_eq!(enables.is_enabled(Interrupt::I4), i & 0x10 != 0); + } + } +} diff --git a/riscv-peripheral/src/plic/pendings.rs b/riscv-peripheral/src/plic/pendings.rs new file mode 100644 index 00000000..8185d9b8 --- /dev/null +++ b/riscv-peripheral/src/plic/pendings.rs @@ -0,0 +1,64 @@ +//! Interrupt pending bits register. + +use crate::{ + common::{Reg, RO}, + plic::InterruptNumber, +}; + +/// Interrupts pending bits register. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[repr(transparent)] +pub struct PENDINGS { + ptr: *mut u32, +} + +impl PENDINGS { + /// Creates a new Interrupts pending bits register from a base address. + /// + /// # Safety + /// + /// The base address must point to a valid Interrupts pending bits register. + #[inline] + pub(crate) const unsafe fn new(address: usize) -> Self { + Self { ptr: address as _ } + } + + #[cfg(test)] + #[inline] + pub(crate) fn address(self) -> usize { + self.ptr as _ + } + + /// Checks if an interrupt triggered by a given source is pending. + #[inline] + pub fn is_pending(self, source: I) -> bool { + let source = source.number() as usize; + let offset = (source / u32::BITS as usize) as _; + // SAFETY: valid interrupt number + let reg: Reg = unsafe { Reg::new(self.ptr.offset(offset)) }; + reg.read_bit(source % u32::BITS as usize) + } +} + +#[cfg(test)] +mod test { + use super::super::test::Interrupt; + use super::*; + + #[test] + fn test_pendings() { + // slice to emulate the interrupt pendings register + let mut raw_reg = [0u32; 32]; + // SAFETY: valid memory address + let pendings = unsafe { PENDINGS::new(raw_reg.as_mut_ptr() as _) }; + + for i in 0..255 { + // SAFETY: valid memory address + unsafe { raw_reg.as_mut_ptr().write_volatile(i) }; + assert_eq!(pendings.is_pending(Interrupt::I1), i & 0x2 != 0); + assert_eq!(pendings.is_pending(Interrupt::I2), i & 0x4 != 0); + assert_eq!(pendings.is_pending(Interrupt::I3), i & 0x8 != 0); + assert_eq!(pendings.is_pending(Interrupt::I4), i & 0x10 != 0); + } + } +} diff --git a/riscv-peripheral/src/plic/priorities.rs b/riscv-peripheral/src/plic/priorities.rs new file mode 100644 index 00000000..a9726838 --- /dev/null +++ b/riscv-peripheral/src/plic/priorities.rs @@ -0,0 +1,98 @@ +//! Interrupts Priorities register. + +use crate::{ + common::{Reg, RW}, + plic::{InterruptNumber, PriorityNumber}, +}; + +/// Interrupts priorities register. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[repr(transparent)] +pub struct PRIORITIES { + ptr: *mut u32, +} + +impl PRIORITIES { + /// Creates a new Interrupts priorities register from a base address. + /// + /// # Safety + /// + /// The base address must point to a valid Interrupts priorities register. + #[inline] + pub(crate) const unsafe fn new(address: usize) -> Self { + Self { ptr: address as _ } + } + + #[cfg(test)] + #[inline] + pub(crate) fn address(self) -> usize { + self.ptr as _ + } + + /// Returns the priority assigned to a given interrupt source. + #[inline] + pub fn get_priority(self, source: I) -> P { + // SAFETY: valid interrupt number + let reg: Reg = unsafe { Reg::new(self.ptr.offset(source.number() as _)) }; + P::from_number(reg.read() as _).unwrap() + } + + /// Sets the priority level of a given interrupt source. + /// + /// # Safety + /// + /// Changing the priority level can break priority-based critical sections. + #[inline] + pub unsafe fn set_priority( + self, + source: I, + priority: P, + ) { + // SAFETY: valid interrupt number + let reg: Reg = unsafe { Reg::new(self.ptr.offset(source.number() as _)) }; + reg.write(priority.number() as _); + } + + /// Resets all the priority levels of all the external interrupt sources to 0. + /// + /// # Note + /// + /// Priority level 0 is reserved for "no interrupt". + /// Thus, this method effectively disables the all the external interrupts. + #[inline] + pub fn reset(self) { + for source in 0..=I::MAX_INTERRUPT_NUMBER as _ { + // SAFETY: interrupt number within range + let reg: Reg = unsafe { Reg::new(self.ptr.offset(source)) }; + reg.write(0); + } + } +} + +#[cfg(test)] +mod test { + use super::super::test::{Interrupt, Priority}; + use super::*; + + #[test] + fn test_priorities() { + // slice to emulate the interrupt priorities register + let mut raw_reg = [0u32; 1024]; + // SAFETY: valid memory address + let priorities = unsafe { PRIORITIES::new(raw_reg.as_mut_ptr() as _) }; + + for i in 1..=Interrupt::MAX_INTERRUPT_NUMBER { + let source = Interrupt::from_number(i).unwrap(); + for j in 0..=Priority::MAX_PRIORITY_NUMBER { + let priority = Priority::from_number(j).unwrap(); + unsafe { priorities.set_priority(source, priority) }; + assert_eq!(priorities.get_priority::<_, Priority>(source), priority); + } + } + priorities.reset::(); + for i in 1..=Interrupt::MAX_INTERRUPT_NUMBER { + let source = Interrupt::from_number(i).unwrap(); + assert_eq!(priorities.get_priority::<_, Priority>(source), Priority::P0); + } + } +} diff --git a/riscv-peripheral/src/plic/threshold.rs b/riscv-peripheral/src/plic/threshold.rs new file mode 100644 index 00000000..936a13b1 --- /dev/null +++ b/riscv-peripheral/src/plic/threshold.rs @@ -0,0 +1,55 @@ +//! Priority threshold register. + +use crate::{common::unsafe_peripheral, plic::PriorityNumber}; + +unsafe_peripheral!(THRESHOLD, u32, RW); + +impl THRESHOLD { + /// Returns the priority threshold level. + #[inline] + pub fn get_threshold(self) -> P { + P::from_number(self.register.read() as _).unwrap() + } + + /// Sets the priority threshold level. + /// + /// # Safety + /// + /// Changing the priority threshold can break priority-based critical sections. + #[inline] + pub unsafe fn set_threshold(self, threshold: P) { + self.register.write(threshold.number() as _) + } + + /// Resets the priority threshold level to 0. + /// + /// # Note + /// + /// Threshold 0 implies that all interrupts are accepted. + /// Thus, resetting the threshold is equivalent to accepting interrupts from any enabled interrupt source. + #[inline] + pub fn reset(self) { + self.register.write(0) + } +} + +#[cfg(test)] +mod test { + use super::super::test::Priority; + use super::*; + + #[test] + fn test_threshold() { + let mut raw_reg = 0u32; + // SAFETY: valid memory address + let threshold = unsafe { THRESHOLD::new(&mut raw_reg as *mut _ as _) }; + + for i in 0..=Priority::MAX_PRIORITY_NUMBER { + let priority = Priority::from_number(i).unwrap(); + unsafe { threshold.set_threshold(priority) }; + assert_eq!(threshold.get_threshold::(), priority); + } + threshold.reset(); + assert_eq!(threshold.get_threshold::(), Priority::P0); + } +}