浏览代码

Merge pull request #164 from rust-embedded/add-peripheral

Add `riscv-peripheral` crate
Román Cárdenas Rodríguez 1 年之前
父节点
当前提交
ddf131f24e

+ 10 - 0
.github/workflows/changelog.yaml

@@ -21,6 +21,8 @@ jobs:
               - 'riscv/**'
               - 'riscv/**'
             riscv-pac:
             riscv-pac:
               - 'riscv-pac/**'
               - 'riscv-pac/**'
+            riscv-peripheral:
+              - 'riscv-peripheral/**'
             riscv-rt:
             riscv-rt:
               - 'riscv-rt/**'
               - 'riscv-rt/**'
             riscv-semihosting:
             riscv-semihosting:
@@ -57,3 +59,11 @@ jobs:
           changeLogPath: ./riscv-semihosting/CHANGELOG.md
           changeLogPath: ./riscv-semihosting/CHANGELOG.md
           skipLabels: 'skip changelog'
           skipLabels: 'skip changelog'
           missingUpdateErrorMessage: 'Please add a changelog entry in the riscv-semihosting/CHANGELOG.md file.'
           missingUpdateErrorMessage: 'Please add a changelog entry in the riscv-semihosting/CHANGELOG.md file.'
+
+      - name: Check for CHANGELOG.md (riscv-peripheral)
+        if: steps.changes.outputs.riscv-peripheral == 'true'
+        uses: dangoslen/changelog-enforcer@v3
+        with:
+          changeLogPath: ./riscv-peripheral/CHANGELOG.md
+          skipLabels: 'skip changelog'
+          missingUpdateErrorMessage: 'Please add a changelog entry in the riscv-peripheral/CHANGELOG.md file.'

+ 2 - 1
.github/workflows/clippy.yaml

@@ -29,7 +29,8 @@ jobs:
       - name: Run clippy (no features)
       - name: Run clippy (no features)
         run: cargo clippy --all --no-default-features -- -D warnings
         run: cargo clippy --all --no-default-features -- -D warnings
       - name: Run clippy (all features)
       - name: Run clippy (all features)
-        run: cargo clippy --all --all-features -- -D warnings
+        # We exclude riscv-peripheral because it's not yet stable-compliant
+        run: cargo clippy --exclude riscv-peripheral --all --all-features -- -D warnings
   
   
   # Additonal clippy checks for riscv-rt
   # Additonal clippy checks for riscv-rt
   clippy-riscv-rt:
   clippy-riscv-rt:

+ 64 - 0
.github/workflows/riscv-peripheral.yaml

@@ -0,0 +1,64 @@
+on:
+  push:
+    branches: [ master ]
+  pull_request:
+  merge_group:
+
+name: Build check (riscv-peripheral)
+
+jobs:
+  # We check that the crate builds and links for all the toolchains and targets.
+  build-riscv:
+    strategy:
+      matrix:
+        # All generated code should be running on stable now, MRSV is 1.75.0
+        toolchain: [ stable, nightly, 1.75.0 ]
+        target:
+          - riscv32i-unknown-none-elf
+          - riscv32imc-unknown-none-elf
+          - riscv32imac-unknown-none-elf
+          - riscv64imac-unknown-none-elf
+          - riscv64gc-unknown-none-elf
+        include:
+          # Nightly is only for reference and allowed to fail
+          - toolchain: nightly
+            experimental: true
+    runs-on: ubuntu-latest
+    continue-on-error: ${{ matrix.experimental || false }}
+    steps:
+    - uses: actions/checkout@v4
+    - uses: dtolnay/rust-toolchain@master
+      with:
+        toolchain: ${{ matrix.toolchain }}
+        targets: ${{ matrix.target }}
+    - name: Build (no features)
+      run: cargo build --package riscv-peripheral --target ${{ matrix.target }}
+    - name: Build (all features)
+      run: cargo build --package riscv-peripheral --target ${{ matrix.target }} --all-features
+
+  # On MacOS, Ubuntu, and Windows, we run the tests.
+  build-others:
+    strategy:
+      matrix:
+        os:
+        - macos-latest 
+        - ubuntu-latest
+        # - windows-latest issues when testing and external symbols are not found
+    runs-on: ${{ matrix.os }}
+    steps:
+      - uses: actions/checkout@v3
+      - uses: dtolnay/rust-toolchain@stable
+      - name: Build (no features)
+        run: cargo test --package riscv-peripheral
+      - name: Build (all features)
+        run: cargo test --package riscv-peripheral --all-features
+
+  # Job to check that all the builds succeeded
+  build-check:
+    needs:
+    - build-riscv
+    - build-others
+    runs-on: ubuntu-latest
+    if: always()
+    steps:
+      - run: jq --exit-status 'all(.result == "success")' <<< '${{ toJson(needs) }}'

+ 1 - 0
Cargo.toml

@@ -3,6 +3,7 @@ resolver = "2"
 members = [
 members = [
     "riscv",
     "riscv",
     "riscv-pac",
     "riscv-pac",
+    "riscv-peripheral",
     "riscv-rt",
     "riscv-rt",
     "riscv-semihosting",
     "riscv-semihosting",
 ]
 ]

+ 2 - 0
README.md

@@ -4,6 +4,7 @@ This repository contains various crates useful for writing Rust programs on RISC
 
 
 * [`riscv`]: CPU registers access and intrinsics
 * [`riscv`]: CPU registers access and intrinsics
 * [`riscv-pac`]: Common traits to be implemented by RISC-V PACs
 * [`riscv-pac`]: Common traits to be implemented by RISC-V PACs
+* [`riscv-peripheral`]: Interfaces for standard RISC-V peripherals
 * [`riscv-rt`]: Startup code and interrupt handling
 * [`riscv-rt`]: Startup code and interrupt handling
 * [`riscv-semihosting`]: Semihosting for RISC-V processors
 * [`riscv-semihosting`]: Semihosting for RISC-V processors
 
 
@@ -23,6 +24,7 @@ to intervene to uphold that code of conduct.
 
 
 [`riscv`]: https://crates.io/crates/riscv
 [`riscv`]: https://crates.io/crates/riscv
 [`riscv-pac`]: https://crates.io/crates/riscv-pac
 [`riscv-pac`]: https://crates.io/crates/riscv-pac
+[`riscv-peripheral`]: https://crates.io/crates/riscv-peripheral
 [`riscv-rt`]: https://crates.io/crates/riscv-rt
 [`riscv-rt`]: https://crates.io/crates/riscv-rt
 [`riscv-semihosting`]: https://crates.io/crates/riscv-semihosting
 [`riscv-semihosting`]: https://crates.io/crates/riscv-semihosting
 [team]: https://github.com/rust-embedded/wg#the-risc-v-team
 [team]: https://github.com/rust-embedded/wg#the-risc-v-team

+ 6 - 0
riscv-pac/CHANGELOG.md

@@ -7,8 +7,14 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
 
 
 ## [Unreleased]
 ## [Unreleased]
 
 
+- Fix crates.io badge links
+
 ## [v0.1.0] - 2024-01-14
 ## [v0.1.0] - 2024-01-14
 
 
 ### Added
 ### Added
 
 
 - Add `InterruptNumber`, `PriorityNumber`, and `HartIdNumber` traits.
 - Add `InterruptNumber`, `PriorityNumber`, and `HartIdNumber` traits.
+
+### Changed
+
+- Update `README.md`

+ 1 - 1
riscv-pac/Cargo.toml

@@ -1,6 +1,6 @@
 [package]
 [package]
 name = "riscv-pac"
 name = "riscv-pac"
-version = "0.1.0"
+version = "0.1.1"
 edition = "2021"
 edition = "2021"
 rust-version = "1.60"
 rust-version = "1.60"
 repository = "https://github.com/rust-embedded/riscv"
 repository = "https://github.com/rust-embedded/riscv"

+ 4 - 4
riscv-pac/README.md

@@ -1,5 +1,5 @@
-[![crates.io](https://img.shields.io/crates/d/riscv.svg)](https://crates.io/crates/riscv)
-[![crates.io](https://img.shields.io/crates/v/riscv.svg)](https://crates.io/crates/riscv)
+[![crates.io](https://img.shields.io/crates/d/riscv-pac.svg)](https://crates.io/crates/riscv-pac)
+[![crates.io](https://img.shields.io/crates/v/riscv-pac.svg)](https://crates.io/crates/riscv-pac)
 
 
 # `riscv-pac`
 # `riscv-pac`
 
 
@@ -7,7 +7,7 @@
 
 
 This project is developed and maintained by the [RISC-V team][team].
 This project is developed and maintained by the [RISC-V team][team].
 
 
-## [Documentation](https://docs.rs/crate/riscv)
+## [Documentation](https://docs.rs/crate/riscv-pac)
 
 
 ## Minimum Supported Rust Version (MSRV)
 ## Minimum Supported Rust Version (MSRV)
 
 
@@ -16,7 +16,7 @@ compile with older versions but that may change in any new patch release.
 
 
 ## License
 ## License
 
 
-Copyright 2023-2024s [RISC-V team][team]
+Copyright 2023-2024 [RISC-V team][team]
 
 
 Permission to use, copy, modify, and/or distribute this software for any purpose
 Permission to use, copy, modify, and/or distribute this software for any purpose
 with or without fee is hereby granted, provided that the above copyright notice
 with or without fee is hereby granted, provided that the above copyright notice

+ 12 - 0
riscv-peripheral/CHANGELOG.md

@@ -0,0 +1,12 @@
+# Change Log
+
+All notable changes to this project will be documented in this file.
+
+The format is based on [Keep a Changelog](http://keepachangelog.com/)
+and this project adheres to [Semantic Versioning](http://semver.org/).
+
+## [Unreleased]
+
+### Added
+
+- Add `ACLINT`, `CLINT`, and `PLIC` structs

+ 26 - 0
riscv-peripheral/Cargo.toml

@@ -0,0 +1,26 @@
+[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"
+embedded-hal-async = { version = "1.0.0", optional =  true }
+riscv = { path = "../riscv", version = "0.11.0" }
+riscv-pac = { path = "../riscv-pac", version = "0.1.0" }
+
+[dev-dependencies]
+heapless = "0.8.0"
+
+[features]
+aclint-hal-async = ["embedded-hal-async"]
+
+[package.metadata.docs.rs]
+all-features = true
+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",
+]

+ 40 - 0
riscv-peripheral/README.md

@@ -0,0 +1,40 @@
+[![crates.io](https://img.shields.io/crates/d/riscv-peripheral.svg)](https://crates.io/crates/riscv-peripheral)
+[![crates.io](https://img.shields.io/crates/v/riscv-peripheral.svg)](https://crates.io/crates/riscv-peripheral)
+
+# `riscv-peripheral`
+
+> Interfaces for standard RISC-V peripherals
+
+This project is developed and maintained by the [RISC-V team][team].
+
+## [Documentation](https://docs.rs/crate/riscv-peripheral)
+
+## Minimum Supported Rust Version (MSRV)
+
+This crate is guaranteed to compile on stable Rust 1.75 and up. It *might*
+compile with older versions but that may change in any new patch release.
+
+## License
+
+Copyright 2023-2024 [RISC-V team][team]
+
+Permission to use, copy, modify, and/or distribute this software for any purpose
+with or without fee is hereby granted, provided that the above copyright notice
+and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
+REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
+INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
+OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
+TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
+THIS SOFTWARE.
+
+## Code of Conduct
+
+Contribution to this crate is organized under the terms of the [Rust Code of
+Conduct][CoC], the maintainer of this crate, the [RISC-V team][team], promises
+to intervene to uphold that code of conduct.
+
+[CoC]: CODE_OF_CONDUCT.md
+[team]: https://github.com/rust-embedded/wg#the-risc-v-team

+ 204 - 0
riscv-peripheral/examples/e310x.rs

@@ -0,0 +1,204 @@
+//! Peripheral definitions for the E310x chip.
+//! This is a simple example of how to use the `riscv-peripheral` crate to generate
+//! peripheral definitions for a target.
+
+use riscv_pac::{HartIdNumber, 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<Self, u16> {
+        if number > Self::MAX_HART_ID_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<Self, u16> {
+        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<Self, u8> {
+        if number > Self::MAX_PRIORITY_NUMBER {
+            Err(number)
+        } else {
+            // SAFETY: valid priority number
+            Ok(unsafe { core::mem::transmute(number) })
+        }
+    }
+}
+
+#[cfg(feature = "aclint-hal-async")]
+riscv_peripheral::clint_codegen!(
+    base 0x0200_0000,
+    freq 32_768,
+    async_delay,
+    mtimecmps [mtimecmp0=(HartId::H0,"`H0`")],
+    msips [msip0=(HartId::H0,"`H0`")],
+);
+
+#[cfg(not(feature = "aclint-hal-async"))]
+riscv_peripheral::clint_codegen!(
+    base 0x0200_0000,
+    freq 32_768,
+    mtimecmps [mtimecmp0=(HartId::H0,"`H0`")],
+    msips [msip0=(HartId::H0,"`H0`")],
+);
+
+riscv_peripheral::plic_codegen!(
+    base 0x0C00_0000,
+    ctxs [ctx0=(HartId::H0,"`H0`")],
+);
+
+#[cfg(feature = "aclint-hal-async")]
+/// extern functions needed by the `riscv-peripheral` crate for the `async` feature.
+///
+/// # Note
+///
+/// The functionality in this module is just to illustrate how to enable the `async` feature
+/// The timer queue used here, while functional, is unsound and should not be used in production.
+/// In this case, you should protect the timer queue with a mutex or critical section.
+/// For a more robust implementation, use proper timer queues such as the ones provided by `embassy-time`
+mod async_no_mangle {
+    use super::CLINT;
+    use heapless::binary_heap::{BinaryHeap, Min};
+    use riscv_peripheral::{aclint::mtimer::MTIMER, hal_async::aclint::Timer};
+
+    const N_TIMERS: usize = 16;
+    static mut TIMER_QUEUE: BinaryHeap<Timer, Min, N_TIMERS> = BinaryHeap::new();
+
+    #[no_mangle]
+    fn _riscv_peripheral_aclint_mtimer() -> MTIMER {
+        CLINT::mtimer()
+    }
+
+    #[no_mangle]
+    fn _riscv_peripheral_aclint_push_timer(t: Timer) -> Result<(), Timer> {
+        unsafe { TIMER_QUEUE.push(t) }
+    }
+
+    #[no_mangle]
+    fn _riscv_peripheral_aclint_wake_timers(current_tick: u64) -> Option<u64> {
+        let mut next_expires = None;
+        while let Some(t) = unsafe { TIMER_QUEUE.peek() } {
+            if t.expires() > current_tick {
+                next_expires = Some(t.expires());
+                break;
+            }
+            let t = unsafe { TIMER_QUEUE.pop() }.unwrap();
+            t.waker().wake_by_ref();
+        }
+        next_expires
+    }
+}
+
+fn main() {}

+ 141 - 0
riscv-peripheral/src/aclint.rs

@@ -0,0 +1,141 @@
+//! 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;
+
+pub use riscv_pac::HartIdNumber; // re-export useful riscv-pac traits
+
+/// 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<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.
+    #[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<Self, u16> {
+            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));
+    }
+}

+ 100 - 0
riscv-peripheral/src/aclint/mswi.rs

@@ -0,0 +1,100 @@
+//! 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<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 _) }
+    }
+
+    /// Returns the `MSIP` register for the current HART.
+    ///
+    /// # Note
+    ///
+    /// This function determines the current HART ID by reading the [`riscv::register::mhartid`] CSR.
+    /// Thus, it can only be used in M-mode. For S-mode, use [`MSWI::msip`] instead.
+    #[inline]
+    pub fn msip_mhartid(&self) -> MSIP {
+        let hart_id = riscv::register::mhartid::read();
+        // SAFETY: `hart_id` is valid for the target and is the current hart
+        unsafe { MSIP::new(self.msip0.get_ptr().add(hart_id) 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);
+        }
+    }
+}

+ 92 - 0
riscv-peripheral/src/aclint/mtimer.rs

@@ -0,0 +1,92 @@
+//! 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<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 _) }
+    }
+
+    /// Returns the `MTIMECMP` register for the current HART.
+    ///
+    /// # Note
+    ///
+    /// This function determines the current HART ID by reading the [`riscv::register::mhartid`] CSR.
+    /// Thus, it can only be used in M-mode. For S-mode, use [`MTIMER::mtimecmp`] instead.
+    #[inline]
+    pub fn mtimecmp_mhartid(&self) -> MTIMECMP {
+        let hart_id = riscv::register::mhartid::read();
+        // SAFETY: `hart_id` is valid for the target and is the current hart
+        unsafe { MTIMECMP::new(self.mtimecmp0.get_ptr().add(hart_id) 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 _
+        );
+    }
+}

+ 118 - 0
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<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);
+    }
+}
+
+#[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);
+        }
+    }
+}

+ 407 - 0
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<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 {}
+}

+ 5 - 0
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

+ 46 - 0
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 {}
+    }
+}

+ 6 - 0
riscv-peripheral/src/hal_async.rs

@@ -0,0 +1,6 @@
+//! async trait implementations for embedded-hal
+
+pub use embedded_hal_async::*; // re-export embedded-hal-async to allow macros to use it
+
+#[cfg(feature = "aclint-hal-async")]
+pub mod aclint; // ACLINT and CLINT peripherals

+ 269 - 0
riscv-peripheral/src/hal_async/aclint.rs

@@ -0,0 +1,269 @@
+//! Asynchronous delay implementation for the (A)CLINT peripheral.
+//!
+//! # Note
+//!
+//! The asynchronous delay implementation for the (A)CLINT peripheral relies on the machine-level timer interrupts.
+//! Therefore, it needs to schedule the machine-level timer interrupts via the [`MTIMECMP`] register assigned to the current HART.
+//! Thus, the [`Delay`] instance must be created on the same HART that is used to call the asynchronous delay methods.
+//!
+//! # Requirements
+//!
+//! The following `extern "Rust"` functions must be implemented:
+//!
+//! - `fn _riscv_peripheral_aclint_mtimer(hart_id: usize) -> MTIMER`: This function returns the `MTIMER` register for the given HART ID.
+//! - `fn _riscv_peripheral_aclint_push_timer(t: Timer) -> Result<(), Timer>`: This function pushes a new timer to a timer queue assigned to the given HART ID.
+//! If it fails (e.g., the timer queue is full), it returns back the timer that failed to be pushed.
+//! The logic of timer queues are application-specific and are not provided by this crate.
+//! - `fn _riscv_peripheral_aclint_wake_timers(current_tick: u64) -> Option<u64>`:
+//! This function pops all the expired timers from a timer queue assigned to the current HART ID and wakes their associated wakers.
+//! The function returns the next [`MTIME`] tick at which the next timer expires. If the queue is empty, it returns `None`.
+
+use crate::aclint::mtimer::{MTIME, MTIMECMP, MTIMER};
+pub use crate::hal_async::delay::DelayNs;
+use core::{
+    cmp::{Eq, Ord, PartialEq, PartialOrd},
+    future::Future,
+    pin::Pin,
+    task::{Context, Poll, Waker},
+};
+
+extern "Rust" {
+    /// Returns the `MTIMER` register for the current HART ID.
+    /// This is necessary for [`MachineTimer`] to obtain the corresponding `MTIMER` register.
+    ///
+    /// # Safety
+    ///
+    /// Do not call this function directly. It is only meant to be called by [`MachineTimer`].
+    fn _riscv_peripheral_aclint_mtimer() -> MTIMER;
+
+    /// Tries to push a new timer to the timer queue assigned to the `MTIMER` register for the current HART ID.
+    /// If it fails (e.g., the timer queue is full), it returns back the timer that failed to be pushed.
+    ///
+    /// # Safety
+    ///
+    /// Do not call this function directly. It is only meant to be called by [`DelayAsync`].
+    fn _riscv_peripheral_aclint_push_timer(t: Timer) -> Result<(), Timer>;
+
+    /// Pops all the expired timers from the timer queue assigned to the `MTIMER` register for the
+    /// current HART ID and wakes their associated wakers. Once it is done, if the queue is empty,
+    /// it returns `None`. Alternatively, if the queue is not empty but the earliest timer has not expired
+    /// yet, it returns `Some(next_expires)` where `next_expires` is the tick at which this timer expires.
+    ///
+    /// # Safety
+    ///
+    /// Do not call this function directly. It is only meant to be called by [`MachineTimer`] and [`DelayAsync`].
+    fn _riscv_peripheral_aclint_wake_timers(current_tick: u64) -> Option<u64>;
+}
+
+/// Machine-level timer interrupt handler. This handler is triggered whenever the `MTIME`
+/// register reaches the value of the `MTIMECMP` register of the current HART.
+#[no_mangle]
+#[allow(non_snake_case)]
+fn MachineTimer() {
+    // recover the MTIME and MTIMECMP registers for the current HART
+    let mtimer = unsafe { _riscv_peripheral_aclint_mtimer() };
+    let (mtime, mtimercmp) = (mtimer.mtime, mtimer.mtimecmp_mhartid());
+    // schedule the next machine timer interrupt
+    schedule_machine_timer(mtime, mtimercmp);
+}
+
+/// Schedules the next machine timer interrupt for the given HART ID according to the timer queue.
+fn schedule_machine_timer(mtime: MTIME, mtimercmp: MTIMECMP) {
+    unsafe { riscv::register::mie::clear_mtimer() }; // disable machine timer interrupts to avoid reentrancy
+    let current_tick = mtime.read();
+    if let Some(next_expires) = unsafe { _riscv_peripheral_aclint_wake_timers(current_tick) } {
+        debug_assert!(next_expires > current_tick);
+        mtimercmp.write(next_expires); // schedule next interrupt at next_expires
+        unsafe { riscv::register::mie::set_mtimer() }; // enable machine timer interrupts
+    }
+}
+
+/// Asynchronous delay implementation for (A)CLINT peripherals.
+///
+/// # Note
+///
+/// The asynchronous delay implementation for (A)CLINT peripherals relies on the machine-level timer interrupts.
+/// Therefore, it needs to schedule the machine-level timer interrupts via the [`MTIMECMP`] register assigned to the current HART.
+/// Thus, the [`Delay`] instance must be created on the same HART that is used to call the asynchronous delay methods.
+/// Additionally, the rest of the application must not modify the [`MTIMER`] register assigned to the current HART.
+#[derive(Clone)]
+pub struct Delay {
+    freq: usize,
+    mtime: MTIME,
+    mtimecmp: MTIMECMP,
+}
+
+impl Delay {
+    /// Creates a new `Delay` instance for the current HART.
+    #[inline]
+    pub fn new(freq: usize) -> Self {
+        let mtimer = unsafe { _riscv_peripheral_aclint_mtimer() };
+        let (mtime, mtimecmp) = (mtimer.mtime, mtimer.mtimecmp_mhartid());
+        Self {
+            freq,
+            mtime,
+            mtimecmp,
+        }
+    }
+
+    /// 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;
+    }
+}
+
+impl DelayNs for Delay {
+    #[inline]
+    async fn delay_ns(&mut self, ns: u32) {
+        let n_ticks = ns as u64 * self.get_freq() as u64 / 1_000_000_000;
+        DelayAsync::new(self, n_ticks).await;
+    }
+
+    #[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, 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, n_ticks).await;
+    }
+}
+
+/// Timer queue entry.
+/// When pushed to the timer queue via the `_riscv_peripheral_aclint_push_timer` function,
+/// this entry provides the necessary information to adapt it to the timer queue implementation.
+#[derive(Debug)]
+pub struct Timer {
+    freq: usize,
+    mtime: MTIME,
+    mtimecmp: MTIMECMP,
+    expires: u64,
+    waker: Waker,
+}
+
+impl Timer {
+    /// Creates a new timer queue entry.
+    #[inline]
+    const fn new(
+        freq: usize,
+        mtime: MTIME,
+        mtimecmp: MTIMECMP,
+        expires: u64,
+        waker: Waker,
+    ) -> Self {
+        Self {
+            freq,
+            mtime,
+            mtimecmp,
+            expires,
+            waker,
+        }
+    }
+
+    /// Returns the frequency of the [`MTIME`] register associated with this timer.
+    #[inline]
+    pub const fn freq(&self) -> usize {
+        self.freq
+    }
+
+    /// Returns the [`MTIME`] register associated with this timer.
+    #[inline]
+    pub const fn mtime(&self) -> MTIME {
+        self.mtime
+    }
+
+    /// Returns the [`MTIMECMP`] register associated with this timer.
+    #[inline]
+    pub const fn mtimecmp(&self) -> MTIMECMP {
+        self.mtimecmp
+    }
+
+    /// Returns the tick at which the timer expires.
+    #[inline]
+    pub const fn expires(&self) -> u64 {
+        self.expires
+    }
+
+    /// Returns the waker associated with this timer.
+    #[inline]
+    pub fn waker(&self) -> Waker {
+        self.waker.clone()
+    }
+}
+
+impl PartialEq for Timer {
+    fn eq(&self, other: &Self) -> bool {
+        self.freq == other.freq && self.expires == other.expires
+    }
+}
+
+impl Eq for Timer {}
+
+impl Ord for Timer {
+    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
+        self.expires.cmp(&other.expires)
+    }
+}
+
+impl PartialOrd for Timer {
+    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
+        Some(self.expires.cmp(&other.expires))
+    }
+}
+
+struct DelayAsync<'a> {
+    delay: &'a Delay,
+    expires: u64,
+    pushed: bool,
+}
+
+impl<'a> DelayAsync<'a> {
+    pub fn new(delay: &'a Delay, n_ticks: u64) -> Self {
+        let t0 = delay.mtime.read();
+        let expires = t0.wrapping_add(n_ticks);
+        Self {
+            delay,
+            expires,
+            pushed: false,
+        }
+    }
+}
+
+impl<'a> Future for DelayAsync<'a> {
+    type Output = ();
+
+    #[inline]
+    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
+        if self.delay.mtime.read() < self.expires {
+            if !self.pushed {
+                // we only push the timer to the queue the first time we poll
+                self.pushed = true;
+                let timer = Timer::new(
+                    self.delay.freq,
+                    self.delay.mtime,
+                    self.delay.mtimecmp,
+                    self.expires,
+                    cx.waker().clone(),
+                );
+                unsafe {
+                    _riscv_peripheral_aclint_push_timer(timer).expect("timer queue is full");
+                };
+                // we also need to reschedule the machine timer interrupt
+                schedule_machine_timer(self.delay.mtime, self.delay.mtimecmp);
+            }
+            Poll::Pending
+        } else {
+            Poll::Ready(())
+        }
+    }
+}

+ 20 - 0
riscv-peripheral/src/lib.rs

@@ -0,0 +1,20 @@
+//! Standard RISC-V peripherals for embedded systems written in Rust.
+//!
+//! ## Features
+//!
+//! - `aclint-hal-async`: enables the [`hal_async::delay::DelayNs`] implementation for the ACLINT peripheral.
+//! This feature relies on external functions that must be provided by the user. See [`hal_async::aclint`] for more information.
+
+#![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 = "embedded-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

+ 357 - 0
riscv-peripheral/src/macros.rs

@@ -0,0 +1,357 @@
+//! 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::DelayNs` trait
+/// ```
+///
+/// ## 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<Self, u16> {
+///     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::<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::<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::DelayNs` trait in order to use delay methods.
+            #[inline]
+            pub const fn delay() -> $crate::hal::aclint::Delay {
+                $crate::hal::aclint::Delay::new(Self::mtime(), Self::freq())
+            }
+        }
+        $crate::clint_codegen!($($tail)*);
+    };
+    (async_delay, $($tail:tt)*) => {
+        impl CLINT {
+            /// Asynchronous delay implementation for CLINT peripherals.
+            ///
+            /// # Note
+            ///
+            /// You must export the `riscv_peripheral::hal_async::delay::DelayNs` trait in order to use delay methods.
+            ///
+            /// This implementation relies on the machine-level timer interrupts to wake futures.
+            /// Therefore, it needs to schedule the machine-level timer interrupts via the `MTIMECMP` register assigned to the current HART.
+            /// Thus, the `Delay` instance must be created on the same HART that is used to call the asynchronous delay methods.
+            /// Additionally, the rest of the application must not modify the `MTIMER` register assigned to the current HART.
+            #[inline]
+            pub fn async_delay() -> $crate::hal_async::aclint::Delay {
+                $crate::hal_async::aclint::Delay::new(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::<PLIC>::priorities()
+            }
+
+            /// Returns the pendings register of the PLIC.
+            #[inline]
+            pub fn pendings() -> $crate::plic::pendings::PENDINGS {
+                $crate::plic::PLIC::<PLIC>::pendings()
+            }
+
+            /// Returns the context proxy of a given PLIC HART context.
+            #[inline]
+            pub fn ctx<H: $crate::plic::HartIdNumber>(hart_id: H) -> $crate::plic::CTX<Self> {
+                $crate::plic::PLIC::<PLIC>::ctx(hart_id)
+            }
+
+            /// Returns the PLIC HART context for the current HART.
+            ///
+            /// # Note
+            ///
+            /// This function determines the current HART ID by reading the [`riscv::register::mhartid`] CSR.
+            /// Thus, it can only be used in M-mode. For S-mode, use [`PLIC::ctx`] instead.
+            #[inline]
+            pub fn ctx_mhartid(&self) -> $crate::plic::CTX<Self> {
+                $crate::plic::PLIC::<PLIC>::ctx_mhartid()
+            }
+        }
+        $crate::plic_codegen!($($tail)*);
+    };
+    (ctxs [$($fn:ident = ($ctx:expr , $sctx:expr)),+], $($tail:tt)*) => {
+        impl PLIC {
+            $(
+                #[doc = "Returns a PLIC context proxy for context of HART "]
+                #[doc = $sctx]
+                #[doc = "."]
+                #[inline]
+                pub fn $fn() -> $crate::plic::CTX<Self> {
+                    Self::ctx($ctx)
+                }
+            )*
+        }
+        $crate::plic_codegen!($($tail)*);
+    };
+}

+ 311 - 0
riscv-peripheral/src/plic.rs

@@ -0,0 +1,311 @@
+//! 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;
+
+pub use riscv_pac::{HartIdNumber, InterruptNumber, PriorityNumber}; // re-export useful riscv-pac traits
+
+/// 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;
+
+    /// 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 HART context.
+    #[inline]
+    pub fn ctx<H: HartIdNumber>(hart_id: H) -> CTX<P> {
+        // SAFETY: valid context number
+        unsafe { CTX::new(hart_id.number()) }
+    }
+
+    /// Returns the PLIC HART context for the current HART.
+    ///
+    /// # Note
+    ///
+    /// This function determines the current HART ID by reading the [`riscv::register::mhartid`] CSR.
+    /// Thus, it can only be used in M-mode. For S-mode, use [`PLIC::ctx`] instead.
+    #[inline]
+    pub fn ctx_mhartid() -> CTX<P> {
+        let hart_id = riscv::register::mhartid::read();
+        // SAFETY: `hart_id` is valid for the target and is the current hart
+        unsafe { CTX::new(hart_id as _) }
+    }
+}
+
+/// 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<P: Plic> {
+    context: usize,
+    _marker: core::marker::PhantomData<P>,
+}
+
+impl<P: Plic> CTX<P> {
+    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::{HartIdNumber, 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<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 HartIdNumber for Context {
+        const MAX_HART_ID_NUMBER: u16 = 2;
+
+        #[inline]
+        fn number(self) -> u16 {
+            self as _
+        }
+
+        #[inline]
+        fn from_number(number: u16) -> Result<Self, u16> {
+            if number > Self::MAX_HART_ID_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_HART_ID_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));
+    }
+}

+ 49 - 0
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<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));
+        }
+    }
+}

+ 238 - 0
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<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);
+        }
+    }
+}

+ 64 - 0
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<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);
+        }
+    }
+}

+ 98 - 0
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<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
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<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);
+    }
+}