Browse Source

Merge branch 'master' of github.com:rust-embedded/riscv into riscv-rt-asm

Román Cárdenas Rodríguez 2 tháng trước cách đây
mục cha
commit
afddf094a0

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

@@ -21,6 +21,8 @@ jobs:
               - 'riscv/**'
             riscv-pac:
               - 'riscv-pac/**'
+            riscv-peripheral:
+              - 'riscv-peripheral/**'
             riscv-rt:
               - 'riscv-rt/**'
             riscv-semihosting:
@@ -57,3 +59,11 @@ jobs:
           changeLogPath: ./riscv-semihosting/CHANGELOG.md
           skipLabels: 'skip changelog'
           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)
         run: cargo clippy --all --no-default-features -- -D warnings
       - 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
   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 = [
     "riscv",
     "riscv-pac",
+    "riscv-peripheral",
     "riscv-rt",
     "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-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-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-pac`]: https://crates.io/crates/riscv-pac
+[`riscv-peripheral`]: https://crates.io/crates/riscv-peripheral
 [`riscv-rt`]: https://crates.io/crates/riscv-rt
 [`riscv-semihosting`]: https://crates.io/crates/riscv-semihosting
 [team]: https://github.com/rust-embedded/wg#the-risc-v-team

+ 8 - 0
riscv-pac/CHANGELOG.md

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

+ 1 - 1
riscv-pac/Cargo.toml

@@ -1,6 +1,6 @@
 [package]
 name = "riscv-pac"
-version = "0.1.0"
+version = "0.1.1"
 edition = "2021"
 rust-version = "1.60"
 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`
 
@@ -7,7 +7,7 @@
 
 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)
 
@@ -16,7 +16,7 @@ compile with older versions but that may change in any new patch release.
 
 ## 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
 with or without fee is hereby granted, provided that the above copyright notice

+ 14 - 0
riscv-peripheral/CHANGELOG.md

@@ -0,0 +1,14 @@
+# 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]
+
+## [v0.1.0] - 2024-02-15
+
+### Added
+
+- Add `ACLINT`, `CLINT`, and `PLIC` structs

+ 34 - 0
riscv-peripheral/Cargo.toml

@@ -0,0 +1,34 @@
+[package]
+name = "riscv-peripheral"
+version = "0.1.0"
+edition = "2021"
+rust-version = "1.75"
+repository = "https://github.com/rust-embedded/riscv"
+authors = ["The RISC-V Team <risc-v@teams.rust-embedded.org>"]
+categories = ["embedded", "hardware-support", "no-std"]
+description = "Interfaces for standard RISC-V peripherals"
+documentation = "https://docs.rs/riscv-peripheral"
+keywords = ["riscv", "peripheral", "clint", "plic"]
+license = "ISC"
+
+# 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.1" }
+riscv-pac = { path = "../riscv-pac", version = "0.1.1" }
+
+[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);
+    }
+}

+ 6 - 0
riscv-rt/CHANGELOG.md

@@ -15,6 +15,12 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
 
 - `start_rust` is no longer needed, as it is now written in assembly
 
+## [v0.12.2] - 2024-02-15
+
+### Added
+
+- Implementation of `default_mp_hook` when `single-hart` feature is enabled.
+
 ## [v0.12.1] - 2024-01-24
 
 ### Added

+ 2 - 2
riscv-rt/Cargo.toml

@@ -1,6 +1,6 @@
 [package]
 name = "riscv-rt"
-version = "0.12.1"
+version = "0.12.2"
 rust-version = "1.60"
 repository = "https://github.com/rust-embedded/riscv"
 authors = ["The RISC-V Team <risc-v@teams.rust-embedded.org>"]
@@ -17,7 +17,7 @@ s-mode = []
 single-hart = []
 
 [dependencies]
-riscv = {path = "../riscv", version = "0.11.0"}
+riscv = {path = "../riscv", version = "0.11.1"}
 riscv-rt-macros = { path = "macros", version = "0.2.1" }
 
 [dev-dependencies]

+ 6 - 2
riscv-rt/src/lib.rs

@@ -579,15 +579,19 @@ pub static __INTERRUPTS: [Option<unsafe extern "C" fn()>; 12] = [
 pub extern "Rust" fn default_pre_init() {}
 
 /// Default implementation of `_mp_hook` wakes hart 0 and busy-loops all the other harts.
+/// Users can override this function by defining their own `_mp_hook`.
+/// 
+/// # Note
+/// 
+/// If the `single-hart` feature is enabled, `_mp_hook` is not called.
 #[doc(hidden)]
 #[no_mangle]
 #[rustfmt::skip]
-#[cfg(not(feature = "single-hart"))]
 pub extern "Rust" fn default_mp_hook(hartid: usize) -> bool {
     match hartid {
         0 => true,
         _ => loop {
-            unsafe { riscv::asm::wfi() }
+            riscv::asm::wfi();
         },
     }
 }

+ 6 - 0
riscv/CHANGELOG.md

@@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
 
 ## [Unreleased]
 
+## [v0.11.1] - 2024-02-15
+
+### Changed
+
+- Made `asm::wfi`, `fence`, `fence_i` and `sfence` safe (ie, removed `unsafe` from their definitions)
+
 ## [v0.11.0] - 2024-01-14
 
 ### Added

+ 1 - 1
riscv/Cargo.toml

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

+ 4 - 4
riscv/src/asm.rs

@@ -48,7 +48,7 @@ instruction!(
     ///
     /// Provides a hint to the implementation that the current hart can be stalled until an interrupt might need servicing.
     /// The WFI instruction is just a hint, and a legal implementation is to implement WFI as a NOP.
-    , unsafe wfi, "wfi");
+    , wfi, "wfi");
 instruction!(
     /// `SFENCE.VMA` instruction wrapper (all address spaces and page table levels)
     ///
@@ -57,7 +57,7 @@ instruction!(
     /// are ordinarily not ordered with respect to loads and stores in the instruction stream.
     /// Executing an `SFENCE.VMA` instruction guarantees that any stores in the instruction stream prior to the
     /// `SFENCE.VMA` are ordered before all implicit references subsequent to the `SFENCE.VMA`.
-    , unsafe sfence_vma_all, "sfence.vma");
+    , sfence_vma_all, "sfence.vma");
 instruction!(
     /// `FENCE` instruction wrapper
     ///
@@ -71,7 +71,7 @@ instruction!(
     /// The FENCE instruction also orders memory reads and writes made by the hart as observed by
     /// memory reads and writes made by an external device. However, FENCE does not order observations
     /// of events made by an external device using any other signaling mechanism.
-    , unsafe fence, "fence");
+    , fence, "fence");
 instruction!(
     /// `FENCE.I` instruction wrapper
     ///
@@ -89,7 +89,7 @@ instruction!(
     /// The unused fields in the FENCE.I instruction, imm\[11:0\], rs1, and rd, are reserved for
     /// finer-grain fences in future extensions. For forward compatibility, base
     /// implementations shall ignore these fields, and standard software shall zero these fields.
-    , unsafe fence_i, "fence.i");
+    , fence_i, "fence.i");
 
 /// `SFENCE.VMA` instruction wrapper
 ///