Explorar o código

First version of PLIC and (A)CLINT

Román Cárdenas hai 1 ano
pai
achega
197f2fcd71
Modificáronse 16 ficheiros con 1566 adicións e 2 borrados
  1. 3 0
      .gitignore
  2. 16 0
      Cargo.toml
  3. 8 2
      README.md
  4. 83 0
      src/aclint.rs
  5. 77 0
      src/aclint/mswi.rs
  6. 63 0
      src/aclint/mtimer.rs
  7. 77 0
      src/aclint/sswi.rs
  8. 407 0
      src/common.rs
  9. 9 0
      src/lib.rs
  10. 1 0
      src/macros.rs
  11. 338 0
      src/plic.rs
  12. 49 0
      src/plic/claim.rs
  13. 232 0
      src/plic/enables.rs
  14. 57 0
      src/plic/pendings.rs
  15. 91 0
      src/plic/priorities.rs
  16. 55 0
      src/plic/threshold.rs

+ 3 - 0
.gitignore

@@ -12,3 +12,6 @@ Cargo.lock
 
 # MSVC Windows builds of rustc generate these, which store debugging information
 *.pdb
+
+.DS_Store
+.vscode/

+ 16 - 0
Cargo.toml

@@ -0,0 +1,16 @@
+[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]
+riscv = { git = "https://github.com/rust-embedded/riscv", branch = "master" }
+
+[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",
+]

+ 8 - 2
README.md

@@ -1,2 +1,8 @@
-# riscv-peripheral
-Standard RISC-V targets for embedded systems written in Rust
+# `riscv-peripheral`
+
+> Standard RISC-V targets 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.

+ 83 - 0
src/aclint.rs

@@ -0,0 +1,83 @@
+//! Devices for the Core Local Interruptor (CLINT) and Advanced CLINT (ACLINT) peripherals.
+//!
+//! CLINT pecification: <https://github.com/pulp-platform/clint>
+//! ACLINT Specification: <https://chromitem-soc.readthedocs.io/en/latest/clint.html>
+
+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<Self, u16>;
+}
+
+/// 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 {
+    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<C: Clint> {
+    _marker: core::marker::PhantomData<C>,
+}
+
+impl<C: Clint> CLINT<C> {
+    const MTIMECMP_OFFSET: usize = 0x4000;
+
+    const MTIME_OFFSET: usize = 0xBFF8;
+
+    /// Returns the `MSWI` peripheral.
+    pub const fn mswi() -> mswi::MSWI {
+        // SAFETY: valid base address
+        unsafe { mswi::MSWI::new(C::BASE) }
+    }
+
+    /// Returns the `MTIMER` peripheral.
+    pub const fn mtimer() -> mtimer::MTIMER {
+        // SAFETY: valid base address
+        unsafe {
+            mtimer::MTIMER::new(
+                C::BASE + Self::MTIMECMP_OFFSET,
+                C::BASE + Self::MTIME_OFFSET,
+            )
+        }
+    }
+}

+ 77 - 0
src/aclint/mswi.rs

@@ -0,0 +1,77 @@
+pub use super::HartIdNumber;
+use crate::common::unsafe_peripheral;
+
+/// Machine-level Software Interrupt Device.
+#[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),
+        }
+    }
+
+    /// Sets the Machine Software Interrupt bit of the `mie` CSR.
+    /// This bit must be set for the `MSWI` to trigger machine software interrupts.
+    ///
+    /// # Safety
+    ///
+    /// Enabling the `MSWI` may break mask-based critical sections.
+    #[inline]
+    pub unsafe fn enable() {
+        riscv::register::mie::set_msoft();
+    }
+
+    /// Clears the Machine Software Interrupt bit of the `mie` CSR.
+    /// When cleared, the `MSWI` cannot trigger machine software interrupts.
+    #[inline]
+    pub fn disable() {
+        // SAFETY: it is safe to disable interrupts
+        unsafe { riscv::register::mie::clear_msoft() };
+    }
+
+    /// 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<H: HartIdNumber>(&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);
+    }
+}

+ 63 - 0
src/aclint/mtimer.rs

@@ -0,0 +1,63 @@
+pub use super::HartIdNumber;
+use crate::common::safe_peripheral;
+
+/// Machine-level Timer Device.
+#[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 address must point to a valid `MTIMER` peripheral.
+    #[inline]
+    pub const unsafe fn new(mtimecmp: usize, mtime: usize) -> Self {
+        Self {
+            mtimecmp0: MTIMECMP::new(mtimecmp),
+            mtime: MTIME::new(mtime),
+        }
+    }
+
+    /// 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 enable() {
+        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 disable() {
+        // SAFETY: it is safe to disable interrupts
+        unsafe { riscv::register::mie::clear_mtimer() };
+    }
+
+    /// Returns the `MTIME` register for the HART which ID is `hart_id`.
+    ///
+    /// # Note
+    ///
+    /// For HART ID 0, you can simply use [`MTIMER::mtimecmp0`].
+    #[inline]
+    pub fn mtimecmp<H: HartIdNumber>(&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);

+ 77 - 0
src/aclint/sswi.rs

@@ -0,0 +1,77 @@
+pub use super::HartIdNumber;
+use crate::common::unsafe_peripheral;
+
+/// Supervisor-level Software Interrupt Device.
+#[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),
+        }
+    }
+
+    /// 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<H: HartIdNumber>(&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);
+    }
+}

+ 407 - 0
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<T: Copy, A: Access> {
+    ptr: *mut T,
+    phantom: core::marker::PhantomData<A>,
+}
+
+unsafe impl<T: Copy + Send, A: Access> Send for Reg<T, A> {}
+unsafe impl<T: Copy + Sync, A: Access> Sync for Reg<T, A> {}
+
+impl<T: Copy, A: Access> Reg<T, A> {
+    /// 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<T: Copy, A: Read> Reg<T, A> {
+    /// 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<T: Copy, A: Write> Reg<T, A> {
+    /// 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<T: Copy, A: Read + Write> Reg<T, A> {
+    /// 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<R>(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<A: Read> 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<A: Read + Write> 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<A: Read + Write> 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 {}
+}

+ 9 - 0
src/lib.rs

@@ -0,0 +1,9 @@
+#![no_std]
+
+pub use riscv; // re-export riscv crate to allow users to use it without importing it
+
+pub mod common;
+pub mod macros; // macros for easing the definition of peripherals in PACs
+
+pub mod aclint;
+pub mod plic;

+ 1 - 0
src/macros.rs

@@ -0,0 +1 @@
+

+ 338 - 0
src/plic.rs

@@ -0,0 +1,338 @@
+//! Platform-Level Interrupt Controller (PLIC) peripheral.
+//!
+//! Specification: <https://github.com/riscv/riscv-plic-spec/blob/master/riscv-plic.adoc>
+
+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<Self, u16>;
+}
+
+/// 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<Self, u8>;
+}
+
+/// 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<Self, u16>;
+}
+
+/// 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<P: Plic> {
+    _marker: core::marker::PhantomData<P>,
+}
+
+impl<P: Plic> PLIC<P> {
+    const PRIORITIES_OFFSET: usize = 0;
+
+    const PENDINGS_OFFSET: usize = 0x1000;
+
+    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;
+
+    /// Sets the Machine External Interrupt bit of the `mie` CSR.
+    /// This bit must be set for the PLIC to trigger machine external interrupts.
+    ///
+    /// # Safety
+    ///
+    /// Enabling the `PLIC` may break mask-based critical sections.
+    #[inline]
+    pub unsafe fn enable() {
+        riscv::register::mie::set_mext();
+    }
+
+    /// Clears the Machine External Interrupt bit of the `mie` CSR.
+    /// When cleared, the PLIC does not trigger machine external interrupts.
+    #[inline]
+    pub fn disable() {
+        // SAFETY: it is safe to disable interrupts
+        unsafe { riscv::register::mie::clear_mext() };
+    }
+
+    /// 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 {
+        unsafe { priorities::PRIORITIES::new(P::BASE + Self::PRIORITIES_OFFSET) }
+    }
+
+    /// Returns the pendings register of the PLIC.
+    /// This register allows to check if an interrupt source is pending.
+    /// This register is shared among all the contexts.
+    #[inline]
+    pub fn pendings() -> pendings::PENDINGS {
+        unsafe { pendings::PENDINGS::new(P::BASE + Self::PENDINGS_OFFSET) }
+    }
+
+    /// Returns the interrupt enable register assigned to a given context.
+    /// This register allows to enable/disable interrupt sources for a given context.
+    /// Each context has its own enable register.
+    #[inline]
+    pub fn enables<C: ContextNumber>(context: C) -> enables::ENABLES {
+        let context = context.number() as usize;
+        let addr = P::BASE + Self::ENABLES_OFFSET + context * Self::ENABLES_SEPARATION;
+        // SAFETY: context is a valid index
+        unsafe { enables::ENABLES::new(addr) }
+    }
+
+    /// Returns the interrupt threshold register assigned to a given context.
+    /// This register allows to set the priority threshold level for a given context.
+    /// Each context has its own threshold register.
+    #[inline]
+    pub fn threshold<C: ContextNumber>(context: C) -> threshold::THRESHOLD {
+        let context = context.number() as usize;
+        let addr = P::BASE + Self::THRESHOLDS_OFFSET + context * Self::THRESHOLDS_SEPARATION;
+        // SAFETY: context is a valid index
+        unsafe { threshold::THRESHOLD::new(addr) }
+    }
+
+    /// Returns the interrupt claim/complete register assigned to a given context.
+    /// This register allows to claim and complete interrupts for a given context.
+    /// Each context has its own claim/complete register.
+    #[inline]
+    pub fn claim<C: ContextNumber>(context: C) -> claim::CLAIM {
+        let context = context.number() as usize;
+        let addr = P::BASE + Self::CLAIMS_OFFSET + context * Self::CLAIMS_SEPARATION;
+        // SAFETY: context is a valid index
+        unsafe { claim::CLAIM::new(addr) }
+    }
+}
+
+#[cfg(test)]
+pub(self) mod test {
+    use super::*;
+
+    #[derive(Clone, Copy, Debug, Eq, PartialEq)]
+    #[repr(u16)]
+    pub(super) enum Interrupt {
+        I1 = 1,
+        I2 = 2,
+        I3 = 3,
+        I4 = 4,
+    }
+
+    #[derive(Clone, Copy, Debug, Eq, PartialEq)]
+    #[repr(u8)]
+    pub(super) enum Priority {
+        P0 = 0,
+        P1 = 1,
+        P2 = 2,
+        P3 = 3,
+    }
+
+    #[derive(Clone, Copy, Debug, Eq, PartialEq)]
+    #[repr(u16)]
+    pub(super) 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<Self, u16> {
+            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<Self, u8> {
+            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<Self, u16> {
+            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));
+    }
+}

+ 49 - 0
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<I: InterruptNumber>(self) -> Option<I> {
+        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<I: InterruptNumber>(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::<Interrupt>(), 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));
+        }
+    }
+}

+ 232 - 0
src/plic/enables.rs

@@ -0,0 +1,232 @@
+//! 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 _ }
+    }
+
+    /// Checks if an interrupt source is enabled for the PLIC context.
+    #[inline]
+    pub fn is_enabled<I: InterruptNumber>(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<u32, RW> = 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<I: InterruptNumber>(self, source: I) {
+        let source = source.number() as usize;
+        let offset = (source / u32::BITS as usize) as _;
+        // SAFETY: valid interrupt number
+        let reg: Reg<u32, RW> = 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<I: InterruptNumber>(
+        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<u32, RW> = 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<I: InterruptNumber>(self, source: I) {
+        let source = source.number() as usize;
+        let offset = (source / u32::BITS as usize) as _;
+        // SAFETY: valid interrupt number
+        let reg: Reg<u32, RW> = 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<I: InterruptNumber>(
+        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<u32, RW> = 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<I: InterruptNumber>(self) {
+        for offset in 0..=(I::MAX_INTERRUPT_NUMBER as u32 / u32::BITS) as isize {
+            // SAFETY: valid offset
+            let reg: Reg<u32, RW> = 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<I: InterruptNumber>(self) {
+        for offset in 0..=(I::MAX_INTERRUPT_NUMBER as u32 / u32::BITS) as _ {
+            // SAFETY: valid offset
+            let reg: Reg<u32, RW> = 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::<Interrupt>();
+            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::<Interrupt>() };
+            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);
+        }
+    }
+}

+ 57 - 0
src/plic/pendings.rs

@@ -0,0 +1,57 @@
+//! Interrupt pending bits register.
+
+use crate::{
+    common::{Reg, RO},
+    plic::InterruptNumber,
+};
+
+#[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 _ }
+    }
+
+    /// Checks if an interrupt triggered by a given source is pending.
+    #[inline]
+    pub fn is_pending<I: InterruptNumber>(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<u32, RO> = 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);
+        }
+    }
+}

+ 91 - 0
src/plic/priorities.rs

@@ -0,0 +1,91 @@
+//! Interrupts Priorities register.
+
+use crate::{
+    common::{Reg, RW},
+    plic::{InterruptNumber, PriorityNumber},
+};
+
+#[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 _ }
+    }
+
+    /// Returns the priority assigned to a given interrupt source.
+    #[inline]
+    pub fn get_priority<I: InterruptNumber, P: PriorityNumber>(self, source: I) -> P {
+        // SAFETY: valid interrupt number
+        let reg: Reg<u32, RW> = 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<I: InterruptNumber, P: PriorityNumber>(
+        self,
+        source: I,
+        priority: P,
+    ) {
+        // SAFETY: valid interrupt number
+        let reg: Reg<u32, RW> = 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<I: InterruptNumber>(self) {
+        for source in 0..=I::MAX_INTERRUPT_NUMBER as _ {
+            // SAFETY: interrupt number within range
+            let reg: Reg<u32, RW> = 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::<Interrupt>();
+        for i in 1..=Interrupt::MAX_INTERRUPT_NUMBER {
+            let source = Interrupt::from_number(i).unwrap();
+            assert_eq!(priorities.get_priority::<_, Priority>(source), Priority::P0);
+        }
+    }
+}

+ 55 - 0
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<P: PriorityNumber>(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<P: PriorityNumber>(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>(), priority);
+        }
+        threshold.reset();
+        assert_eq!(threshold.get_threshold::<Priority>(), Priority::P0);
+    }
+}