diff --git a/.github/workflows/changelog.yaml b/.github/workflows/changelog.yaml index 25b888f0..e613abbe 100644 --- a/.github/workflows/changelog.yaml +++ b/.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.' diff --git a/.github/workflows/clippy.yaml b/.github/workflows/clippy.yaml index 06afb85a..c32f926e 100644 --- a/.github/workflows/clippy.yaml +++ b/.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: diff --git a/.github/workflows/riscv-peripheral.yaml b/.github/workflows/riscv-peripheral.yaml new file mode 100644 index 00000000..8797a8a4 --- /dev/null +++ b/.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) }}' diff --git a/Cargo.toml b/Cargo.toml index fe8fdd1e..5e73fed7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,7 @@ resolver = "2" members = [ "riscv", "riscv-pac", + "riscv-peripheral", "riscv-rt", "riscv-semihosting", ] diff --git a/README.md b/README.md index f35a5e50..5bfbda80 100644 --- a/README.md +++ b/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 diff --git a/riscv-pac/CHANGELOG.md b/riscv-pac/CHANGELOG.md index f4a5a6d8..e804f61e 100644 --- a/riscv-pac/CHANGELOG.md +++ b/riscv-pac/CHANGELOG.md @@ -7,8 +7,14 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] +- Fix crates.io badge links + ## [v0.1.0] - 2024-01-14 ### Added - Add `InterruptNumber`, `PriorityNumber`, and `HartIdNumber` traits. + +### Changed + +- Update `README.md` diff --git a/riscv-pac/Cargo.toml b/riscv-pac/Cargo.toml index 1e8d5cc3..b1cb2ae4 100644 --- a/riscv-pac/Cargo.toml +++ b/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" diff --git a/riscv-pac/README.md b/riscv-pac/README.md index 61c50447..fada9925 100644 --- a/riscv-pac/README.md +++ b/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 diff --git a/riscv-peripheral/CHANGELOG.md b/riscv-peripheral/CHANGELOG.md new file mode 100644 index 00000000..5fb9bd90 --- /dev/null +++ b/riscv-peripheral/CHANGELOG.md @@ -0,0 +1,12 @@ +# Change Log + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](http://keepachangelog.com/) +and this project adheres to [Semantic Versioning](http://semver.org/). + +## [Unreleased] + +### Added + +- Add `ACLINT`, `CLINT`, and `PLIC` structs diff --git a/riscv-peripheral/Cargo.toml b/riscv-peripheral/Cargo.toml new file mode 100644 index 00000000..d0d4e499 --- /dev/null +++ b/riscv-peripheral/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "riscv-peripheral" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +embedded-hal = "1.0.0" +embedded-hal-async = { version = "1.0.0", optional = true } +riscv = { path = "../riscv", version = "0.11.0" } +riscv-pac = { path = "../riscv-pac", version = "0.1.0" } + +[dev-dependencies] +heapless = "0.8.0" + +[features] +aclint-hal-async = ["embedded-hal-async"] + +[package.metadata.docs.rs] +all-features = true +default-target = "riscv64imac-unknown-none-elf" +targets = [ + "riscv32i-unknown-none-elf", "riscv32imc-unknown-none-elf", "riscv32imac-unknown-none-elf", + "riscv64imac-unknown-none-elf", "riscv64gc-unknown-none-elf", +] diff --git a/riscv-peripheral/README.md b/riscv-peripheral/README.md new file mode 100644 index 00000000..940f4bef --- /dev/null +++ b/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 diff --git a/riscv-peripheral/examples/e310x.rs b/riscv-peripheral/examples/e310x.rs new file mode 100644 index 00000000..c0801f24 --- /dev/null +++ b/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 { + 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 { + if number == 0 || number > Self::MAX_INTERRUPT_NUMBER { + Err(number) + } else { + // SAFETY: valid interrupt number + Ok(unsafe { core::mem::transmute(number) }) + } + } +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[repr(u8)] +pub enum Priority { + P0 = 0, + P1 = 1, + P2 = 2, + P3 = 3, + P4 = 4, + P5 = 5, + P6 = 6, + P7 = 7, +} + +unsafe impl PriorityNumber for Priority { + const MAX_PRIORITY_NUMBER: u8 = 7; + + #[inline] + fn number(self) -> u8 { + self as _ + } + + #[inline] + fn from_number(number: u8) -> Result { + if number > Self::MAX_PRIORITY_NUMBER { + Err(number) + } else { + // SAFETY: valid priority number + Ok(unsafe { core::mem::transmute(number) }) + } + } +} + +#[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 = 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 { + 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() {} diff --git a/riscv-peripheral/src/aclint.rs b/riscv-peripheral/src/aclint.rs new file mode 100644 index 00000000..5aa08239 --- /dev/null +++ b/riscv-peripheral/src/aclint.rs @@ -0,0 +1,141 @@ +//! Devices for the Core Local Interruptor (CLINT) and Advanced CLINT (ACLINT) peripherals. +//! +//! CLINT pecification: +//! ACLINT Specification: + +pub mod mswi; +pub mod mtimer; +pub mod sswi; + +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 { + _marker: core::marker::PhantomData, +} + +impl CLINT { + const MTIMECMP_OFFSET: usize = 0x4000; + + const MTIME_OFFSET: usize = 0xBFF8; + + /// Returns the `MSWI` peripheral. + #[inline] + pub const fn mswi() -> mswi::MSWI { + // SAFETY: valid base address + unsafe { mswi::MSWI::new(C::BASE) } + } + + /// Returns the `MTIMER` peripheral. + #[inline] + pub const fn mtimer() -> mtimer::MTIMER { + // SAFETY: valid base address + unsafe { + mtimer::MTIMER::new( + C::BASE + Self::MTIMECMP_OFFSET, + C::BASE + Self::MTIME_OFFSET, + ) + } + } +} + +#[cfg(test)] +pub(crate) mod test { + use super::HartIdNumber; + + #[derive(Clone, Copy, Debug, Eq, PartialEq)] + #[repr(u16)] + pub(crate) enum HartId { + H0 = 0, + H1 = 1, + H2 = 2, + } + + unsafe impl HartIdNumber for HartId { + const MAX_HART_ID_NUMBER: u16 = 2; + + #[inline] + fn number(self) -> u16 { + self as _ + } + + #[inline] + fn from_number(number: u16) -> Result { + if number > Self::MAX_HART_ID_NUMBER { + Err(number) + } else { + // SAFETY: valid context number + Ok(unsafe { core::mem::transmute(number) }) + } + } + } + + #[test] + fn check_hart_id_enum() { + assert_eq!(HartId::H0.number(), 0); + assert_eq!(HartId::H1.number(), 1); + assert_eq!(HartId::H2.number(), 2); + + assert_eq!(HartId::from_number(0), Ok(HartId::H0)); + assert_eq!(HartId::from_number(1), Ok(HartId::H1)); + assert_eq!(HartId::from_number(2), Ok(HartId::H2)); + + assert_eq!(HartId::from_number(3), Err(3)); + } + + #[allow(dead_code)] + #[test] + fn check_clint() { + // Call CLINT macro with a base address and a list of mtimecmps for easing access to per-HART mtimecmp regs. + crate::clint_codegen!( + base 0x0200_0000, + mtimecmps [mtimecmp0=(HartId::H0,"`H0`"), mtimecmp1=(HartId::H1,"`H1`"), mtimecmp2=(HartId::H2,"`H2`")], + msips [msip0=(HartId::H0,"`H0`"), msip1=(HartId::H1,"`H1`"), msip2=(HartId::H2,"`H2`")], + ); + + let mswi = CLINT::mswi(); + let mtimer = CLINT::mtimer(); + + assert_eq!(mswi.msip0.get_ptr() as usize, 0x0200_0000); + assert_eq!(mtimer.mtimecmp0.get_ptr() as usize, 0x0200_4000); + assert_eq!(mtimer.mtime.get_ptr() as usize, 0x0200_bff8); + + let mtimecmp0 = mtimer.mtimecmp(HartId::H0); + let mtimecmp1 = mtimer.mtimecmp(HartId::H1); + let mtimecmp2 = mtimer.mtimecmp(HartId::H2); + + assert_eq!(mtimecmp0.get_ptr() as usize, 0x0200_4000); + assert_eq!(mtimecmp1.get_ptr() as usize, 0x0200_4000 + 8); // 8 bytes per register + assert_eq!(mtimecmp2.get_ptr() as usize, 0x0200_4000 + 2 * 8); + + assert_eq!(CLINT::mtime(), mtimer.mtime); + assert_eq!(CLINT::mtimecmp0(), mtimer.mtimecmp(HartId::H0)); + assert_eq!(CLINT::mtimecmp1(), mtimer.mtimecmp(HartId::H1)); + assert_eq!(CLINT::mtimecmp2(), mtimer.mtimecmp(HartId::H2)); + + assert_eq!(CLINT::msip0(), mswi.msip(HartId::H0)); + assert_eq!(CLINT::msip1(), mswi.msip(HartId::H1)); + assert_eq!(CLINT::msip2(), mswi.msip(HartId::H2)); + } +} diff --git a/riscv-peripheral/src/aclint/mswi.rs b/riscv-peripheral/src/aclint/mswi.rs new file mode 100644 index 00000000..4e85121a --- /dev/null +++ b/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(&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); + } + } +} diff --git a/riscv-peripheral/src/aclint/mtimer.rs b/riscv-peripheral/src/aclint/mtimer.rs new file mode 100644 index 00000000..5e5e1c1c --- /dev/null +++ b/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(&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 _ + ); + } +} diff --git a/riscv-peripheral/src/aclint/sswi.rs b/riscv-peripheral/src/aclint/sswi.rs new file mode 100644 index 00000000..51072d66 --- /dev/null +++ b/riscv-peripheral/src/aclint/sswi.rs @@ -0,0 +1,118 @@ +//! Supervisor-level Software Interrupt Device. + +pub use super::HartIdNumber; +use crate::common::unsafe_peripheral; + +/// SSWI peripheral. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[repr(transparent)] +pub struct SSWI { + /// `SETSSIP` register for HART ID 0. In multi-HART architectures, + /// use [`SSWI::setssip`] for accessing the `SETSSIP` of other HARTs. + pub setssip0: SETSSIP, +} + +impl SSWI { + /// Creates a new `SSWI` peripheral from a base address. + /// + /// # Safety + /// + /// The base address must point to a valid `SSWI` peripheral. + #[inline] + pub const unsafe fn new(address: usize) -> Self { + Self { + setssip0: SETSSIP::new(address), + } + } + + /// Returns `true` if a supervisor software interrupt is pending. + #[inline] + pub fn is_interrupting() -> bool { + riscv::register::sip::read().ssoft() + } + + /// Returns `true` if Supervisor Software Interrupts are enabled. + #[inline] + pub fn is_enabled() -> bool { + riscv::register::mie::read().ssoft() + } + + /// Sets the Supervisor Software Interrupt bit of the `mie` CSR. + /// This bit must be set for the `SSWI` to trigger supervisor software interrupts. + /// + /// # Safety + /// + /// Enabling the `SSWI` may break mask-based critical sections. + #[inline] + pub unsafe fn enable() { + riscv::register::mie::set_ssoft(); + } + + /// Clears the Supervisor Software Interrupt bit of the `mie` CSR. + /// When cleared, the `SSWI` cannot trigger supervisor software interrupts. + #[inline] + pub fn disable() { + // SAFETY: it is safe to disable interrupts + unsafe { riscv::register::mie::clear_ssoft() }; + } + + /// Returns the `SETSSIP` register for the HART which ID is `hart_id`. + /// + /// # Note + /// + /// For HART ID 0, you can simply use [`SSWI::setssip0`]. + #[inline] + pub fn setssip(&self, hart_id: H) -> SETSSIP { + // SAFETY: `hart_id` is valid for the target + unsafe { SETSSIP::new(self.setssip0.get_ptr().offset(hart_id.number() as _) as _) } + } +} + +unsafe_peripheral!(SETSSIP, u32, RW); + +impl SETSSIP { + /// Returns `true` if a supervisor software interrupt is pending. + #[inline] + pub fn is_pending(self) -> bool { + self.register.read() != 0 + } + + /// Writes to the register to trigger a supervisor software interrupt. + #[inline] + pub fn pend(self) { + self.register.write(1); + } + + /// Clears the register to unpend a supervisor software interrupt. + #[inline] + pub fn unpend(self) { + self.register.write(0); + } +} + +#[cfg(test)] +mod test { + use super::super::test::HartId; + use super::*; + + #[test] + fn test_sswi() { + // slice to emulate the interrupt pendings register + let raw_reg = [0u32; HartId::MAX_HART_ID_NUMBER as usize + 1]; + // SAFETY: valid memory address + let mswi = unsafe { SSWI::new(raw_reg.as_ptr() as _) }; + + for i in 0..=HartId::MAX_HART_ID_NUMBER { + let hart_id = HartId::from_number(i).unwrap(); + let setssip = mswi.setssip(hart_id); + assert!(!setssip.is_pending()); + assert_eq!(raw_reg[i as usize], 0); + setssip.pend(); + assert!(setssip.is_pending()); + assert_ne!(raw_reg[i as usize], 0); + setssip.unpend(); + assert!(!setssip.is_pending()); + assert_eq!(raw_reg[i as usize], 0); + } + } +} diff --git a/riscv-peripheral/src/common.rs b/riscv-peripheral/src/common.rs new file mode 100644 index 00000000..ceca166c --- /dev/null +++ b/riscv-peripheral/src/common.rs @@ -0,0 +1,407 @@ +//! Common definitions for all the peripheral registers. + +/// Read-only type state for `A` in [`Reg`]. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct RO; + +/// Write-only type state for `A` in [`Reg`]. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct WO; + +/// Read-write type state for `A` in [`Reg`]. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct RW; + +/// Generic trait for all the peripheral registers. +/// This trait is sealed and cannot be implemented by any external crate. +pub trait Access: sealed::Access + Copy {} +impl Access for RO {} +impl Access for WO {} +impl Access for RW {} + +/// Trait for readable registers. +pub trait Read: Access {} +impl Read for RO {} +impl Read for RW {} + +/// Trait for writable registers. +pub trait Write: Access {} +impl Write for WO {} +impl Write for RW {} + +/// Generic register structure. `T` refers to the data type of the register. +/// Alternatively, `A` corresponds to the access level (e.g., read-only, read-write...). +/// +/// # Note +/// +/// This structure assumes that it points to a valid peripheral register. +/// If so, it is safe to read from or write to the register. +/// However, keep in mind that read-modify-write operations may lead to **wrong** behavior. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[repr(transparent)] +pub struct Reg { + ptr: *mut T, + phantom: core::marker::PhantomData, +} + +unsafe impl Send for Reg {} +unsafe impl Sync for Reg {} + +impl Reg { + /// Creates a new register from a pointer. + /// + /// # Safety + /// + /// The pointer must be valid and must be correctly aligned. + #[inline] + pub const unsafe fn new(ptr: *mut T) -> Self { + Self { + ptr, + phantom: core::marker::PhantomData, + } + } + + /// Returns a pointer to the register. + #[inline] + pub const fn get_ptr(self) -> *mut T { + self.ptr + } +} + +impl Reg { + /// Performs a volatile read of the peripheral register with no side effects. + /// + /// # Note + /// + /// If you want to perform a read-modify-write operation, use [`Reg::modify`] instead. + #[inline] + pub fn read(self) -> T { + // SAFETY: valid address and register is readable + unsafe { self.ptr.read_volatile() } + } +} + +impl Reg { + /// Performs a volatile write of the peripheral register. + /// + /// # Note + /// + /// If you want to perform a read-modify-write operation, use [`Reg::modify`] instead. + #[inline] + pub fn write(self, val: T) { + // SAFETY: valid address and register is writable + unsafe { self.ptr.write_volatile(val) } + } +} + +impl Reg { + /// It modifies the value of the register according to a given function `f`. + /// After writing the new value to the register, it returns the value returned by `f`. + /// + /// # Note + /// + /// It performs a non-atomic read-modify-write operation, which may lead to **wrong** behavior. + #[inline] + pub fn modify(self, f: impl FnOnce(&mut T) -> R) -> R { + let mut val = self.read(); + let res = f(&mut val); + self.write(val); + res + } +} + +/// Macro to provide bit-wise operations to integer number registers. +macro_rules! bitwise_reg { + ($TYPE: ty) => { + impl Reg<$TYPE, A> { + /// Reads the `n`th bit of the register. + #[inline] + pub fn read_bit(self, n: usize) -> bool { + let mask = 1 << n; + let val = self.read(); + val & mask == mask + } + + /// Reads a range of bits of the register specified by the `start` and `end` indexes, both included. + #[inline] + pub fn read_bits(self, start: usize, end: usize) -> $TYPE { + let n_bits = end - start + 1; + let mask = ((1 << n_bits) - 1) << start; + let val = self.read(); + (val & mask) >> start + } + } + + impl Reg<$TYPE, A> { + /// Clears the `n`th bit of the register. + /// + /// # Note + /// + /// It performs a non-atomic read-modify-write operation, which may lead to **wrong** behavior. + #[inline] + pub fn clear_bit(self, n: usize) { + self.modify(|val| *val &= !(1 << n)); + } + + /// Sets the nth bit of the register. + /// + /// # Note + /// + /// It performs a non-atomic read-modify-write operation, which may lead to **wrong** behavior. + #[inline] + pub fn set_bit(self, n: usize) { + self.modify(|val| *val |= 1 << n); + } + + /// Writes a range of bits of the register specified by the `start` and `end` indexes, both included. + #[inline] + pub fn write_bits(self, start: usize, end: usize, val: $TYPE) { + let n_bits = end - start + 1; + let mask = ((1 << n_bits) - 1) << start; + self.modify(|v| *v = (*v & !mask) | ((val << start) & mask)); + } + } + }; +} +bitwise_reg!(u8); +bitwise_reg!(u16); +bitwise_reg!(u32); +bitwise_reg!(u64); +bitwise_reg!(u128); +bitwise_reg!(usize); +bitwise_reg!(i8); +bitwise_reg!(i16); +bitwise_reg!(i32); +bitwise_reg!(i64); +bitwise_reg!(i128); +bitwise_reg!(isize); + +/// Macro to provide atomic bit-wise operations to integer number registers. +macro_rules! bitwise_atomic_reg { + ($TYPE: ty, $ATOMIC: ty) => { + impl Reg<$TYPE, A> { + /// Creates a new atomic reference to the register. + /// + /// # Safety + /// + /// * Register must be properly aligned **for atomic operations**. + /// * The register must not be accessed through non-atomic operations for the whole lifetime `'a`. + pub unsafe fn as_atomic<'a>(&self) -> &'a $ATOMIC { + // SAFETY: guaranteed by the caller + unsafe { &*self.ptr.cast() } + } + + /// Clears the `n`th bit of the register atomically. + /// + /// # Safety + /// + /// * Register must be properly aligned **for atomic operations**. + /// * The register must not be accessed through non-atomic operations until this function returns. + #[inline] + pub unsafe fn atomic_clear_bit(&self, n: usize, order: core::sync::atomic::Ordering) { + // SAFETY: guaranteed by the caller + unsafe { self.as_atomic() }.fetch_and(!(1 << n), order); + } + + /// Sets the `n`th bit of the register atomically. + /// + /// # Safety + /// + /// * Register must be properly aligned **for atomic operations**. + /// * The register must not be accessed through non-atomic operations until this function returns. + #[inline] + pub unsafe fn atomic_set_bit(&self, n: usize, order: core::sync::atomic::Ordering) { + // SAFETY: guaranteed by the caller + unsafe { self.as_atomic() }.fetch_or(1 << n, order); + } + } + }; +} + +#[cfg(target_has_atomic = "8")] +bitwise_atomic_reg!(u8, core::sync::atomic::AtomicU8); +#[cfg(target_has_atomic = "16")] +bitwise_atomic_reg!(u16, core::sync::atomic::AtomicU16); +#[cfg(target_has_atomic = "32")] +bitwise_atomic_reg!(u32, core::sync::atomic::AtomicU32); +#[cfg(target_has_atomic = "64")] +bitwise_atomic_reg!(u64, core::sync::atomic::AtomicU64); +#[cfg(target_has_atomic = "ptr")] +bitwise_atomic_reg!(usize, core::sync::atomic::AtomicUsize); +#[cfg(target_has_atomic = "8")] +bitwise_atomic_reg!(i8, core::sync::atomic::AtomicI8); +#[cfg(target_has_atomic = "16")] +bitwise_atomic_reg!(i16, core::sync::atomic::AtomicI16); +#[cfg(target_has_atomic = "32")] +bitwise_atomic_reg!(i32, core::sync::atomic::AtomicI32); +#[cfg(target_has_atomic = "64")] +bitwise_atomic_reg!(i64, core::sync::atomic::AtomicI64); +#[cfg(target_has_atomic = "ptr")] +bitwise_atomic_reg!(isize, core::sync::atomic::AtomicIsize); + +/// Macro to define the archetypal behavior of registers. +macro_rules! peripheral { + ($REGISTER: ident, $TYPE: ty, $ACCESS: ident) => { + /// Peripheral register. + #[derive(Clone, Copy, Debug, Eq, PartialEq)] + #[repr(transparent)] + pub struct $REGISTER { + register: $crate::common::Reg<$TYPE, $crate::common::$ACCESS>, + } + + impl $REGISTER { + /// Creates a new register from an address. + /// + /// # Safety + /// + /// The address assigned must be valid and must be correctly aligned. + #[inline] + pub const unsafe fn new(address: usize) -> Self { + Self { + register: $crate::common::Reg::new(address as _), + } + } + } + }; + ($REGISTER: ident, $TYPE: ty, $ACCESS: ident, $GENERIC: ident) => { + /// Peripheral register. + #[derive(Clone, Copy, Debug, Eq, PartialEq)] + #[repr(transparent)] + pub struct $REGISTER<$GENERIC> { + register: $crate::common::Reg<$TYPE, $crate::common::$ACCESS>, + _marker: core::marker::PhantomData<$GENERIC>, + } + + impl<$GENERIC> $REGISTER<$GENERIC> { + /// Creates a new register from an address. + /// + /// # Safety + /// + /// The address assigned must be valid and must be correctly aligned. + #[inline] + pub const unsafe fn new(address: usize) -> Self { + Self { + register: $crate::common::Reg::new(address as _), + _marker: core::marker::PhantomData, + } + } + } + }; +} + +/// Macro to define the archetypal behavior of *safe* registers. +/// You must specify the register name, its data type, and its access level. +/// +/// # Note +/// +/// Safe peripheral registers implement [`core::ops::Deref`] to [`Reg`]. +/// You can safely use the dereferenced [`Reg::read`], [`Reg::write`], and/or [`Reg::modify`] methods. +macro_rules! safe_peripheral { + ($REGISTER: ident, $TYPE: ty, $ACCESS: ident) => { + $crate::common::peripheral!($REGISTER, $TYPE, $ACCESS); + + impl $REGISTER { + /// Returns the underlying raw register. + #[inline] + pub const fn get_register(self) -> $crate::common::Reg<$TYPE, $crate::common::$ACCESS> { + self.register + } + } + + impl core::ops::Deref for $REGISTER { + type Target = $crate::common::Reg<$TYPE, $crate::common::$ACCESS>; + + fn deref(&self) -> &Self::Target { + &self.register + } + } + }; + ($REGISTER: ident, $TYPE: ty, $ACCESS: ident, $GENERIC: ident) => { + $crate::common::peripheral!($REGISTER, $TYPE, $ACCESS, $GENERIC); + + impl $REGISTER { + /// Returns the underlying raw register. + #[inline] + pub const fn get_register(self) -> $crate::common::Reg<$TYPE, $crate::common::$ACCESS> { + self.register + } + } + + impl<$GENERIC> core::ops::Deref for $REGISTER<$GENERIC> { + type Target = $crate::common::Reg<$TYPE, $crate::common::$ACCESS>; + + fn deref(&self) -> &Self::Target { + &self.register + } + } + }; +} + +/// Macro to define the archetypal behavior of *unsafe* registers. +/// You must specify the register name, its data type, and its access level. +/// +/// # Note +/// +/// Unsafe peripheral registers need special care when reading and/or writing. +/// They usually provide additional methods to perform safe (or unsafe) operations. +/// Nevertheless, you can still access the underlying register using the `unsafe get_register(self)` method. +macro_rules! unsafe_peripheral { + ($REGISTER: ident, $TYPE: ty, $ACCESS: ident) => { + $crate::common::peripheral!($REGISTER, $TYPE, $ACCESS); + + impl $REGISTER { + /// Returns a raw pointer to the register. + #[inline] + pub const fn get_ptr(self) -> *mut $TYPE { + self.register.get_ptr() + } + + /// Returns the underlying raw register. + /// + /// # Safety + /// + /// This register is not supposed to be used directly. + /// Use the other provided methods instead. Otherwise, use this method at your own risk. + #[inline] + pub const unsafe fn get_register( + self, + ) -> $crate::common::Reg<$TYPE, $crate::common::$ACCESS> { + self.register + } + } + }; + ($REGISTER: ident, $TYPE: ty, $ACCESS: ident, $GENERIC: ident) => { + $crate::common::peripheral!($REGISTER, $TYPE, $ACCESS, $GENERIC); + + impl<$GENERIC> $REGISTER<$GENERIC> { + /// Returns a raw pointer to the register. + #[inline] + pub const fn get_ptr(self) -> *mut $TYPE { + self.register.get_ptr() + } + + /// Returns the underlying register. + /// + /// # Safety + /// + /// This register is not supposed to be used directly. + /// Use the other provided methods instead. Otherwise, use this method at your own risk. + #[inline] + pub const unsafe fn get_register( + self, + ) -> $crate::common::Reg<$TYPE, $crate::common::$ACCESS> { + self.register + } + } + }; +} + +pub(crate) use {peripheral, safe_peripheral, unsafe_peripheral}; + +mod sealed { + use super::*; + pub trait Access {} + impl Access for RO {} + impl Access for WO {} + impl Access for RW {} +} diff --git a/riscv-peripheral/src/hal.rs b/riscv-peripheral/src/hal.rs new file mode 100644 index 00000000..8d16ffe0 --- /dev/null +++ b/riscv-peripheral/src/hal.rs @@ -0,0 +1,5 @@ +//! trait implementations for embedded-hal + +pub use embedded_hal::*; // re-export embedded-hal to allow macros to use it + +pub mod aclint; // ACLINT and CLINT peripherals diff --git a/riscv-peripheral/src/hal/aclint.rs b/riscv-peripheral/src/hal/aclint.rs new file mode 100644 index 00000000..143fe852 --- /dev/null +++ b/riscv-peripheral/src/hal/aclint.rs @@ -0,0 +1,46 @@ +//! Delay trait implementation for (A)CLINT peripherals + +use crate::aclint::mtimer::MTIME; +pub use crate::hal::delay::DelayNs; + +/// Delay implementation for (A)CLINT peripherals. +pub struct Delay { + mtime: MTIME, + freq: usize, +} + +impl Delay { + /// Creates a new `Delay` instance. + #[inline] + pub const fn new(mtime: MTIME, freq: usize) -> Self { + Self { mtime, freq } + } + + /// Returns the frequency of the `MTIME` register. + #[inline] + pub const fn get_freq(&self) -> usize { + self.freq + } + + /// Sets the frequency of the `MTIME` register. + #[inline] + pub fn set_freq(&mut self, freq: usize) { + self.freq = freq; + } + + /// Returns the `MTIME` register. + #[inline] + pub const fn get_mtime(&self) -> MTIME { + self.mtime + } +} + +impl DelayNs for Delay { + #[inline] + fn delay_ns(&mut self, ns: u32) { + let t0 = self.mtime.read(); + let ns_64: u64 = ns.into(); + let n_ticks = ns_64 * self.freq as u64 / 1_000_000_000; + while self.mtime.read().wrapping_sub(t0) < n_ticks {} + } +} diff --git a/riscv-peripheral/src/hal_async.rs b/riscv-peripheral/src/hal_async.rs new file mode 100644 index 00000000..fdb657e9 --- /dev/null +++ b/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 diff --git a/riscv-peripheral/src/hal_async/aclint.rs b/riscv-peripheral/src/hal_async/aclint.rs new file mode 100644 index 00000000..c7cd7ab7 --- /dev/null +++ b/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`: +//! 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; +} + +/// 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 { + 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 { + 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(()) + } + } +} diff --git a/riscv-peripheral/src/lib.rs b/riscv-peripheral/src/lib.rs new file mode 100644 index 00000000..f582f249 --- /dev/null +++ b/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 diff --git a/riscv-peripheral/src/macros.rs b/riscv-peripheral/src/macros.rs new file mode 100644 index 00000000..f4c6e8b4 --- /dev/null +++ b/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 { +/// if number > Self::MAX_HART_ID_NUMBER { +/// Err(number) +/// } else { +/// // SAFETY: valid context number +/// Ok(unsafe { core::mem::transmute(number) }) +/// } +/// } +/// } +/// +/// clint_codegen!( +/// base 0x0200_0000, +/// mtimecmps [mtimecmp0 = (HartId::H0, "`H0`"), mtimecmp1 = (HartId::H1, "`H1`"), mtimecmp2 = (HartId::H2, "`H2`")], +/// msips [msip0=(HartId::H0,"`H0`"), msip1=(HartId::H1,"`H1`"), msip2=(HartId::H2,"`H2`")], // do not forget the ending comma! +/// ); +/// +/// let mswi = CLINT::mswi(); // MSWI peripheral +/// let mtimer = CLINT::mtimer(); // MTIMER peripheral +/// +/// let mtimecmp0 = CLINT::mtimecmp0(); // mtimecmp register for HART 0 +/// let mtimecmp1 = CLINT::mtimecmp1(); // mtimecmp register for HART 1 +/// let mtimecmp2 = CLINT::mtimecmp2(); // mtimecmp register for HART 2 +/// +/// let msip0 = CLINT::msip0(); // msip register for HART 0 +/// let msip1 = CLINT::msip1(); // msip register for HART 1 +/// let msip2 = CLINT::msip2(); // msip register for HART 2 +/// ``` +#[macro_export] +macro_rules! clint_codegen { + () => { + #[allow(unused_imports)] + use CLINT as _; // assert that the CLINT struct is defined + }; + (base $addr:literal, $($tail:tt)*) => { + /// CLINT peripheral + #[allow(clippy::upper_case_acronyms)] + #[derive(Clone, Copy, Debug, Eq, PartialEq)] + pub struct CLINT; + + unsafe impl $crate::aclint::Clint for CLINT { + const BASE: usize = $addr; + } + + impl CLINT { + /// Returns `true` if a machine timer **OR** software interrupt is pending. + #[inline] + pub fn is_interrupting() -> bool { + Self::mswi_is_interrupting() || Self::mtimer_is_interrupting() + } + + /// Returns `true` if machine timer **OR** software interrupts are enabled. + pub fn is_enabled() -> bool { + Self::mswi_is_enabled() || Self::mtimer_is_enabled() + } + + /// Enables machine timer **AND** software interrupts to allow the CLINT to trigger interrupts. + /// + /// # Safety + /// + /// Enabling the `CLINT` may break mask-based critical sections. + #[inline] + pub unsafe fn enable() { + Self::mswi_enable(); + Self::mtimer_enable(); + } + + /// Disables machine timer **AND** software interrupts to prevent the CLINT from triggering interrupts. + #[inline] + pub fn disable() { + Self::mswi_disable(); + Self::mtimer_disable(); + } + + /// Returns `true` if a machine software interrupt is pending. + #[inline] + pub fn mswi_is_interrupting() -> bool { + $crate::riscv::register::mip::read().msoft() + } + + /// Returns `true` if Machine Software Interrupts are enabled. + #[inline] + pub fn mswi_is_enabled() -> bool { + $crate::riscv::register::mie::read().msoft() + } + + /// Enables the `MSWI` peripheral. + /// + /// # Safety + /// + /// Enabling the `MSWI` may break mask-based critical sections. + #[inline] + pub unsafe fn mswi_enable() { + $crate::riscv::register::mie::set_msoft(); + } + + /// Disables the `MSWI` peripheral. + #[inline] + pub fn mswi_disable() { + // SAFETY: it is safe to disable interrupts + unsafe { $crate::riscv::register::mie::clear_msoft() }; + } + + /// Returns the `MSWI` peripheral. + #[inline] + pub const fn mswi() -> $crate::aclint::mswi::MSWI { + $crate::aclint::CLINT::::mswi() + } + + /// Returns `true` if a machine timer interrupt is pending. + #[inline] + pub fn mtimer_is_interrupting() -> bool { + $crate::riscv::register::mip::read().mtimer() + } + + /// Returns `true` if Machine Timer Interrupts are enabled. + #[inline] + pub fn mtimer_is_enabled() -> bool { + $crate::riscv::register::mie::read().mtimer() + } + + /// Sets the Machine Timer Interrupt bit of the `mie` CSR. + /// This bit must be set for the `MTIMER` to trigger machine timer interrupts. + /// + /// # Safety + /// + /// Enabling the `MTIMER` may break mask-based critical sections. + #[inline] + pub unsafe fn mtimer_enable() { + $crate::riscv::register::mie::set_mtimer(); + } + + /// Clears the Machine Timer Interrupt bit of the `mie` CSR. + /// When cleared, the `MTIMER` cannot trigger machine timer interrupts. + #[inline] + pub fn mtimer_disable() { + // SAFETY: it is safe to disable interrupts + unsafe { $crate::riscv::register::mie::clear_mtimer() }; + } + + /// Returns the `MTIMER` peripheral. + #[inline] + pub const fn mtimer() -> $crate::aclint::mtimer::MTIMER { + $crate::aclint::CLINT::::mtimer() + } + + /// Returns the `MTIME` register of the `MTIMER` peripheral. + #[inline] + pub const fn mtime() -> $crate::aclint::mtimer::MTIME { + Self::mtimer().mtime + } + } + $crate::clint_codegen!($($tail)*); + }; + (freq $freq:literal, $($tail:tt)*) => { + impl CLINT { + /// Returns the frequency of the `MTIME` register. + #[inline] + pub const fn freq() -> usize { + $freq + } + + /// Delay implementation for CLINT peripherals. + /// + /// # Note + /// + /// You must export the `riscv_peripheral::hal::delay::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::::priorities() + } + + /// Returns the pendings register of the PLIC. + #[inline] + pub fn pendings() -> $crate::plic::pendings::PENDINGS { + $crate::plic::PLIC::::pendings() + } + + /// Returns the context proxy of a given PLIC HART context. + #[inline] + pub fn ctx(hart_id: H) -> $crate::plic::CTX { + $crate::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 { + $crate::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::ctx($ctx) + } + )* + } + $crate::plic_codegen!($($tail)*); + }; +} diff --git a/riscv-peripheral/src/plic.rs b/riscv-peripheral/src/plic.rs new file mode 100644 index 00000000..0619b0f7 --- /dev/null +++ b/riscv-peripheral/src/plic.rs @@ -0,0 +1,311 @@ +//! Platform-Level Interrupt Controller (PLIC) peripheral. +//! +//! Specification: + +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 { + _marker: core::marker::PhantomData

, +} + +impl PLIC

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

{ + // 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

{ + 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 { + context: usize, + _marker: core::marker::PhantomData

, +} + +impl CTX

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