From 50c6460cda9478c491d2487d8b442c3a6826dd80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rom=C3=A1n=20C=C3=A1rdenas=20Rodr=C3=ADguez?= Date: Tue, 22 Aug 2023 20:54:46 +0200 Subject: [PATCH 01/25] Initial commit --- .gitignore | 14 ++++++++++++++ README.md | 2 ++ 2 files changed, 16 insertions(+) create mode 100644 .gitignore create mode 100644 README.md diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..6985cf1b --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb diff --git a/README.md b/README.md new file mode 100644 index 00000000..e752500e --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# riscv-peripheral +Standard RISC-V targets for embedded systems written in Rust From 197f2fcd718c66fd1002136b3827cfa84c54696b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rom=C3=A1n=20C=C3=A1rdenas?= Date: Wed, 6 Sep 2023 12:21:15 +0200 Subject: [PATCH 02/25] First version of PLIC and (A)CLINT --- .gitignore | 3 + Cargo.toml | 16 ++ README.md | 10 +- src/aclint.rs | 83 +++++++++ src/aclint/mswi.rs | 77 ++++++++ src/aclint/mtimer.rs | 63 +++++++ src/aclint/sswi.rs | 77 ++++++++ src/common.rs | 407 +++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 9 + src/macros.rs | 1 + src/plic.rs | 338 ++++++++++++++++++++++++++++++++++ src/plic/claim.rs | 49 +++++ src/plic/enables.rs | 232 +++++++++++++++++++++++ src/plic/pendings.rs | 57 ++++++ src/plic/priorities.rs | 91 +++++++++ src/plic/threshold.rs | 55 ++++++ 16 files changed, 1566 insertions(+), 2 deletions(-) create mode 100644 Cargo.toml create mode 100644 src/aclint.rs create mode 100644 src/aclint/mswi.rs create mode 100644 src/aclint/mtimer.rs create mode 100644 src/aclint/sswi.rs create mode 100644 src/common.rs create mode 100644 src/lib.rs create mode 100644 src/macros.rs create mode 100644 src/plic.rs create mode 100644 src/plic/claim.rs create mode 100644 src/plic/enables.rs create mode 100644 src/plic/pendings.rs create mode 100644 src/plic/priorities.rs create mode 100644 src/plic/threshold.rs diff --git a/.gitignore b/.gitignore index 6985cf1b..6cfa27d8 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,6 @@ Cargo.lock # MSVC Windows builds of rustc generate these, which store debugging information *.pdb + +.DS_Store +.vscode/ diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 00000000..d6a2f938 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "riscv-peripheral" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +riscv = { git = "https://github.com/rust-embedded/riscv", branch = "master" } + +[package.metadata.docs.rs] +default-target = "riscv64imac-unknown-none-elf" +targets = [ + "riscv32i-unknown-none-elf", "riscv32imc-unknown-none-elf", "riscv32imac-unknown-none-elf", + "riscv64imac-unknown-none-elf", "riscv64gc-unknown-none-elf", +] diff --git a/README.md b/README.md index e752500e..c371abd9 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,8 @@ -# riscv-peripheral -Standard RISC-V targets for embedded systems written in Rust +# `riscv-peripheral` + +> Standard RISC-V targets for embedded systems written in Rust + +## Minimum Supported Rust Version (MSRV) + +This crate is guaranteed to compile on stable Rust 1.61 and up. It *might* +compile with older versions but that may change in any new patch release. diff --git a/src/aclint.rs b/src/aclint.rs new file mode 100644 index 00000000..8506f6ad --- /dev/null +++ b/src/aclint.rs @@ -0,0 +1,83 @@ +//! Devices for the Core Local Interruptor (CLINT) and Advanced CLINT (ACLINT) peripherals. +//! +//! CLINT pecification: +//! ACLINT Specification: + +pub mod mswi; +pub mod mtimer; +pub mod sswi; + +/// Trait for enums of HART IDs in (A)CLINT peripherals. +/// +/// # Note +/// +/// If your target only has one HART (HART ID 0), you don't need to implement this trait. +/// Instead, you can access directly to the base registers through the `(A)CLINT` structs. +/// +/// # Safety +/// +/// * This trait must only be implemented on a PAC of a target with a PLIC peripheral. +/// * This trait must only be implemented on enums of HART IDs. +/// * Each enum variant must represent a distinct value (no duplicates are permitted). +/// * Each enum variant must always return the same value (do not change at runtime). +/// * All the HART ID numbers must be less than or equal to `MAX_HART_ID_NUMBER`. +/// * `MAX_HART_ID_NUMBER` must coincide with the highest allowed HART ID number. +pub unsafe trait HartIdNumber: Copy { + /// Highest number assigned to a HART ID. + const MAX_HART_ID_NUMBER: u16; + + /// Converts a HART Id to its corresponding number. + fn number(self) -> u16; + + /// Tries to convert a number to a valid HART ID. + /// If the conversion fails, it returns an error with the number back. + fn from_number(value: u16) -> Result; +} + +/// Trait for a CLINT peripheral. +/// +/// # Safety +/// +/// * This trait must only be implemented on a PAC of a target with a CLINT peripheral. +/// * The CLINT peripheral base address `BASE` must be valid for the target device. +pub unsafe trait Clint: Copy { + 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. + pub const fn mswi() -> mswi::MSWI { + // SAFETY: valid base address + unsafe { mswi::MSWI::new(C::BASE) } + } + + /// Returns the `MTIMER` peripheral. + pub const fn mtimer() -> mtimer::MTIMER { + // SAFETY: valid base address + unsafe { + mtimer::MTIMER::new( + C::BASE + Self::MTIMECMP_OFFSET, + C::BASE + Self::MTIME_OFFSET, + ) + } + } +} diff --git a/src/aclint/mswi.rs b/src/aclint/mswi.rs new file mode 100644 index 00000000..ca19443a --- /dev/null +++ b/src/aclint/mswi.rs @@ -0,0 +1,77 @@ +pub use super::HartIdNumber; +use crate::common::unsafe_peripheral; + +/// Machine-level Software Interrupt Device. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[repr(transparent)] +pub struct MSWI { + /// `MSIP` register for HART ID 0. In multi-HART architectures, + /// use [`MSWI::msip`] for accessing the `MSIP` of other HARTs. + pub msip0: MSIP, +} + +impl MSWI { + /// Creates a new `MSWI` peripheral from a base address. + /// + /// # Safety + /// + /// The base address must point to a valid `MSWI` peripheral. + #[inline] + pub const unsafe fn new(address: usize) -> Self { + Self { + msip0: MSIP::new(address), + } + } + + /// Sets the Machine Software Interrupt bit of the `mie` CSR. + /// This bit must be set for the `MSWI` to trigger machine software interrupts. + /// + /// # Safety + /// + /// Enabling the `MSWI` may break mask-based critical sections. + #[inline] + pub unsafe fn enable() { + riscv::register::mie::set_msoft(); + } + + /// Clears the Machine Software Interrupt bit of the `mie` CSR. + /// When cleared, the `MSWI` cannot trigger machine software interrupts. + #[inline] + pub fn disable() { + // SAFETY: it is safe to disable interrupts + unsafe { riscv::register::mie::clear_msoft() }; + } + + /// Returns the `MSIP` register for the HART which ID is `hart_id`. + /// + /// # Note + /// + /// For HART ID 0, you can simply use [`MSWI::msip0`]. + #[inline] + pub fn msip(&self, hart_id: H) -> MSIP { + // SAFETY: `hart_id` is valid for the target + unsafe { MSIP::new(self.msip0.get_ptr().offset(hart_id.number() as _) as _) } + } +} + +unsafe_peripheral!(MSIP, u32, RW); + +impl MSIP { + /// Returns `true` if a machine software interrupt is pending. + #[inline] + pub fn is_pending(self) -> bool { + self.register.read() != 0 + } + + /// Writes to the register to trigger a machine software interrupt. + #[inline] + pub fn pend(self) { + self.register.write(1); + } + + /// Clears the register to unpend a machine software interrupt. + #[inline] + pub fn unpend(self) { + self.register.write(0); + } +} diff --git a/src/aclint/mtimer.rs b/src/aclint/mtimer.rs new file mode 100644 index 00000000..a4a20db6 --- /dev/null +++ b/src/aclint/mtimer.rs @@ -0,0 +1,63 @@ +pub use super::HartIdNumber; +use crate::common::safe_peripheral; + +/// Machine-level Timer Device. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct MTIMER { + /// `MTIMECMP` register for HART ID 0. In multi-HART architectures, + /// use [`MTIMER::mtimecmp`] for accessing the `MTIMECMP` of other HARTs. + pub mtimecmp0: MTIMECMP, + /// The `MTIME` register is shared among all the HARTs. + pub mtime: MTIME, +} + +impl MTIMER { + /// Creates a new `MTIMER` peripheral from a base address. + /// + /// # Safety + /// + /// The base address must point to a valid `MTIMER` peripheral. + #[inline] + pub const unsafe fn new(mtimecmp: usize, mtime: usize) -> Self { + Self { + mtimecmp0: MTIMECMP::new(mtimecmp), + mtime: MTIME::new(mtime), + } + } + + /// Sets the Machine Timer Interrupt bit of the `mie` CSR. + /// This bit must be set for the `MTIMER` to trigger machine timer interrupts. + /// + /// # Safety + /// + /// Enabling the `MTIMER` may break mask-based critical sections. + #[inline] + pub unsafe fn enable() { + riscv::register::mie::set_mtimer(); + } + + /// Clears the Machine Timer Interrupt bit of the `mie` CSR. + /// When cleared, the `MTIMER` cannot trigger machine timer interrupts. + #[inline] + pub fn disable() { + // SAFETY: it is safe to disable interrupts + unsafe { riscv::register::mie::clear_mtimer() }; + } + + /// Returns the `MTIME` register for the HART which ID is `hart_id`. + /// + /// # Note + /// + /// For HART ID 0, you can simply use [`MTIMER::mtimecmp0`]. + #[inline] + pub fn mtimecmp(&self, hart_id: H) -> MTIMECMP { + // SAFETY: `hart_id` is valid for the target + unsafe { MTIMECMP::new(self.mtimecmp0.get_ptr().offset(hart_id.number() as _) as _) } + } +} + +// MTIMECMP register. +safe_peripheral!(MTIMECMP, u64, RW); + +// MTIME register. +safe_peripheral!(MTIME, u64, RW); diff --git a/src/aclint/sswi.rs b/src/aclint/sswi.rs new file mode 100644 index 00000000..14b457e6 --- /dev/null +++ b/src/aclint/sswi.rs @@ -0,0 +1,77 @@ +pub use super::HartIdNumber; +use crate::common::unsafe_peripheral; + +/// Supervisor-level Software Interrupt Device. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[repr(transparent)] +pub struct SSWI { + /// `SETSSIP` register for HART ID 0. In multi-HART architectures, + /// use [`SSWI::setssip`] for accessing the `SETSSIP` of other HARTs. + pub setssip0: SETSSIP, +} + +impl SSWI { + /// Creates a new `SSWI` peripheral from a base address. + /// + /// # Safety + /// + /// The base address must point to a valid `SSWI` peripheral. + #[inline] + pub const unsafe fn new(address: usize) -> Self { + Self { + setssip0: SETSSIP::new(address), + } + } + + /// Sets the Supervisor Software Interrupt bit of the `mie` CSR. + /// This bit must be set for the `SSWI` to trigger supervisor software interrupts. + /// + /// # Safety + /// + /// Enabling the `SSWI` may break mask-based critical sections. + #[inline] + pub unsafe fn enable() { + riscv::register::mie::set_ssoft(); + } + + /// Clears the Supervisor Software Interrupt bit of the `mie` CSR. + /// When cleared, the `SSWI` cannot trigger supervisor software interrupts. + #[inline] + pub fn disable() { + // SAFETY: it is safe to disable interrupts + unsafe { riscv::register::mie::clear_ssoft() }; + } + + /// Returns the `SETSSIP` register for the HART which ID is `hart_id`. + /// + /// # Note + /// + /// For HART ID 0, you can simply use [`SSWI::setssip0`]. + #[inline] + pub fn setssip(&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); + } +} diff --git a/src/common.rs b/src/common.rs new file mode 100644 index 00000000..ceca166c --- /dev/null +++ b/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/src/lib.rs b/src/lib.rs new file mode 100644 index 00000000..c78b19a3 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,9 @@ +#![no_std] + +pub use riscv; // re-export riscv crate to allow users to use it without importing it + +pub mod common; +pub mod macros; // macros for easing the definition of peripherals in PACs + +pub mod aclint; +pub mod plic; diff --git a/src/macros.rs b/src/macros.rs new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/src/macros.rs @@ -0,0 +1 @@ + diff --git a/src/plic.rs b/src/plic.rs new file mode 100644 index 00000000..1f46dc2d --- /dev/null +++ b/src/plic.rs @@ -0,0 +1,338 @@ +//! Platform-Level Interrupt Controller (PLIC) peripheral. +//! +//! Specification: + +pub mod claim; +pub mod enables; +pub mod pendings; +pub mod priorities; +pub mod threshold; + +/// Trait for enums of interrupt numbers. +/// +/// This trait should be implemented by a peripheral access crate (PAC) +/// on its enum of available external interrupts for a specific device. +/// Each variant must convert to a `u16` of its interrupt number. +/// +/// # Note +/// +/// Recall that the interrupt number `0` is reserved as "no interrupt". +/// +/// # Safety +/// +/// * This trait must only be implemented on a PAC of a target with a PLIC peripheral. +/// * This trait must only be implemented on enums of external interrupts. +/// * Each enum variant must represent a distinct value (no duplicates are permitted), +/// * Each enum variant must always return the same value (do not change at runtime). +/// * All the interrupt numbers must be less than or equal to `MAX_INTERRUPT_NUMBER`. +/// * `MAX_INTERRUPT_NUMBER` must coincide with the highest allowed interrupt number. +pub unsafe trait InterruptNumber: Copy { + /// Highest number assigned to an interrupt source. + const MAX_INTERRUPT_NUMBER: u16; + + /// Converts an interrupt source to its corresponding number. + fn number(self) -> u16; + + /// Tries to convert a number to a valid interrupt source. + /// If the conversion fails, it returns an error with the number back. + fn from_number(value: u16) -> Result; +} + +/// Trait for enums of priority levels. +/// +/// This trait should be implemented by a peripheral access crate (PAC) +/// on its enum of available priority numbers for a specific device. +/// Each variant must convert to a `u8` of its priority level. +/// +/// # Note +/// +/// Recall that the priority number `0` is reserved as "never interrupt". +/// +/// # Safety +/// +/// * This trait must only be implemented on a PAC of a target with a PLIC peripheral. +/// * This trait must only be implemented on enums of priority levels. +/// * Each enum variant must represent a distinct value (no duplicates are permitted). +/// * Each enum variant must always return the same value (do not change at runtime). +/// * There must be a valid priority number set to 0 (i.e., never interrupt). +/// * All the priority level numbers must be less than or equal to `MAX_PRIORITY_NUMBER`. +/// * `MAX_PRIORITY_NUMBER` must coincide with the highest allowed priority number. +pub unsafe trait PriorityNumber: Copy { + /// Number assigned to the highest priority level. + const MAX_PRIORITY_NUMBER: u8; + + /// Converts a priority level to its corresponding number. + fn number(self) -> u8; + + /// Tries to convert a number to a valid priority level. + /// If the conversion fails, it returns an error with the number back. + fn from_number(value: u8) -> Result; +} + +/// Trait for enums of PLIC contexts. +/// +/// This trait should be implemented by a peripheral access crate (PAC) +/// on its enum of available contexts for a specific device. +/// Each variant must convert to a `u16` of its context number. +/// +/// # Safety +/// +/// * This trait must only be implemented on a PAC of a target with a PLIC peripheral. +/// * This trait must only be implemented on enums of contexts. +/// * Each enum variant must represent a distinct value (no duplicates are permitted), +/// * Each anum variant must always return the same value (do not change at runtime). +/// * All the context numbers must be less than or equal to `MAX_CONTEXT_NUMBER`. +/// * `MAX_CONTEXT_NUMBER` must coincide with the highest allowed context number. +pub unsafe trait ContextNumber: Copy { + /// Highest number assigned to a context. + const MAX_CONTEXT_NUMBER: u16; + + /// Converts an context to its corresponding number. + fn number(self) -> u16; + + /// Tries to convert a number to a valid context. + /// If the conversion fails, it returns an error with the number back. + fn from_number(value: u16) -> Result; +} + +/// Trait for a PLIC peripheral. +/// +/// # Safety +/// +/// * This trait must only be implemented on a PAC of a target with a PLIC peripheral. +/// * The PLIC peripheral base address `BASE` must be valid for the target device. +pub unsafe trait Plic: Copy { + /// Base address of the PLIC peripheral. + const BASE: usize; +} + +/// Platform-Level Interrupt Controler (PLIC) peripheral. +/// +/// The RISC-V standard does not specify a fixed location for the PLIC. +/// Thus, each platform must specify the base address of the PLIC on the platform. +/// The base address, as well as all the associated types, are defined in the [`Plic`] trait. +/// +/// The PLIC standard allows up to 15_872 different contexts for interfacing the PLIC. +/// Each context has an assigned index starting from 0 to up to 15_871. +/// Usually, each HART uses a dedicated context. In this way, they do not interfere +/// with each other when attending to external interruptions. +#[allow(clippy::upper_case_acronyms)] +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] +pub struct PLIC { + _marker: core::marker::PhantomData

, +} + +impl PLIC

{ + const PRIORITIES_OFFSET: usize = 0; + + const PENDINGS_OFFSET: usize = 0x1000; + + const ENABLES_OFFSET: usize = 0x2000; + const ENABLES_SEPARATION: usize = 0x80; + + const THRESHOLDS_OFFSET: usize = 0x20_0000; + const THRESHOLDS_SEPARATION: usize = 0x1000; + + const CLAIMS_OFFSET: usize = 0x20_0004; + const CLAIMS_SEPARATION: usize = 0x1000; + + /// Sets the Machine External Interrupt bit of the `mie` CSR. + /// This bit must be set for the PLIC to trigger machine external interrupts. + /// + /// # Safety + /// + /// Enabling the `PLIC` may break mask-based critical sections. + #[inline] + pub unsafe fn enable() { + riscv::register::mie::set_mext(); + } + + /// Clears the Machine External Interrupt bit of the `mie` CSR. + /// When cleared, the PLIC does not trigger machine external interrupts. + #[inline] + pub fn disable() { + // SAFETY: it is safe to disable interrupts + unsafe { riscv::register::mie::clear_mext() }; + } + + /// Returns the priorities register of the PLIC. + /// This register allows to set the priority level of each interrupt source. + /// The priority level of each interrupt source is shared among all the contexts. + #[inline] + pub fn priorities() -> priorities::PRIORITIES { + unsafe { priorities::PRIORITIES::new(P::BASE + Self::PRIORITIES_OFFSET) } + } + + /// Returns the pendings register of the PLIC. + /// This register allows to check if an interrupt source is pending. + /// This register is shared among all the contexts. + #[inline] + pub fn pendings() -> pendings::PENDINGS { + unsafe { pendings::PENDINGS::new(P::BASE + Self::PENDINGS_OFFSET) } + } + + /// Returns the interrupt enable register assigned to a given context. + /// This register allows to enable/disable interrupt sources for a given context. + /// Each context has its own enable register. + #[inline] + pub fn enables(context: C) -> enables::ENABLES { + let context = context.number() as usize; + let addr = P::BASE + Self::ENABLES_OFFSET + context * Self::ENABLES_SEPARATION; + // SAFETY: context is a valid index + unsafe { enables::ENABLES::new(addr) } + } + + /// Returns the interrupt threshold register assigned to a given context. + /// This register allows to set the priority threshold level for a given context. + /// Each context has its own threshold register. + #[inline] + pub fn threshold(context: C) -> threshold::THRESHOLD { + let context = context.number() as usize; + let addr = P::BASE + Self::THRESHOLDS_OFFSET + context * Self::THRESHOLDS_SEPARATION; + // SAFETY: context is a valid index + unsafe { threshold::THRESHOLD::new(addr) } + } + + /// Returns the interrupt claim/complete register assigned to a given context. + /// This register allows to claim and complete interrupts for a given context. + /// Each context has its own claim/complete register. + #[inline] + pub fn claim(context: C) -> claim::CLAIM { + let context = context.number() as usize; + let addr = P::BASE + Self::CLAIMS_OFFSET + context * Self::CLAIMS_SEPARATION; + // SAFETY: context is a valid index + unsafe { claim::CLAIM::new(addr) } + } +} + +#[cfg(test)] +pub(self) mod test { + use super::*; + + #[derive(Clone, Copy, Debug, Eq, PartialEq)] + #[repr(u16)] + pub(super) enum Interrupt { + I1 = 1, + I2 = 2, + I3 = 3, + I4 = 4, + } + + #[derive(Clone, Copy, Debug, Eq, PartialEq)] + #[repr(u8)] + pub(super) enum Priority { + P0 = 0, + P1 = 1, + P2 = 2, + P3 = 3, + } + + #[derive(Clone, Copy, Debug, Eq, PartialEq)] + #[repr(u16)] + pub(super) enum Context { + C0 = 0, + C1 = 1, + C2 = 2, + } + + unsafe impl InterruptNumber for Interrupt { + const MAX_INTERRUPT_NUMBER: u16 = 4; + + #[inline] + fn number(self) -> u16 { + self as _ + } + + #[inline] + fn from_number(number: u16) -> Result { + if number > Self::MAX_INTERRUPT_NUMBER || number == 0 { + Err(number) + } else { + // SAFETY: valid interrupt number + Ok(unsafe { core::mem::transmute(number) }) + } + } + } + + unsafe impl PriorityNumber for Priority { + const MAX_PRIORITY_NUMBER: u8 = 3; + + #[inline] + fn number(self) -> u8 { + self as _ + } + + #[inline] + fn from_number(number: u8) -> Result { + if number > Self::MAX_PRIORITY_NUMBER { + Err(number) + } else { + // SAFETY: valid priority number + Ok(unsafe { core::mem::transmute(number) }) + } + } + } + + unsafe impl ContextNumber for Context { + const MAX_CONTEXT_NUMBER: u16 = 2; + + #[inline] + fn number(self) -> u16 { + self as _ + } + + #[inline] + fn from_number(number: u16) -> Result { + if number > Self::MAX_CONTEXT_NUMBER { + Err(number) + } else { + // SAFETY: valid context number + Ok(unsafe { core::mem::transmute(number) }) + } + } + } + + #[test] + fn check_interrupt_enum() { + assert_eq!(Interrupt::I1.number(), 1); + assert_eq!(Interrupt::I2.number(), 2); + assert_eq!(Interrupt::I3.number(), 3); + assert_eq!(Interrupt::I4.number(), 4); + + assert_eq!(Interrupt::from_number(1), Ok(Interrupt::I1)); + assert_eq!(Interrupt::from_number(2), Ok(Interrupt::I2)); + assert_eq!(Interrupt::from_number(3), Ok(Interrupt::I3)); + assert_eq!(Interrupt::from_number(4), Ok(Interrupt::I4)); + + assert_eq!(Interrupt::from_number(0), Err(0)); + assert_eq!(Interrupt::from_number(5), Err(5)); + } + + #[test] + fn check_priority_enum() { + assert_eq!(Priority::P0.number(), 0); + assert_eq!(Priority::P1.number(), 1); + assert_eq!(Priority::P2.number(), 2); + assert_eq!(Priority::P3.number(), 3); + + assert_eq!(Priority::from_number(0), Ok(Priority::P0)); + assert_eq!(Priority::from_number(1), Ok(Priority::P1)); + assert_eq!(Priority::from_number(2), Ok(Priority::P2)); + assert_eq!(Priority::from_number(3), Ok(Priority::P3)); + + assert_eq!(Priority::from_number(4), Err(4)); + } + + #[test] + fn check_context_enum() { + assert_eq!(Context::C0.number(), 0); + assert_eq!(Context::C1.number(), 1); + assert_eq!(Context::C2.number(), 2); + + assert_eq!(Context::from_number(0), Ok(Context::C0)); + assert_eq!(Context::from_number(1), Ok(Context::C1)); + assert_eq!(Context::from_number(2), Ok(Context::C2)); + + assert_eq!(Context::from_number(3), Err(3)); + } +} diff --git a/src/plic/claim.rs b/src/plic/claim.rs new file mode 100644 index 00000000..dbf5b378 --- /dev/null +++ b/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/src/plic/enables.rs b/src/plic/enables.rs new file mode 100644 index 00000000..7ed48869 --- /dev/null +++ b/src/plic/enables.rs @@ -0,0 +1,232 @@ +//! Interrupt enables register of a PLIC context. + +use crate::{ + common::{Reg, RW}, + plic::InterruptNumber, +}; + +/// Enables register of a PLIC context. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[repr(transparent)] +pub struct ENABLES { + ptr: *mut u32, +} + +impl ENABLES { + /// Creates a new Interrupts enables register from a base address. + /// + /// # Safety + /// + /// The base address must point to a valid Interrupts enables register. + #[inline] + pub(crate) const unsafe fn new(address: usize) -> Self { + Self { ptr: address as _ } + } + + /// Checks if an interrupt source is enabled for the PLIC context. + #[inline] + pub fn is_enabled(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/src/plic/pendings.rs b/src/plic/pendings.rs new file mode 100644 index 00000000..47abb9dd --- /dev/null +++ b/src/plic/pendings.rs @@ -0,0 +1,57 @@ +//! Interrupt pending bits register. + +use crate::{ + common::{Reg, RO}, + plic::InterruptNumber, +}; + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[repr(transparent)] +pub struct PENDINGS { + ptr: *mut u32, +} + +impl PENDINGS { + /// Creates a new Interrupts pending bits register from a base address. + /// + /// # Safety + /// + /// The base address must point to a valid Interrupts pending bits register. + #[inline] + pub(crate) const unsafe fn new(address: usize) -> Self { + Self { ptr: address as _ } + } + + /// Checks if an interrupt triggered by a given source is pending. + #[inline] + pub fn is_pending(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/src/plic/priorities.rs b/src/plic/priorities.rs new file mode 100644 index 00000000..9b8ad06b --- /dev/null +++ b/src/plic/priorities.rs @@ -0,0 +1,91 @@ +//! Interrupts Priorities register. + +use crate::{ + common::{Reg, RW}, + plic::{InterruptNumber, PriorityNumber}, +}; + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[repr(transparent)] +pub struct PRIORITIES { + ptr: *mut u32, +} + +impl PRIORITIES { + /// Creates a new Interrupts priorities register from a base address. + /// + /// # Safety + /// + /// The base address must point to a valid Interrupts priorities register. + #[inline] + pub(crate) const unsafe fn new(address: usize) -> Self { + Self { ptr: address as _ } + } + + /// Returns the priority assigned to a given interrupt source. + #[inline] + pub fn get_priority(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/src/plic/threshold.rs b/src/plic/threshold.rs new file mode 100644 index 00000000..936a13b1 --- /dev/null +++ b/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); + } +} From 42f56420b2e47fdf12d281026b22ae2d75dbbfdf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rom=C3=A1n=20C=C3=A1rdenas?= Date: Thu, 7 Sep 2023 17:01:43 +0200 Subject: [PATCH 03/25] Macro for creating CLINT peripherals --- src/aclint.rs | 47 ++++++++++++++++ src/macros.rs | 147 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/plic.rs | 8 +-- 3 files changed, 198 insertions(+), 4 deletions(-) diff --git a/src/aclint.rs b/src/aclint.rs index 8506f6ad..d889964a 100644 --- a/src/aclint.rs +++ b/src/aclint.rs @@ -65,12 +65,14 @@ impl CLINT { 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 { @@ -81,3 +83,48 @@ impl CLINT { } } } + +#[cfg(test)] +pub(crate) mod test { + use super::*; + + #[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)); + } +} diff --git a/src/macros.rs b/src/macros.rs index 8b137891..56ee495c 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -1 +1,148 @@ +/// 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 2 different argument types: +/// +/// - Base address (**MANDATORY**): base address of the CLINT peripheral of the target. +/// - Per-HART mtimecmp registers (**OPTIONAL**): a list of `mtimecmp` registers for easing access to per-HART mtimecmp 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;); +/// +/// let mswi = CLINT::mswi(); // MSWI peripheral +/// let mtimer = CLINT::mtimer(); // MTIMER peripheral +/// ``` +/// +/// ## 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; +/// mtimecmp mtimecmp0 = HartId::H0; +/// mtimecmp mtimecmp1 = HartId::H1; +/// mtimecmp mtimecmp2 = HartId::H2; +/// ); +/// +/// 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 +/// ``` +#[macro_export] +macro_rules! clint_codegen { + () => {}; + (base $addr:literal; $($tail:tt)*) => { + /// CLINT peripheral + #[derive(Clone, Copy, Debug, Eq, PartialEq)] + pub struct CLINT; + unsafe impl $crate::aclint::Clint for CLINT { + const BASE: usize = $addr; + } + + impl CLINT { + /// Returns the `MSWI` peripheral. + #[inline] + pub const fn mswi() -> $crate::aclint::mswi::MSWI { + $crate::aclint::CLINT::::mswi() + } + + /// Returns the `MTIMER` peripheral. + #[inline] + pub const fn mtimer() -> $crate::aclint::mtimer::MTIMER { + $crate::aclint::CLINT::::mtimer() + } + } + + clint_codegen!($($tail)*); + }; + (mtimecmp $fn:ident = $hart:expr; $($tail:tt)*) => { + impl CLINT { + /// Returns the `MTIMECMP` register for the given HART. + #[inline] + pub fn $fn() -> $crate::aclint::mtimer::MTIMECMP { + Self::mtimer().mtimecmp($hart) + } + } + + clint_codegen!($($tail)*); + }; +} + +#[cfg(test)] +mod tests { + use crate::aclint::test::HartId; + + #[test] + fn test_clint_mtimecmp() { + // Call CLINT macro with a base address and a list of mtimecmps for easing access to per-HART mtimecmp regs. + clint_codegen!(base 0x0200_0000; + mtimecmp mtimecmp0 = HartId::H0; + mtimecmp mtimecmp1 = HartId::H1; + mtimecmp mtimecmp2 = HartId::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 + 1 * 8); // 8 bytes per register + assert_eq!(mtimecmp2.get_ptr() as usize, 0x0200_4000 + 2 * 8); + + // Check that the mtimecmpX functions are equivalent to the mtimer.mtimecmp(X) function. + let mtimecmp0 = CLINT::mtimecmp0(); + let mtimecmp1 = CLINT::mtimecmp1(); + let mtimecmp2 = CLINT::mtimecmp2(); + + assert_eq!( + mtimecmp0.get_ptr() as usize, + mtimer.mtimecmp(HartId::H0).get_ptr() as usize + ); + assert_eq!( + mtimecmp1.get_ptr() as usize, + mtimer.mtimecmp(HartId::H1).get_ptr() as usize + ); + assert_eq!( + mtimecmp2.get_ptr() as usize, + mtimer.mtimecmp(HartId::H2).get_ptr() as usize + ); + } +} diff --git a/src/plic.rs b/src/plic.rs index 1f46dc2d..c83247c7 100644 --- a/src/plic.rs +++ b/src/plic.rs @@ -206,12 +206,12 @@ impl PLIC

{ } #[cfg(test)] -pub(self) mod test { +pub(crate) mod test { use super::*; #[derive(Clone, Copy, Debug, Eq, PartialEq)] #[repr(u16)] - pub(super) enum Interrupt { + pub(crate) enum Interrupt { I1 = 1, I2 = 2, I3 = 3, @@ -220,7 +220,7 @@ pub(self) mod test { #[derive(Clone, Copy, Debug, Eq, PartialEq)] #[repr(u8)] - pub(super) enum Priority { + pub(crate) enum Priority { P0 = 0, P1 = 1, P2 = 2, @@ -229,7 +229,7 @@ pub(self) mod test { #[derive(Clone, Copy, Debug, Eq, PartialEq)] #[repr(u16)] - pub(super) enum Context { + pub(crate) enum Context { C0 = 0, C1 = 1, C2 = 2, From de55f4ff55d36812944a4be49f3f7f33e8b5df48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rom=C3=A1n=20C=C3=A1rdenas?= Date: Fri, 8 Sep 2023 13:36:49 +0200 Subject: [PATCH 04/25] Improve CLINT codegen macro --- src/macros.rs | 43 ++++++++++++++++++++++--------------------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/src/macros.rs b/src/macros.rs index 56ee495c..a341c353 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -15,7 +15,7 @@ /// ``` /// use riscv_peripheral::clint_codegen; /// -/// clint_codegen!(base 0x0200_0000;); +/// clint_codegen!(base 0x0200_0000,); // do not forget the ending comma! /// /// let mswi = CLINT::mswi(); // MSWI peripheral /// let mtimer = CLINT::mtimer(); // MTIMER peripheral @@ -45,10 +45,9 @@ /// } /// } /// -/// clint_codegen!(base 0x0200_0000; -/// mtimecmp mtimecmp0 = HartId::H0; -/// mtimecmp mtimecmp1 = HartId::H1; -/// mtimecmp mtimecmp2 = HartId::H2; +/// clint_codegen!( +/// base 0x0200_0000, +/// mtimecmps [mtimecmp0 = HartId::H0, mtimecmp1 = HartId::H1, mtimecmp2 = HartId::H2], // do not forget the ending comma! /// ); /// /// let mswi = CLINT::mswi(); // MSWI peripheral @@ -60,8 +59,11 @@ /// ``` #[macro_export] macro_rules! clint_codegen { - () => {}; - (base $addr:literal; $($tail:tt)*) => { + () => { + #[allow(unused_imports)] + use CLINT as _; // assert that the CLINT struct is defined + }; + (base $addr:literal, $($tail:tt)*) => { /// CLINT peripheral #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct CLINT; @@ -83,19 +85,19 @@ macro_rules! clint_codegen { $crate::aclint::CLINT::::mtimer() } } - - clint_codegen!($($tail)*); + $crate::clint_codegen!($($tail)*); }; - (mtimecmp $fn:ident = $hart:expr; $($tail:tt)*) => { + (mtimecmps [$($fn:ident = $hart:expr),+], $($tail:tt)*) => { impl CLINT { - /// Returns the `MTIMECMP` register for the given HART. - #[inline] - pub fn $fn() -> $crate::aclint::mtimer::MTIMECMP { - Self::mtimer().mtimecmp($hart) - } + $( + /// Returns the `MTIMECMP` register for the given HART. + #[inline] + pub fn $fn() -> $crate::aclint::mtimer::MTIMECMP { + Self::mtimer().mtimecmp($hart) + } + )* } - - clint_codegen!($($tail)*); + $crate::clint_codegen!($($tail)*); }; } @@ -106,10 +108,9 @@ mod tests { #[test] fn test_clint_mtimecmp() { // Call CLINT macro with a base address and a list of mtimecmps for easing access to per-HART mtimecmp regs. - clint_codegen!(base 0x0200_0000; - mtimecmp mtimecmp0 = HartId::H0; - mtimecmp mtimecmp1 = HartId::H1; - mtimecmp mtimecmp2 = HartId::H2; + clint_codegen!( + base 0x0200_0000, + mtimecmps [mtimecmp0=HartId::H0, mtimecmp1=HartId::H1, mtimecmp2=HartId::H2], ); let mswi = CLINT::mswi(); From 9067bad4b00a0601a8085bb3626bc519fcb339c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rom=C3=A1n=20C=C3=A1rdenas?= Date: Fri, 8 Sep 2023 17:11:48 +0200 Subject: [PATCH 05/25] PLIC macro --- src/aclint.rs | 44 ++++++++++++- src/macros.rs | 97 ++++++++++++++++------------ src/plic.rs | 143 ++++++++++++++++++++++++++++++++--------- src/plic/enables.rs | 6 ++ src/plic/pendings.rs | 6 ++ src/plic/priorities.rs | 6 ++ 6 files changed, 230 insertions(+), 72 deletions(-) diff --git a/src/aclint.rs b/src/aclint.rs index d889964a..64f993e7 100644 --- a/src/aclint.rs +++ b/src/aclint.rs @@ -86,7 +86,7 @@ impl CLINT { #[cfg(test)] pub(crate) mod test { - use super::*; + use super::HartIdNumber; #[derive(Clone, Copy, Debug, Eq, PartialEq)] #[repr(u16)] @@ -127,4 +127,46 @@ pub(crate) mod test { assert_eq!(HartId::from_number(3), Err(3)); } + + #[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, mtimecmp1=HartId::H1, mtimecmp2=HartId::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 + 1 * 8); // 8 bytes per register + assert_eq!(mtimecmp2.get_ptr() as usize, 0x0200_4000 + 2 * 8); + + // Check that the mtimecmpX functions are equivalent to the mtimer.mtimecmp(X) function. + let mtimecmp0 = CLINT::mtimecmp0(); + let mtimecmp1 = CLINT::mtimecmp1(); + let mtimecmp2 = CLINT::mtimecmp2(); + + assert_eq!( + mtimecmp0.get_ptr() as usize, + mtimer.mtimecmp(HartId::H0).get_ptr() as usize + ); + assert_eq!( + mtimecmp1.get_ptr() as usize, + mtimer.mtimecmp(HartId::H1).get_ptr() as usize + ); + assert_eq!( + mtimecmp2.get_ptr() as usize, + mtimer.mtimecmp(HartId::H2).get_ptr() as usize + ); + } } diff --git a/src/macros.rs b/src/macros.rs index a341c353..095e8c27 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -90,7 +90,6 @@ macro_rules! clint_codegen { (mtimecmps [$($fn:ident = $hart:expr),+], $($tail:tt)*) => { impl CLINT { $( - /// Returns the `MTIMECMP` register for the given HART. #[inline] pub fn $fn() -> $crate::aclint::mtimer::MTIMECMP { Self::mtimer().mtimecmp($hart) @@ -101,49 +100,67 @@ macro_rules! clint_codegen { }; } -#[cfg(test)] -mod tests { - use crate::aclint::test::HartId; - - #[test] - fn test_clint_mtimecmp() { - // Call CLINT macro with a base address and a list of mtimecmps for easing access to per-HART mtimecmp regs. - clint_codegen!( - base 0x0200_0000, - mtimecmps [mtimecmp0=HartId::H0, mtimecmp1=HartId::H1, mtimecmp2=HartId::H2], - ); +#[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 + #[derive(Clone, Copy, Debug, Eq, PartialEq)] + pub struct PLIC; - let mswi = CLINT::mswi(); - let mtimer = CLINT::mtimer(); + unsafe impl $crate::plic::Plic for PLIC { + const BASE: usize = $addr; + } - 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); + impl PLIC { + /// 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::plic::PLIC::::enable(); + } - let mtimecmp0 = mtimer.mtimecmp(HartId::H0); - let mtimecmp1 = mtimer.mtimecmp(HartId::H1); - let mtimecmp2 = mtimer.mtimecmp(HartId::H2); + /// Disables machine external interrupts to prevent the PLIC from triggering interrupts. + #[inline] + pub fn disable() { + $crate::plic::PLIC::::disable(); + } - assert_eq!(mtimecmp0.get_ptr() as usize, 0x0200_4000); - assert_eq!(mtimecmp1.get_ptr() as usize, 0x0200_4000 + 1 * 8); // 8 bytes per register - assert_eq!(mtimecmp2.get_ptr() as usize, 0x0200_4000 + 2 * 8); + /// Returns the priorities register of the PLIC. + #[inline] + pub fn priorities() -> $crate::plic::priorities::PRIORITIES { + $crate::plic::PLIC::::priorities() + } - // Check that the mtimecmpX functions are equivalent to the mtimer.mtimecmp(X) function. - let mtimecmp0 = CLINT::mtimecmp0(); - let mtimecmp1 = CLINT::mtimecmp1(); - let mtimecmp2 = CLINT::mtimecmp2(); + /// Returns the pendings register of the PLIC. + #[inline] + pub fn pendings() -> $crate::plic::pendings::PENDINGS { + $crate::plic::PLIC::::pendings() + } - assert_eq!( - mtimecmp0.get_ptr() as usize, - mtimer.mtimecmp(HartId::H0).get_ptr() as usize - ); - assert_eq!( - mtimecmp1.get_ptr() as usize, - mtimer.mtimecmp(HartId::H1).get_ptr() as usize - ); - assert_eq!( - mtimecmp2.get_ptr() as usize, - mtimer.mtimecmp(HartId::H2).get_ptr() as usize - ); - } + /// Returns the context proxy of a given PLIC context. + #[inline] + pub fn ctx(context: C) -> $crate::plic::CTX { + $crate::plic::PLIC::::ctx(context) + } + } + $crate::plic_codegen!($($tail)*); + }; + (ctxs [$($fn:ident = $ctx:expr),+], $($tail:tt)*) => { + impl PLIC { + $( + #[inline] + pub fn $fn() -> $crate::plic::CTX { + Self::ctx($ctx) + } + )* + } + $crate::plic_codegen!($($tail)*); + }; } diff --git a/src/plic.rs b/src/plic.rs index c83247c7..687c25e5 100644 --- a/src/plic.rs +++ b/src/plic.rs @@ -127,15 +127,6 @@ impl PLIC

{ const PENDINGS_OFFSET: usize = 0x1000; - const ENABLES_OFFSET: usize = 0x2000; - const ENABLES_SEPARATION: usize = 0x80; - - const THRESHOLDS_OFFSET: usize = 0x20_0000; - const THRESHOLDS_SEPARATION: usize = 0x1000; - - const CLAIMS_OFFSET: usize = 0x20_0004; - const CLAIMS_SEPARATION: usize = 0x1000; - /// Sets the Machine External Interrupt bit of the `mie` CSR. /// This bit must be set for the PLIC to trigger machine external interrupts. /// @@ -160,6 +151,7 @@ impl PLIC

{ /// 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) } } @@ -168,46 +160,84 @@ impl PLIC

{ /// This register is shared among all the contexts. #[inline] pub fn pendings() -> pendings::PENDINGS { + // SAFETY: valid address unsafe { pendings::PENDINGS::new(P::BASE + Self::PENDINGS_OFFSET) } } - /// Returns the interrupt enable register assigned to a given context. - /// This register allows to enable/disable interrupt sources for a given context. - /// Each context has its own enable register. + /// Returns the context proxy of a given context. + /// This proxy provides access to the PLIC registers of the given context. + #[inline] + pub fn ctx(context: C) -> CTX

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

, +} + +impl CTX

{ + const ENABLES_OFFSET: usize = 0x2000; + const ENABLES_SEPARATION: usize = 0x80; + + const THRESHOLDS_OFFSET: usize = 0x20_0000; + const THRESHOLDS_SEPARATION: usize = 0x1000; + + const CLAIMS_OFFSET: usize = 0x20_0004; + const CLAIMS_SEPARATION: usize = 0x1000; + + /// Creates a new PLIC context proxy + /// + /// # Safety + /// + /// The context number must be valid for the target device. + #[inline] + pub(crate) unsafe fn new(context: u16) -> Self { + Self { + context: context as _, + _marker: core::marker::PhantomData, + } + } + + /// Returns the context number of this proxy. + #[inline] + pub const fn context(self) -> u16 { + self.context as _ + } + + /// Returns the interrupts enable register of the context. #[inline] - pub fn enables(context: C) -> enables::ENABLES { - let context = context.number() as usize; - let addr = P::BASE + Self::ENABLES_OFFSET + context * Self::ENABLES_SEPARATION; - // SAFETY: context is a valid index + 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 assigned to a given context. - /// This register allows to set the priority threshold level for a given context. - /// Each context has its own threshold register. + /// Returns the interrupt threshold register of the context. #[inline] - pub fn threshold(context: C) -> threshold::THRESHOLD { - let context = context.number() as usize; - let addr = P::BASE + Self::THRESHOLDS_OFFSET + context * Self::THRESHOLDS_SEPARATION; - // SAFETY: context is a valid index + 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 assigned to a given context. - /// This register allows to claim and complete interrupts for a given context. - /// Each context has its own claim/complete register. + /// Returns the interrupt claim/complete register of the context. #[inline] - pub fn claim(context: C) -> claim::CLAIM { - let context = context.number() as usize; - let addr = P::BASE + Self::CLAIMS_OFFSET + context * Self::CLAIMS_SEPARATION; - // SAFETY: context is a valid index + 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::*; + use super::{ContextNumber, InterruptNumber, PriorityNumber}; #[derive(Clone, Copy, Debug, Eq, PartialEq)] #[repr(u16)] @@ -335,4 +365,55 @@ pub(crate) mod test { 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, ctx1 = Context::C1, ctx2 = Context::C2], + ); + + let priorities = PLIC::priorities(); + let pendings = PLIC::pendings(); + + assert_eq!(priorities.address(), 0x0C00_0000); + assert_eq!(pendings.address(), 0x0C00_1000); + + for i in 0..=Context::MAX_CONTEXT_NUMBER { + let context = Context::from_number(i).unwrap(); + let ctx = PLIC::ctx(context); + + assert_eq!( + ctx.enables().address(), + 0x0C00_0000 + 0x2000 + i as usize * 0x80 + ); + assert_eq!( + ctx.threshold().get_ptr() as usize, + 0x0C00_0000 + 0x20_0000 + i as usize * 0x1000 + ); + assert_eq!( + ctx.claim().get_ptr() as usize, + 0x0C00_0000 + 0x20_0004 + i as usize * 0x1000 + ); + } + + let ctx0 = PLIC::ctx0(); + let ctx_0_ = PLIC::ctx(Context::C0); + assert_eq!(ctx0.enables().address(), ctx_0_.enables().address()); + assert_eq!(ctx0.threshold().get_ptr(), ctx_0_.threshold().get_ptr()); + assert_eq!(ctx0.claim().get_ptr(), ctx_0_.claim().get_ptr()); + + let ctx1 = PLIC::ctx1(); + let ctx_1_ = PLIC::ctx(Context::C1); + assert_eq!(ctx1.enables().address(), ctx_1_.enables().address()); + assert_eq!(ctx1.threshold().get_ptr(), ctx_1_.threshold().get_ptr()); + assert_eq!(ctx1.claim().get_ptr(), ctx_1_.claim().get_ptr()); + + let ctx2 = PLIC::ctx2(); + let ctx_2_ = PLIC::ctx(Context::C2); + assert_eq!(ctx2.enables().address(), ctx_2_.enables().address()); + assert_eq!(ctx2.threshold().get_ptr(), ctx_2_.threshold().get_ptr()); + assert_eq!(ctx2.claim().get_ptr(), ctx_2_.claim().get_ptr()); + } } diff --git a/src/plic/enables.rs b/src/plic/enables.rs index 7ed48869..03ab9a35 100644 --- a/src/plic/enables.rs +++ b/src/plic/enables.rs @@ -23,6 +23,12 @@ impl ENABLES { 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 { diff --git a/src/plic/pendings.rs b/src/plic/pendings.rs index 47abb9dd..c9244d6a 100644 --- a/src/plic/pendings.rs +++ b/src/plic/pendings.rs @@ -22,6 +22,12 @@ impl PENDINGS { 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 { diff --git a/src/plic/priorities.rs b/src/plic/priorities.rs index 9b8ad06b..00abede1 100644 --- a/src/plic/priorities.rs +++ b/src/plic/priorities.rs @@ -22,6 +22,12 @@ impl PRIORITIES { 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 { From 1459e8faee5bc41153fbaa167a6f88d3b7b3dffc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rom=C3=A1n=20C=C3=A1rdenas=20Rodr=C3=ADguez?= Date: Tue, 19 Sep 2023 11:12:37 +0200 Subject: [PATCH 06/25] Create rust.yml Automatic CI action. Will improve it shortly --- .github/workflows/rust.yml | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 .github/workflows/rust.yml diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml new file mode 100644 index 00000000..31000a27 --- /dev/null +++ b/.github/workflows/rust.yml @@ -0,0 +1,22 @@ +name: Rust + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +env: + CARGO_TERM_COLOR: always + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - name: Build + run: cargo build --verbose + - name: Run tests + run: cargo test --verbose From 776aaac6919b8192a9f7c4636b95ac83a0091b1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rom=C3=A1n=20C=C3=A1rdenas=20Rodr=C3=ADguez?= Date: Thu, 21 Sep 2023 19:31:39 +0200 Subject: [PATCH 07/25] Update macros.rs bug in PLIC macro --- src/macros.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/macros.rs b/src/macros.rs index 095e8c27..a709256e 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -146,7 +146,7 @@ macro_rules! plic_codegen { /// Returns the context proxy of a given PLIC context. #[inline] - pub fn ctx(context: C) -> $crate::plic::CTX { + pub fn ctx(context: C) -> $crate::plic::CTX { $crate::plic::PLIC::::ctx(context) } } From 6d720363c6bd6d1a05313c9b0eaf145b58c384bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rom=C3=A1n=20C=C3=A1rdenas?= Date: Fri, 22 Sep 2023 09:39:36 +0200 Subject: [PATCH 08/25] modified macro for documentation --- src/aclint.rs | 2 +- src/macros.rs | 12 +++++++++--- src/plic.rs | 2 +- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/aclint.rs b/src/aclint.rs index 64f993e7..e5fbcf80 100644 --- a/src/aclint.rs +++ b/src/aclint.rs @@ -133,7 +133,7 @@ pub(crate) mod test { // 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, mtimecmp1=HartId::H1, mtimecmp2=HartId::H2], + mtimecmps [mtimecmp0=(HartId::H0,"`H0`"), mtimecmp1=(HartId::H1,"`H1`"), mtimecmp2=(HartId::H2,"`H2`")], ); let mswi = CLINT::mswi(); diff --git a/src/macros.rs b/src/macros.rs index a709256e..07ffc42f 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -47,7 +47,7 @@ /// /// clint_codegen!( /// base 0x0200_0000, -/// mtimecmps [mtimecmp0 = HartId::H0, mtimecmp1 = HartId::H1, mtimecmp2 = HartId::H2], // do not forget the ending comma! +/// mtimecmps [mtimecmp0 = (HartId::H0, "`H0`"), mtimecmp1 = (HartId::H1, "`H1`"), mtimecmp2 = (HartId::H2, "`H2`")], // do not forget the ending comma! /// ); /// /// let mswi = CLINT::mswi(); // MSWI peripheral @@ -87,9 +87,12 @@ macro_rules! clint_codegen { } $crate::clint_codegen!($($tail)*); }; - (mtimecmps [$($fn:ident = $hart:expr),+], $($tail:tt)*) => { + (mtimecmps [$($fn:ident = ($hart:expr , $shart:expr)),+], $($tail:tt)*) => { impl CLINT { $( + #[doc = "Returns the `mtimecmp` peripheral HART "] + #[doc = $shart] + #[doc = "."] #[inline] pub fn $fn() -> $crate::aclint::mtimer::MTIMECMP { Self::mtimer().mtimecmp($hart) @@ -152,9 +155,12 @@ macro_rules! plic_codegen { } $crate::plic_codegen!($($tail)*); }; - (ctxs [$($fn:ident = $ctx:expr),+], $($tail:tt)*) => { + (ctxs [$($fn:ident = ($ctx:expr , $sctx:expr)),+], $($tail:tt)*) => { impl PLIC { $( + #[doc = "Returns a PLIC context proxy for context "] + #[doc = $sctx] + #[doc = "."] #[inline] pub fn $fn() -> $crate::plic::CTX { Self::ctx($ctx) diff --git a/src/plic.rs b/src/plic.rs index 687c25e5..10fb8a4f 100644 --- a/src/plic.rs +++ b/src/plic.rs @@ -371,7 +371,7 @@ pub(crate) mod test { fn check_plic() { crate::plic_codegen!( base 0x0C00_0000, - ctxs [ctx0 = Context::C0, ctx1 = Context::C1, ctx2 = Context::C2], + ctxs [ctx0 = (Context::C0, "`C0`"), ctx1 = (Context::C1, "`C1`"), ctx2 = (Context::C2, "`C2`")], ); let priorities = PLIC::priorities(); From 74490102ac6c4becca5d8603010642c0e0f0ea30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rom=C3=A1n=20C=C3=A1rdenas?= Date: Fri, 22 Sep 2023 12:46:17 +0200 Subject: [PATCH 09/25] Docs and CI actions --- .github/workflows/clippy.yml | 40 +++++++++++++++++++++++++++++++++++ .github/workflows/rustfmt.yml | 18 ++++++++++++++++ README.md | 2 +- src/aclint.rs | 34 +++++++++++++++++++++++++++++ src/aclint/mswi.rs | 4 +++- src/aclint/mtimer.rs | 4 +++- src/aclint/sswi.rs | 4 +++- src/lib.rs | 3 +++ src/macros.rs | 35 ++++++++++++++++++++++++++++++ src/plic/pendings.rs | 1 + src/plic/priorities.rs | 1 + 11 files changed, 142 insertions(+), 4 deletions(-) create mode 100644 .github/workflows/clippy.yml create mode 100644 .github/workflows/rustfmt.yml diff --git a/.github/workflows/clippy.yml b/.github/workflows/clippy.yml new file mode 100644 index 00000000..dc54ca55 --- /dev/null +++ b/.github/workflows/clippy.yml @@ -0,0 +1,40 @@ +on: + push: + branches: [ main ] + pull_request: + merge_group: + +name: Lints compliance check + +env: + CLIPPY_PARAMS: -W clippy::all -W clippy::pedantic -W clippy::nursery -W clippy::cargo + +jobs: + clippy: + strategy: + matrix: + toolchain: [ stable, nightly ] + cargo_flags: [ ] + 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@v3 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ matrix.toolchain }} + components: clippy + - name: Run clippy + run: cargo clippy --all ${{ matrix.cargo_flags }} -- -D warnings + + # Job to check that all the lint checks succeeded + clippy-check: + needs: + - clippy + runs-on: ubuntu-latest + if: always() + steps: + - run: jq --exit-status 'all(.result == "success")' <<< '${{ toJson(needs) }}' \ No newline at end of file diff --git a/.github/workflows/rustfmt.yml b/.github/workflows/rustfmt.yml new file mode 100644 index 00000000..3d792dc7 --- /dev/null +++ b/.github/workflows/rustfmt.yml @@ -0,0 +1,18 @@ +on: + push: + branches: [ main ] + pull_request: + merge_group: + +name: Code formatting check + +jobs: + rustfmt: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt + - name: Run Rustfmt + run: cargo fmt --all -- --check --verbose diff --git a/README.md b/README.md index c371abd9..fffd8329 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # `riscv-peripheral` -> Standard RISC-V targets for embedded systems written in Rust +> Standard RISC-V peripherals for embedded systems written in Rust ## Minimum Supported Rust Version (MSRV) diff --git a/src/aclint.rs b/src/aclint.rs index e5fbcf80..34d1b243 100644 --- a/src/aclint.rs +++ b/src/aclint.rs @@ -41,6 +41,7 @@ pub unsafe trait HartIdNumber: Copy { /// * 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; } @@ -64,6 +65,38 @@ impl CLINT { const MTIME_OFFSET: usize = 0xBFF8; + /// Enables machine software interrupts to let the `MSWI` peripheral trigger interrupts. + /// + /// # Safety + /// + /// Enabling the `MSWI` may break mask-based critical sections. + #[inline] + pub unsafe fn enable_mswi() { + mswi::MSWI::enable(); + } + + /// Disables machine software interrupts to prevent the `MSWI` peripheral from triggering interrupts. + #[inline] + pub fn disable_mswi() { + mswi::MSWI::disable(); + } + + /// Enables machine timer interrupts to let the `MTIMER` peripheral trigger interrupts. + /// + /// # Safety + /// + /// Enabling the `MTIMER` may break mask-based critical sections. + #[inline] + pub unsafe fn enable_mtimer() { + mtimer::MTIMER::enable(); + } + + /// Disables machine timer interrupts to prevent the `MTIMER` peripheral from triggering interrupts. + #[inline] + pub fn disable_mtimer() { + mtimer::MTIMER::disable(); + } + /// Returns the `MSWI` peripheral. #[inline] pub const fn mswi() -> mswi::MSWI { @@ -128,6 +161,7 @@ pub(crate) mod test { 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. diff --git a/src/aclint/mswi.rs b/src/aclint/mswi.rs index ca19443a..885555ba 100644 --- a/src/aclint/mswi.rs +++ b/src/aclint/mswi.rs @@ -1,7 +1,9 @@ +//! Machine-level Software Interrupt Device. + pub use super::HartIdNumber; use crate::common::unsafe_peripheral; -/// Machine-level Software Interrupt Device. +/// MSWI peripheral. #[derive(Clone, Copy, Debug, Eq, PartialEq)] #[repr(transparent)] pub struct MSWI { diff --git a/src/aclint/mtimer.rs b/src/aclint/mtimer.rs index a4a20db6..750cf1bd 100644 --- a/src/aclint/mtimer.rs +++ b/src/aclint/mtimer.rs @@ -1,7 +1,9 @@ +//! Machine-level Timer Device. + pub use super::HartIdNumber; use crate::common::safe_peripheral; -/// Machine-level Timer Device. +/// MTIMER peripheral. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct MTIMER { /// `MTIMECMP` register for HART ID 0. In multi-HART architectures, diff --git a/src/aclint/sswi.rs b/src/aclint/sswi.rs index 14b457e6..f919de0a 100644 --- a/src/aclint/sswi.rs +++ b/src/aclint/sswi.rs @@ -1,7 +1,9 @@ +//! Supervisor-level Software Interrupt Device. + pub use super::HartIdNumber; use crate::common::unsafe_peripheral; -/// Supervisor-level Software Interrupt Device. +/// SSWI peripheral. #[derive(Clone, Copy, Debug, Eq, PartialEq)] #[repr(transparent)] pub struct SSWI { diff --git a/src/lib.rs b/src/lib.rs index c78b19a3..4c67b12b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,6 @@ +//! Standard RISC-V peripherals for embedded systems written in Rust + +#![deny(missing_docs)] #![no_std] pub use riscv; // re-export riscv crate to allow users to use it without importing it diff --git a/src/macros.rs b/src/macros.rs index 07ffc42f..65694bf1 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -1,3 +1,5 @@ +//! 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. /// @@ -73,6 +75,38 @@ macro_rules! clint_codegen { } impl CLINT { + /// Enables the `MSWI` peripheral. + /// + /// # Safety + /// + /// Enabling the `MSWI` may break mask-based critical sections. + #[inline] + pub unsafe fn enable_mswi() { + $crate::aclint::CLINT::::enable_mswi(); + } + + /// Disables the `MSWI` peripheral. + #[inline] + pub fn disable_mswi() { + $crate::aclint::CLINT::::disable_mswi(); + } + + /// Enables the `MTIMER` peripheral. + /// + /// # Safety + /// + /// Enabling the `MTIMER` may break mask-based critical sections. + #[inline] + pub unsafe fn enable_mtimer() { + $crate::aclint::CLINT::::enable_mtimer(); + } + + /// Disables the `MTIMER` peripheral. + #[inline] + pub fn disable_mtimer() { + $crate::aclint::CLINT::::disable_mtimer(); + } + /// Returns the `MSWI` peripheral. #[inline] pub const fn mswi() -> $crate::aclint::mswi::MSWI { @@ -103,6 +137,7 @@ macro_rules! clint_codegen { }; } +/// Macro to create interfaces to PLIC peripherals in PACs. #[macro_export] macro_rules! plic_codegen { () => { diff --git a/src/plic/pendings.rs b/src/plic/pendings.rs index c9244d6a..8185d9b8 100644 --- a/src/plic/pendings.rs +++ b/src/plic/pendings.rs @@ -5,6 +5,7 @@ use crate::{ plic::InterruptNumber, }; +/// Interrupts pending bits register. #[derive(Clone, Copy, Debug, Eq, PartialEq)] #[repr(transparent)] pub struct PENDINGS { diff --git a/src/plic/priorities.rs b/src/plic/priorities.rs index 00abede1..a9726838 100644 --- a/src/plic/priorities.rs +++ b/src/plic/priorities.rs @@ -5,6 +5,7 @@ use crate::{ plic::{InterruptNumber, PriorityNumber}, }; +/// Interrupts priorities register. #[derive(Clone, Copy, Debug, Eq, PartialEq)] #[repr(transparent)] pub struct PRIORITIES { From 1509de2f1277dfeac93f8cd047ea4a34bc93f15a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rom=C3=A1n=20C=C3=A1rdenas?= Date: Fri, 22 Sep 2023 12:48:23 +0200 Subject: [PATCH 10/25] Fix clippy action --- .github/workflows/clippy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/clippy.yml b/.github/workflows/clippy.yml index dc54ca55..c8c92a68 100644 --- a/.github/workflows/clippy.yml +++ b/.github/workflows/clippy.yml @@ -14,7 +14,7 @@ jobs: strategy: matrix: toolchain: [ stable, nightly ] - cargo_flags: [ ] + cargo_flags: [ --all-features, --no-default-features ] include: # Nightly is only for reference and allowed to fail - toolchain: nightly From 90a4035cd3c54a1685a00fb849715589aac49e9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rom=C3=A1n=20C=C3=A1rdenas?= Date: Sat, 23 Sep 2023 19:30:02 +0200 Subject: [PATCH 11/25] Added new functions --- src/aclint.rs | 54 +++++++++++++++++++++++++++++++++++--------- src/aclint/mswi.rs | 12 ++++++++++ src/aclint/mtimer.rs | 14 +++++++++++- src/aclint/sswi.rs | 12 ++++++++++ src/macros.rs | 14 ++++++------ src/plic.rs | 18 +++++++++++---- 6 files changed, 101 insertions(+), 23 deletions(-) diff --git a/src/aclint.rs b/src/aclint.rs index 34d1b243..6b5cfb5c 100644 --- a/src/aclint.rs +++ b/src/aclint.rs @@ -65,45 +65,77 @@ impl CLINT { const MTIME_OFFSET: usize = 0xBFF8; + /// Returns `true` if any CLINT-related interrupt is pending. + #[inline] + pub fn is_interrupting() -> bool { + Self::mswi_is_interrupting() || Self::mtimer_is_interrupting() + } + + /// Returns `true` if a machine software interrupt is pending. + #[inline] + pub fn mswi_is_interrupting() -> bool { + mswi::MSWI::is_interrupting() + } + + /// Returns `true` if Machine Software Interrupts are enabled. + /// This bit must be set for the `MSWI` to trigger machine software interrupts. + #[inline] + pub fn mswi_is_enabled() -> bool { + mswi::MSWI::is_enabled() + } + /// Enables machine software interrupts to let the `MSWI` peripheral trigger interrupts. /// /// # Safety /// /// Enabling the `MSWI` may break mask-based critical sections. #[inline] - pub unsafe fn enable_mswi() { + pub unsafe fn mswi_enable() { mswi::MSWI::enable(); } /// Disables machine software interrupts to prevent the `MSWI` peripheral from triggering interrupts. #[inline] - pub fn disable_mswi() { + pub fn mswi_disable() { mswi::MSWI::disable(); } + /// Returns the `MSWI` peripheral. + #[inline] + pub const fn mswi() -> mswi::MSWI { + // SAFETY: valid base address + unsafe { mswi::MSWI::new(C::BASE) } + } + + /// Returns `true` if a machine timer interrupt is pending. + #[inline] + pub fn mtimer_is_interrupting() -> bool { + mtimer::MTIMER::is_interrupting() + } + + /// Returns `true` if Machine Timer Interrupts are enabled. + /// This bit must be set for the `MTIMER` to trigger machine timer interrupts. + #[inline] + pub fn mtimer_is_enabled() -> bool { + mtimer::MTIMER::is_enabled() + } + /// Enables machine timer interrupts to let the `MTIMER` peripheral trigger interrupts. /// /// # Safety /// /// Enabling the `MTIMER` may break mask-based critical sections. #[inline] - pub unsafe fn enable_mtimer() { + pub unsafe fn mtimer_enable() { mtimer::MTIMER::enable(); } /// Disables machine timer interrupts to prevent the `MTIMER` peripheral from triggering interrupts. #[inline] - pub fn disable_mtimer() { + pub fn mtimer_disable() { mtimer::MTIMER::disable(); } - /// 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 { diff --git a/src/aclint/mswi.rs b/src/aclint/mswi.rs index 885555ba..ab799c12 100644 --- a/src/aclint/mswi.rs +++ b/src/aclint/mswi.rs @@ -25,6 +25,18 @@ impl MSWI { } } + /// Returns `true` if a machine software interrupt is pending. + #[inline] + pub fn is_interrupting() -> bool { + riscv::register::mip::read().msoft() + } + + /// Returns `true` if Machine Software Interrupts are enabled. + #[inline] + pub fn is_enabled() -> bool { + riscv::register::mie::read().msoft() + } + /// Sets the Machine Software Interrupt bit of the `mie` CSR. /// This bit must be set for the `MSWI` to trigger machine software interrupts. /// diff --git a/src/aclint/mtimer.rs b/src/aclint/mtimer.rs index 750cf1bd..2794cc50 100644 --- a/src/aclint/mtimer.rs +++ b/src/aclint/mtimer.rs @@ -27,6 +27,18 @@ impl MTIMER { } } + /// Returns `true` if a machine timer interrupt is pending. + #[inline] + pub fn is_interrupting() -> bool { + riscv::register::mip::read().mtimer() + } + + /// Returns `true` if Machine Timer Interrupts are enabled. + #[inline] + pub fn is_enabled() -> bool { + 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. /// @@ -46,7 +58,7 @@ impl MTIMER { unsafe { riscv::register::mie::clear_mtimer() }; } - /// Returns the `MTIME` register for the HART which ID is `hart_id`. + /// Returns the `MTIMECMP` register for the HART which ID is `hart_id`. /// /// # Note /// diff --git a/src/aclint/sswi.rs b/src/aclint/sswi.rs index f919de0a..e31c6cf1 100644 --- a/src/aclint/sswi.rs +++ b/src/aclint/sswi.rs @@ -25,6 +25,18 @@ impl SSWI { } } + /// 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. /// diff --git a/src/macros.rs b/src/macros.rs index 65694bf1..1aed3f0b 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -81,14 +81,14 @@ macro_rules! clint_codegen { /// /// Enabling the `MSWI` may break mask-based critical sections. #[inline] - pub unsafe fn enable_mswi() { - $crate::aclint::CLINT::::enable_mswi(); + pub unsafe fn mswi_enable() { + $crate::aclint::CLINT::::mswi_enable(); } /// Disables the `MSWI` peripheral. #[inline] - pub fn disable_mswi() { - $crate::aclint::CLINT::::disable_mswi(); + pub fn mswi_disable() { + $crate::aclint::CLINT::::mswi_disable(); } /// Enables the `MTIMER` peripheral. @@ -97,14 +97,14 @@ macro_rules! clint_codegen { /// /// Enabling the `MTIMER` may break mask-based critical sections. #[inline] - pub unsafe fn enable_mtimer() { - $crate::aclint::CLINT::::enable_mtimer(); + pub unsafe fn mtimer_enable() { + $crate::aclint::CLINT::::mtimer_enable(); } /// Disables the `MTIMER` peripheral. #[inline] pub fn disable_mtimer() { - $crate::aclint::CLINT::::disable_mtimer(); + $crate::aclint::CLINT::::mtimer_disable(); } /// Returns the `MSWI` peripheral. diff --git a/src/plic.rs b/src/plic.rs index 10fb8a4f..ef367e5c 100644 --- a/src/plic.rs +++ b/src/plic.rs @@ -127,6 +127,18 @@ impl PLIC

{ const PENDINGS_OFFSET: usize = 0x1000; + /// Returns `true` if a machine external interrupt is pending. + #[inline] + pub fn is_interrupting() -> bool { + riscv::register::mip::read().mext() + } + + /// Returns true if Machine External Interrupts are enabled. + #[inline] + pub fn is_enabled() -> bool { + riscv::register::mie::read().mext() + } + /// Sets the Machine External Interrupt bit of the `mie` CSR. /// This bit must be set for the PLIC to trigger machine external interrupts. /// @@ -156,16 +168,14 @@ impl PLIC

{ } /// Returns the pendings register of the PLIC. - /// This register allows to check if an interrupt source is pending. - /// This register is shared among all the contexts. + /// 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 the context proxy of a given context. - /// This proxy provides access to the PLIC registers of the given context. + /// Returns a proxy to access to all the PLIC registers of a given context. #[inline] pub fn ctx(context: C) -> CTX

{ // SAFETY: valid context number From 13deb053b0730178f38ae3b62a83b38e7879848a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rom=C3=A1n=20C=C3=A1rdenas?= Date: Wed, 27 Sep 2023 15:44:12 +0200 Subject: [PATCH 12/25] Utility functions now only in macros --- src/aclint.rs | 64 --------------------------- src/aclint/mswi.rs | 31 -------------- src/aclint/mtimer.rs | 31 -------------- src/lib.rs | 8 ++-- src/macros.rs | 100 ++++++++++++++++++++++++++++++++++++------- src/plic.rs | 31 -------------- 6 files changed, 89 insertions(+), 176 deletions(-) diff --git a/src/aclint.rs b/src/aclint.rs index 6b5cfb5c..e29ed73e 100644 --- a/src/aclint.rs +++ b/src/aclint.rs @@ -65,41 +65,6 @@ impl CLINT { const MTIME_OFFSET: usize = 0xBFF8; - /// Returns `true` if any CLINT-related interrupt is pending. - #[inline] - pub fn is_interrupting() -> bool { - Self::mswi_is_interrupting() || Self::mtimer_is_interrupting() - } - - /// Returns `true` if a machine software interrupt is pending. - #[inline] - pub fn mswi_is_interrupting() -> bool { - mswi::MSWI::is_interrupting() - } - - /// Returns `true` if Machine Software Interrupts are enabled. - /// This bit must be set for the `MSWI` to trigger machine software interrupts. - #[inline] - pub fn mswi_is_enabled() -> bool { - mswi::MSWI::is_enabled() - } - - /// Enables machine software interrupts to let the `MSWI` peripheral trigger interrupts. - /// - /// # Safety - /// - /// Enabling the `MSWI` may break mask-based critical sections. - #[inline] - pub unsafe fn mswi_enable() { - mswi::MSWI::enable(); - } - - /// Disables machine software interrupts to prevent the `MSWI` peripheral from triggering interrupts. - #[inline] - pub fn mswi_disable() { - mswi::MSWI::disable(); - } - /// Returns the `MSWI` peripheral. #[inline] pub const fn mswi() -> mswi::MSWI { @@ -107,35 +72,6 @@ impl CLINT { unsafe { mswi::MSWI::new(C::BASE) } } - /// Returns `true` if a machine timer interrupt is pending. - #[inline] - pub fn mtimer_is_interrupting() -> bool { - mtimer::MTIMER::is_interrupting() - } - - /// Returns `true` if Machine Timer Interrupts are enabled. - /// This bit must be set for the `MTIMER` to trigger machine timer interrupts. - #[inline] - pub fn mtimer_is_enabled() -> bool { - mtimer::MTIMER::is_enabled() - } - - /// Enables machine timer interrupts to let the `MTIMER` peripheral trigger interrupts. - /// - /// # Safety - /// - /// Enabling the `MTIMER` may break mask-based critical sections. - #[inline] - pub unsafe fn mtimer_enable() { - mtimer::MTIMER::enable(); - } - - /// Disables machine timer interrupts to prevent the `MTIMER` peripheral from triggering interrupts. - #[inline] - pub fn mtimer_disable() { - mtimer::MTIMER::disable(); - } - /// Returns the `MTIMER` peripheral. #[inline] pub const fn mtimer() -> mtimer::MTIMER { diff --git a/src/aclint/mswi.rs b/src/aclint/mswi.rs index ab799c12..fd663145 100644 --- a/src/aclint/mswi.rs +++ b/src/aclint/mswi.rs @@ -25,37 +25,6 @@ impl MSWI { } } - /// Returns `true` if a machine software interrupt is pending. - #[inline] - pub fn is_interrupting() -> bool { - riscv::register::mip::read().msoft() - } - - /// Returns `true` if Machine Software Interrupts are enabled. - #[inline] - pub fn is_enabled() -> bool { - riscv::register::mie::read().msoft() - } - - /// Sets the Machine Software Interrupt bit of the `mie` CSR. - /// This bit must be set for the `MSWI` to trigger machine software interrupts. - /// - /// # Safety - /// - /// Enabling the `MSWI` may break mask-based critical sections. - #[inline] - pub unsafe fn enable() { - riscv::register::mie::set_msoft(); - } - - /// Clears the Machine Software Interrupt bit of the `mie` CSR. - /// When cleared, the `MSWI` cannot trigger machine software interrupts. - #[inline] - pub fn disable() { - // SAFETY: it is safe to disable interrupts - unsafe { riscv::register::mie::clear_msoft() }; - } - /// Returns the `MSIP` register for the HART which ID is `hart_id`. /// /// # Note diff --git a/src/aclint/mtimer.rs b/src/aclint/mtimer.rs index 2794cc50..5deb79ba 100644 --- a/src/aclint/mtimer.rs +++ b/src/aclint/mtimer.rs @@ -27,37 +27,6 @@ impl MTIMER { } } - /// Returns `true` if a machine timer interrupt is pending. - #[inline] - pub fn is_interrupting() -> bool { - riscv::register::mip::read().mtimer() - } - - /// Returns `true` if Machine Timer Interrupts are enabled. - #[inline] - pub fn is_enabled() -> bool { - 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 enable() { - riscv::register::mie::set_mtimer(); - } - - /// Clears the Machine Timer Interrupt bit of the `mie` CSR. - /// When cleared, the `MTIMER` cannot trigger machine timer interrupts. - #[inline] - pub fn disable() { - // SAFETY: it is safe to disable interrupts - unsafe { riscv::register::mie::clear_mtimer() }; - } - /// Returns the `MTIMECMP` register for the HART which ID is `hart_id`. /// /// # Note diff --git a/src/lib.rs b/src/lib.rs index 4c67b12b..2a6a6c76 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,10 +3,10 @@ #![deny(missing_docs)] #![no_std] -pub use riscv; // re-export riscv crate to allow users to use it without importing it +pub use riscv; // re-export riscv crate to allow macros to use it -pub mod common; +pub mod common; // common definitions for all peripherals pub mod macros; // macros for easing the definition of peripherals in PACs -pub mod aclint; -pub mod plic; +pub mod aclint; // ACLINT and CLINT peripherals +pub mod plic; // PLIC peripheral diff --git a/src/macros.rs b/src/macros.rs index 1aed3f0b..39a4d58a 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -75,6 +75,47 @@ macro_rules! clint_codegen { } 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 @@ -82,35 +123,51 @@ macro_rules! clint_codegen { /// Enabling the `MSWI` may break mask-based critical sections. #[inline] pub unsafe fn mswi_enable() { - $crate::aclint::CLINT::::mswi_enable(); + $crate::riscv::register::mie::set_msoft(); } /// Disables the `MSWI` peripheral. #[inline] pub fn mswi_disable() { - $crate::aclint::CLINT::::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() } - /// Enables the `MTIMER` peripheral. + /// 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::aclint::CLINT::::mtimer_enable(); + $crate::riscv::register::mie::set_mtimer(); } - /// Disables the `MTIMER` peripheral. + /// Clears the Machine Timer Interrupt bit of the `mie` CSR. + /// When cleared, the `MTIMER` cannot trigger machine timer interrupts. #[inline] - pub fn disable_mtimer() { - $crate::aclint::CLINT::::mtimer_disable(); - } - - /// Returns the `MSWI` peripheral. - #[inline] - pub const fn mswi() -> $crate::aclint::mswi::MSWI { - $crate::aclint::CLINT::::mswi() + pub fn mtimer_disable() { + // SAFETY: it is safe to disable interrupts + unsafe { $crate::riscv::register::mie::clear_mtimer() }; } /// Returns the `MTIMER` peripheral. @@ -154,6 +211,18 @@ macro_rules! plic_codegen { } 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 @@ -161,13 +230,14 @@ macro_rules! plic_codegen { /// Enabling the `PLIC` may break mask-based critical sections. #[inline] pub unsafe fn enable() { - $crate::plic::PLIC::::enable(); + $crate::riscv::register::mie::set_mext(); } /// Disables machine external interrupts to prevent the PLIC from triggering interrupts. #[inline] pub fn disable() { - $crate::plic::PLIC::::disable(); + // SAFETY: it is safe to disable interrupts + unsafe { $crate::riscv::register::mie::clear_mext() }; } /// Returns the priorities register of the PLIC. diff --git a/src/plic.rs b/src/plic.rs index ef367e5c..cf643956 100644 --- a/src/plic.rs +++ b/src/plic.rs @@ -127,37 +127,6 @@ impl PLIC

{ const PENDINGS_OFFSET: usize = 0x1000; - /// Returns `true` if a machine external interrupt is pending. - #[inline] - pub fn is_interrupting() -> bool { - riscv::register::mip::read().mext() - } - - /// Returns true if Machine External Interrupts are enabled. - #[inline] - pub fn is_enabled() -> bool { - riscv::register::mie::read().mext() - } - - /// Sets the Machine External Interrupt bit of the `mie` CSR. - /// This bit must be set for the PLIC to trigger machine external interrupts. - /// - /// # Safety - /// - /// Enabling the `PLIC` may break mask-based critical sections. - #[inline] - pub unsafe fn enable() { - riscv::register::mie::set_mext(); - } - - /// Clears the Machine External Interrupt bit of the `mie` CSR. - /// When cleared, the PLIC does not trigger machine external interrupts. - #[inline] - pub fn disable() { - // SAFETY: it is safe to disable interrupts - unsafe { riscv::register::mie::clear_mext() }; - } - /// Returns the priorities register of the PLIC. /// This register allows to set the priority level of each interrupt source. /// The priority level of each interrupt source is shared among all the contexts. From 012aa79db4a388a3b6263ec3f85f9d255d9f7c40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rom=C3=A1n=20C=C3=A1rdenas?= Date: Fri, 3 Nov 2023 12:41:02 +0100 Subject: [PATCH 13/25] HAL implementations --- .github/workflows/clippy.yml | 2 +- Cargo.toml | 5 ++ examples/e310x.rs | 165 +++++++++++++++++++++++++++++++++++ src/aclint.rs | 26 ++---- src/aclint/mswi.rs | 27 ++++++ src/aclint/mtimer.rs | 35 +++++++- src/aclint/sswi.rs | 27 ++++++ src/hal.rs | 5 ++ src/hal/aclint.rs | 47 ++++++++++ src/hal_async.rs | 5 ++ src/hal_async/aclint.rs | 82 +++++++++++++++++ src/lib.rs | 3 + src/macros.rs | 59 ++++++++++++- src/plic.rs | 31 ++----- 14 files changed, 473 insertions(+), 46 deletions(-) create mode 100644 examples/e310x.rs create mode 100644 src/hal.rs create mode 100644 src/hal/aclint.rs create mode 100644 src/hal_async.rs create mode 100644 src/hal_async/aclint.rs diff --git a/.github/workflows/clippy.yml b/.github/workflows/clippy.yml index c8c92a68..272ec79d 100644 --- a/.github/workflows/clippy.yml +++ b/.github/workflows/clippy.yml @@ -37,4 +37,4 @@ jobs: runs-on: ubuntu-latest if: always() steps: - - run: jq --exit-status 'all(.result == "success")' <<< '${{ toJson(needs) }}' \ No newline at end of file + - run: jq --exit-status 'all(.result == "success")' <<< '${{ toJson(needs) }}' diff --git a/Cargo.toml b/Cargo.toml index d6a2f938..0509281f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,8 +6,13 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +embedded-hal = "1.0.0-rc.1" +embedded-hal-async = { version = "1.0.0-rc.1", optional = true } riscv = { git = "https://github.com/rust-embedded/riscv", branch = "master" } +[features] +hal-async = ["embedded-hal-async"] + [package.metadata.docs.rs] default-target = "riscv64imac-unknown-none-elf" targets = [ diff --git a/examples/e310x.rs b/examples/e310x.rs new file mode 100644 index 00000000..df21cf11 --- /dev/null +++ b/examples/e310x.rs @@ -0,0 +1,165 @@ +use riscv_peripheral::{ + aclint::HartIdNumber, + plic::{ContextNumber, InterruptNumber, PriorityNumber}, +}; + +#[repr(u16)] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum HartId { + H0 = 0, +} + +unsafe impl HartIdNumber for HartId { + const MAX_HART_ID_NUMBER: u16 = 0; + + #[inline] + fn number(self) -> u16 { + self as _ + } + + #[inline] + fn from_number(number: u16) -> Result { + if number > Self::MAX_HART_ID_NUMBER { + Err(number) + } else { + // SAFETY: valid context number + Ok(unsafe { core::mem::transmute(number) }) + } + } +} + +unsafe impl ContextNumber for HartId { + const MAX_CONTEXT_NUMBER: u16 = 0; + + #[inline] + fn number(self) -> u16 { + self as _ + } + + #[inline] + fn from_number(number: u16) -> Result { + if number > Self::MAX_CONTEXT_NUMBER { + Err(number) + } else { + // SAFETY: valid context number + Ok(unsafe { core::mem::transmute(number) }) + } + } +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[repr(u16)] +pub enum Interrupt { + WATCHDOG = 1, + RTC = 2, + UART0 = 3, + UART1 = 4, + QSPI0 = 5, + QSPI1 = 6, + QSPI2 = 7, + GPIO0 = 8, + GPIO1 = 9, + GPIO2 = 10, + GPIO3 = 11, + GPIO4 = 12, + GPIO5 = 13, + GPIO6 = 14, + GPIO7 = 15, + GPIO8 = 16, + GPIO9 = 17, + GPIO10 = 18, + GPIO11 = 19, + GPIO12 = 20, + GPIO13 = 21, + GPIO14 = 22, + GPIO15 = 23, + GPIO16 = 24, + GPIO17 = 25, + GPIO18 = 26, + GPIO19 = 27, + GPIO20 = 28, + GPIO21 = 29, + GPIO22 = 30, + GPIO23 = 31, + GPIO24 = 32, + GPIO25 = 33, + GPIO26 = 34, + GPIO27 = 35, + GPIO28 = 36, + GPIO29 = 37, + GPIO30 = 38, + GPIO31 = 39, + PWM0CMP0 = 40, + PWM0CMP1 = 41, + PWM0CMP2 = 42, + PWM0CMP3 = 43, + PWM1CMP0 = 44, + PWM1CMP1 = 45, + PWM1CMP2 = 46, + PWM1CMP3 = 47, + PWM2CMP0 = 48, + PWM2CMP1 = 49, + PWM2CMP2 = 50, + PWM2CMP3 = 51, + I2C0 = 52, +} + +unsafe impl InterruptNumber for Interrupt { + const MAX_INTERRUPT_NUMBER: u16 = 52; + + #[inline] + fn number(self) -> u16 { + self as _ + } + + #[inline] + fn from_number(number: u16) -> Result { + if number == 0 || number > Self::MAX_INTERRUPT_NUMBER { + Err(number) + } else { + // SAFETY: valid interrupt number + Ok(unsafe { core::mem::transmute(number) }) + } + } +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[repr(u8)] +pub enum Priority { + P0 = 0, + P1 = 1, + P2 = 2, + P3 = 3, + P4 = 4, + P5 = 5, + P6 = 6, + P7 = 7, +} + +unsafe impl PriorityNumber for Priority { + const MAX_PRIORITY_NUMBER: u8 = 7; + + #[inline] + fn number(self) -> u8 { + self as _ + } + + #[inline] + fn from_number(number: u8) -> Result { + if number > Self::MAX_PRIORITY_NUMBER { + Err(number) + } else { + // SAFETY: valid priority number + Ok(unsafe { core::mem::transmute(number) }) + } + } +} + +riscv_peripheral::clint_codegen!( + base 0x0200_0000, + freq 32_768, + mtimecmps [mtimecmp0=(HartId::H0,"`H0`")], + msips [msip0=(HartId::H0,"`H0`")], +); + +fn main() {} diff --git a/src/aclint.rs b/src/aclint.rs index e29ed73e..c19b6446 100644 --- a/src/aclint.rs +++ b/src/aclint.rs @@ -136,6 +136,7 @@ pub(crate) mod test { 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(); @@ -150,25 +151,16 @@ pub(crate) mod test { let mtimecmp2 = mtimer.mtimecmp(HartId::H2); assert_eq!(mtimecmp0.get_ptr() as usize, 0x0200_4000); - assert_eq!(mtimecmp1.get_ptr() as usize, 0x0200_4000 + 1 * 8); // 8 bytes per register + 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); - // Check that the mtimecmpX functions are equivalent to the mtimer.mtimecmp(X) function. - let mtimecmp0 = CLINT::mtimecmp0(); - let mtimecmp1 = CLINT::mtimecmp1(); - let mtimecmp2 = CLINT::mtimecmp2(); + 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!( - mtimecmp0.get_ptr() as usize, - mtimer.mtimecmp(HartId::H0).get_ptr() as usize - ); - assert_eq!( - mtimecmp1.get_ptr() as usize, - mtimer.mtimecmp(HartId::H1).get_ptr() as usize - ); - assert_eq!( - mtimecmp2.get_ptr() as usize, - mtimer.mtimecmp(HartId::H2).get_ptr() as usize - ); + 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/src/aclint/mswi.rs b/src/aclint/mswi.rs index fd663145..3af4af75 100644 --- a/src/aclint/mswi.rs +++ b/src/aclint/mswi.rs @@ -58,3 +58,30 @@ impl MSIP { 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/src/aclint/mtimer.rs b/src/aclint/mtimer.rs index 5deb79ba..71445675 100644 --- a/src/aclint/mtimer.rs +++ b/src/aclint/mtimer.rs @@ -18,7 +18,7 @@ impl MTIMER { /// /// # Safety /// - /// The base address must point to a valid `MTIMER` peripheral. + /// The base addresses must point to valid `MTIMECMP` and `MTIME` peripherals. #[inline] pub const unsafe fn new(mtimecmp: usize, mtime: usize) -> Self { Self { @@ -44,3 +44,36 @@ 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/src/aclint/sswi.rs b/src/aclint/sswi.rs index e31c6cf1..51072d66 100644 --- a/src/aclint/sswi.rs +++ b/src/aclint/sswi.rs @@ -89,3 +89,30 @@ impl SETSSIP { 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/src/hal.rs b/src/hal.rs new file mode 100644 index 00000000..8d16ffe0 --- /dev/null +++ b/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/src/hal/aclint.rs b/src/hal/aclint.rs new file mode 100644 index 00000000..d448b551 --- /dev/null +++ b/src/hal/aclint.rs @@ -0,0 +1,47 @@ +//! Delay trait implementation for (A)CLINT peripherals + +use crate::aclint::mtimer::MTIME; +pub use crate::hal::delay::DelayUs; + +/// 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 DelayUs for Delay { + #[inline] + fn delay_us(&mut self, us: u32) { + let time_from = self.mtime.read(); + let time_to = time_from.wrapping_add(us as u64 * self.freq as u64 / 1_000_000); + + while time_to < self.mtime.read() {} // wait for overflow + while time_to > self.mtime.read() {} // wait for time to pass + } +} diff --git a/src/hal_async.rs b/src/hal_async.rs new file mode 100644 index 00000000..5cd093c8 --- /dev/null +++ b/src/hal_async.rs @@ -0,0 +1,5 @@ +//! async trait implementations for embedded-hal + +pub use embedded_hal_async::*; // re-export embedded-hal-async to allow macros to use it + +pub mod aclint; // ACLINT and CLINT peripherals diff --git a/src/hal_async/aclint.rs b/src/hal_async/aclint.rs new file mode 100644 index 00000000..25c18d0f --- /dev/null +++ b/src/hal_async/aclint.rs @@ -0,0 +1,82 @@ +//! Asynchronous delay implementation for the (A)CLINT peripheral. + +use crate::aclint::mtimer::MTIME; +pub use crate::hal::aclint::Delay; +pub use crate::hal_async::delay::DelayUs; +use core::{ + cmp::Ordering, + future::Future, + pin::Pin, + task::{Context, Poll}, +}; + +enum DelayAsyncState { + WaitOverflow(u64), + Wait(u64), + Ready, +} + +struct FSMDelay { + mtime: MTIME, + state: DelayAsyncState, +} + +impl FSMDelay { + pub fn new(n_ticks: u64, mtime: MTIME) -> Self { + let t_from = mtime.read(); + let t_to = t_from.wrapping_add(n_ticks); + + let state = match t_to.cmp(&t_from) { + Ordering::Less => DelayAsyncState::WaitOverflow(t_to), + Ordering::Greater => DelayAsyncState::Wait(t_to), + Ordering::Equal => DelayAsyncState::Ready, + }; + + Self { mtime, state } + } +} + +impl Future for FSMDelay { + type Output = (); + + fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll { + match self.state { + DelayAsyncState::WaitOverflow(t_to) => match t_to.cmp(&self.mtime.read()) { + Ordering::Less => Poll::Pending, + Ordering::Greater => { + self.state = DelayAsyncState::Wait(t_to); + Poll::Pending + } + Ordering::Equal => { + self.state = DelayAsyncState::Ready; + Poll::Ready(()) + } + }, + DelayAsyncState::Wait(t_to) => { + if self.mtime.read() < t_to { + Poll::Pending + } else { + self.state = DelayAsyncState::Ready; + Poll::Ready(()) + } + } + DelayAsyncState::Ready => Poll::Ready(()), + } + } +} + +impl DelayUs for Delay { + #[inline] + async fn delay_us(&mut self, us: u32) { + let n_ticks = us as u64 * self.get_freq() as u64 / 1_000_000; + let state = FSMDelay::new(n_ticks, self.get_mtime()); + state.await; + } + + #[inline] + async fn delay_ms(&mut self, ms: u32) { + let n_ticks = ms as u64 * self.get_freq() as u64 / 1_000; + let state = FSMDelay::new(n_ticks, self.get_mtime()); + state.await; + } +} diff --git a/src/lib.rs b/src/lib.rs index 2a6a6c76..8bfd17b3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,6 +6,9 @@ pub use riscv; // re-export riscv crate to allow macros to use it pub mod common; // common definitions for all peripherals +pub mod hal; // trait implementations for embedded-hal +#[cfg(feature = "hal-async")] +pub mod hal_async; // async trait implementations for embedded-hal pub mod macros; // macros for easing the definition of peripherals in PACs pub mod aclint; // ACLINT and CLINT peripherals diff --git a/src/macros.rs b/src/macros.rs index 39a4d58a..38571dc2 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -3,10 +3,12 @@ /// 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 2 different argument types: +/// 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. /// @@ -17,10 +19,11 @@ /// ``` /// use riscv_peripheral::clint_codegen; /// -/// clint_codegen!(base 0x0200_0000,); // do not forget the ending comma! +/// clint_codegen!(base 0x0200_0000, freq 32_768,); // do not forget the ending comma! /// /// let mswi = CLINT::mswi(); // MSWI peripheral /// let mtimer = CLINT::mtimer(); // MTIMER peripheral +/// let delay = CLINT::delay(); // For the `embedded_hal::delay::DelayUs` and `embedded_hal_async::delay::DelayUs` traits /// ``` /// /// ## Base address and per-HART mtimecmp registers @@ -49,7 +52,8 @@ /// /// clint_codegen!( /// base 0x0200_0000, -/// mtimecmps [mtimecmp0 = (HartId::H0, "`H0`"), mtimecmp1 = (HartId::H1, "`H1`"), mtimecmp2 = (HartId::H2, "`H2`")], // do not forget the ending comma! +/// 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 @@ -58,6 +62,10 @@ /// 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 { @@ -67,6 +75,7 @@ macro_rules! clint_codegen { }; (base $addr:literal, $($tail:tt)*) => { /// CLINT peripheral + #[allow(clippy::upper_case_acronyms)] #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct CLINT; @@ -175,13 +184,54 @@ macro_rules! clint_codegen { pub const fn mtimer() -> $crate::aclint::mtimer::MTIMER { $crate::aclint::CLINT::::mtimer() } + + /// Returns the `MTIME` register of the `MTIMER` peripheral. + #[inline] + pub const fn mtime() -> $crate::aclint::mtimer::MTIME { + Self::mtimer().mtime + } + } + $crate::clint_codegen!($($tail)*); + }; + (freq $freq:literal, $($tail:tt)*) => { + impl CLINT { + /// Returns the frequency of the `MTIME` register. + #[inline] + pub const fn freq() -> usize { + $freq + } + + /// Delay implementation for CLINT peripherals. + /// + /// # Note + /// + /// You must export the `riscv_peripheral::hal::delay::DelayUs` trait in order to use delay methods. + /// You must export the `riscv_peripheral::hal_async::delay::DelayUs` trait in order to use async delay methods. + #[inline] + pub const fn delay() -> $crate::hal::aclint::Delay { + $crate::hal::aclint::Delay::new(Self::mtime(), Self::freq()) + } + } + $crate::clint_codegen!($($tail)*); + }; + (msips [$($fn:ident = ($hart:expr , $shart:expr)),+], $($tail:tt)*) => { + impl CLINT { + $( + #[doc = "Returns the `msip` register for HART "] + #[doc = $shart] + #[doc = "."] + #[inline] + pub fn $fn() -> $crate::aclint::mswi::MSIP { + Self::mswi().msip($hart) + } + )* } $crate::clint_codegen!($($tail)*); }; (mtimecmps [$($fn:ident = ($hart:expr , $shart:expr)),+], $($tail:tt)*) => { impl CLINT { $( - #[doc = "Returns the `mtimecmp` peripheral HART "] + #[doc = "Returns the `mtimecmp` register for HART "] #[doc = $shart] #[doc = "."] #[inline] @@ -203,6 +253,7 @@ macro_rules! plic_codegen { }; (base $addr:literal, $($tail:tt)*) => { /// PLIC peripheral + #[allow(clippy::upper_case_acronyms)] #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct PLIC; diff --git a/src/plic.rs b/src/plic.rs index cf643956..3185a77e 100644 --- a/src/plic.rs +++ b/src/plic.rs @@ -361,38 +361,23 @@ pub(crate) mod test { for i in 0..=Context::MAX_CONTEXT_NUMBER { let context = Context::from_number(i).unwrap(); + let i = i as usize; + let ctx = PLIC::ctx(context); - assert_eq!( - ctx.enables().address(), - 0x0C00_0000 + 0x2000 + i as usize * 0x80 - ); + assert_eq!(ctx.enables().address(), 0x0C00_0000 + 0x2000 + i * 0x80); assert_eq!( ctx.threshold().get_ptr() as usize, - 0x0C00_0000 + 0x20_0000 + i as usize * 0x1000 + 0x0C00_0000 + 0x20_0000 + i * 0x1000 ); assert_eq!( ctx.claim().get_ptr() as usize, - 0x0C00_0000 + 0x20_0004 + i as usize * 0x1000 + 0x0C00_0000 + 0x20_0004 + i * 0x1000 ); } - let ctx0 = PLIC::ctx0(); - let ctx_0_ = PLIC::ctx(Context::C0); - assert_eq!(ctx0.enables().address(), ctx_0_.enables().address()); - assert_eq!(ctx0.threshold().get_ptr(), ctx_0_.threshold().get_ptr()); - assert_eq!(ctx0.claim().get_ptr(), ctx_0_.claim().get_ptr()); - - let ctx1 = PLIC::ctx1(); - let ctx_1_ = PLIC::ctx(Context::C1); - assert_eq!(ctx1.enables().address(), ctx_1_.enables().address()); - assert_eq!(ctx1.threshold().get_ptr(), ctx_1_.threshold().get_ptr()); - assert_eq!(ctx1.claim().get_ptr(), ctx_1_.claim().get_ptr()); - - let ctx2 = PLIC::ctx2(); - let ctx_2_ = PLIC::ctx(Context::C2); - assert_eq!(ctx2.enables().address(), ctx_2_.enables().address()); - assert_eq!(ctx2.threshold().get_ptr(), ctx_2_.threshold().get_ptr()); - assert_eq!(ctx2.claim().get_ptr(), ctx_2_.claim().get_ptr()); + assert_eq!(PLIC::ctx0(), PLIC::ctx(Context::C0)); + assert_eq!(PLIC::ctx1(), PLIC::ctx(Context::C1)); + assert_eq!(PLIC::ctx2(), PLIC::ctx(Context::C2)); } } From 147285e8a0a1eedc15ff72380543c0220a18554f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rom=C3=A1n=20C=C3=A1rdenas?= Date: Wed, 8 Nov 2023 15:45:13 +0100 Subject: [PATCH 14/25] concise implementation of e-h --- .github/workflows/clippy.yml | 4 +++ src/hal/aclint.rs | 8 ++--- src/hal_async/aclint.rs | 63 +++++++++--------------------------- 3 files changed, 22 insertions(+), 53 deletions(-) diff --git a/.github/workflows/clippy.yml b/.github/workflows/clippy.yml index 272ec79d..44d91413 100644 --- a/.github/workflows/clippy.yml +++ b/.github/workflows/clippy.yml @@ -19,6 +19,10 @@ jobs: # Nightly is only for reference and allowed to fail - toolchain: nightly experimental: true + # async traits are still not supported in stable + - toolchain: stable + cargo_flags: --all-features + experimental: true runs-on: ubuntu-latest continue-on-error: ${{ matrix.experimental || false }} steps: diff --git a/src/hal/aclint.rs b/src/hal/aclint.rs index d448b551..d554b789 100644 --- a/src/hal/aclint.rs +++ b/src/hal/aclint.rs @@ -38,10 +38,8 @@ impl Delay { impl DelayUs for Delay { #[inline] fn delay_us(&mut self, us: u32) { - let time_from = self.mtime.read(); - let time_to = time_from.wrapping_add(us as u64 * self.freq as u64 / 1_000_000); - - while time_to < self.mtime.read() {} // wait for overflow - while time_to > self.mtime.read() {} // wait for time to pass + let t0 = self.mtime.read(); + let n_ticks = us as u64 * self.freq as u64 / 1_000_000; + while self.mtime.read().wrapping_sub(t0) < n_ticks {} } } diff --git a/src/hal_async/aclint.rs b/src/hal_async/aclint.rs index 25c18d0f..03af1250 100644 --- a/src/hal_async/aclint.rs +++ b/src/hal_async/aclint.rs @@ -4,63 +4,32 @@ use crate::aclint::mtimer::MTIME; pub use crate::hal::aclint::Delay; pub use crate::hal_async::delay::DelayUs; use core::{ - cmp::Ordering, future::Future, pin::Pin, task::{Context, Poll}, }; -enum DelayAsyncState { - WaitOverflow(u64), - Wait(u64), - Ready, -} - -struct FSMDelay { +struct DelayAsync { mtime: MTIME, - state: DelayAsyncState, + t0: u64, + n_ticks: u64, } -impl FSMDelay { - pub fn new(n_ticks: u64, mtime: MTIME) -> Self { - let t_from = mtime.read(); - let t_to = t_from.wrapping_add(n_ticks); - - let state = match t_to.cmp(&t_from) { - Ordering::Less => DelayAsyncState::WaitOverflow(t_to), - Ordering::Greater => DelayAsyncState::Wait(t_to), - Ordering::Equal => DelayAsyncState::Ready, - }; - - Self { mtime, state } +impl DelayAsync { + pub fn new(mtime: MTIME, n_ticks: u64) -> Self { + let t0 = mtime.read(); + Self { mtime, t0, n_ticks } } } -impl Future for FSMDelay { +impl Future for DelayAsync { type Output = (); - fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll { - match self.state { - DelayAsyncState::WaitOverflow(t_to) => match t_to.cmp(&self.mtime.read()) { - Ordering::Less => Poll::Pending, - Ordering::Greater => { - self.state = DelayAsyncState::Wait(t_to); - Poll::Pending - } - Ordering::Equal => { - self.state = DelayAsyncState::Ready; - Poll::Ready(()) - } - }, - DelayAsyncState::Wait(t_to) => { - if self.mtime.read() < t_to { - Poll::Pending - } else { - self.state = DelayAsyncState::Ready; - Poll::Ready(()) - } - } - DelayAsyncState::Ready => Poll::Ready(()), + #[inline] + fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll { + match self.mtime.read().wrapping_sub(self.t0) < self.n_ticks { + true => Poll::Pending, + false => Poll::Ready(()), } } } @@ -69,14 +38,12 @@ impl DelayUs for Delay { #[inline] async fn delay_us(&mut self, us: u32) { let n_ticks = us as u64 * self.get_freq() as u64 / 1_000_000; - let state = FSMDelay::new(n_ticks, self.get_mtime()); - state.await; + DelayAsync::new(self.get_mtime(), n_ticks).await; } #[inline] async fn delay_ms(&mut self, ms: u32) { let n_ticks = ms as u64 * self.get_freq() as u64 / 1_000; - let state = FSMDelay::new(n_ticks, self.get_mtime()); - state.await; + DelayAsync::new(self.get_mtime(), n_ticks).await; } } From a74f311d237fca4eba766a8ce7b178dcec5c3d18 Mon Sep 17 00:00:00 2001 From: Aaron Gowatch Date: Tue, 5 Dec 2023 12:04:57 -0700 Subject: [PATCH 15/25] Track DelayNs changes in embedded-hal-1.0.0-rc.2 --- Cargo.toml | 2 +- src/hal/aclint.rs | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 0509281f..bfd778ba 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,7 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -embedded-hal = "1.0.0-rc.1" +embedded-hal = "1.0.0-rc.2" embedded-hal-async = { version = "1.0.0-rc.1", optional = true } riscv = { git = "https://github.com/rust-embedded/riscv", branch = "master" } diff --git a/src/hal/aclint.rs b/src/hal/aclint.rs index d554b789..143fe852 100644 --- a/src/hal/aclint.rs +++ b/src/hal/aclint.rs @@ -1,7 +1,7 @@ //! Delay trait implementation for (A)CLINT peripherals use crate::aclint::mtimer::MTIME; -pub use crate::hal::delay::DelayUs; +pub use crate::hal::delay::DelayNs; /// Delay implementation for (A)CLINT peripherals. pub struct Delay { @@ -35,11 +35,12 @@ impl Delay { } } -impl DelayUs for Delay { +impl DelayNs for Delay { #[inline] - fn delay_us(&mut self, us: u32) { + fn delay_ns(&mut self, ns: u32) { let t0 = self.mtime.read(); - let n_ticks = us as u64 * self.freq as u64 / 1_000_000; + 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 {} } } From 51066b53b9e94dd32546e2da4d4074e7c521ea73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rom=C3=A1n=20C=C3=A1rdenas?= Date: Wed, 6 Dec 2023 19:04:57 +0100 Subject: [PATCH 16/25] prepare for riscv --- {.github => riscv-peripheral/.github}/workflows/clippy.yml | 0 {.github => riscv-peripheral/.github}/workflows/rust.yml | 0 {.github => riscv-peripheral/.github}/workflows/rustfmt.yml | 0 .gitignore => riscv-peripheral/.gitignore | 0 Cargo.toml => riscv-peripheral/Cargo.toml | 4 ++-- README.md => riscv-peripheral/README.md | 0 {examples => riscv-peripheral/examples}/e310x.rs | 0 {src => riscv-peripheral/src}/aclint.rs | 0 {src => riscv-peripheral/src}/aclint/mswi.rs | 0 {src => riscv-peripheral/src}/aclint/mtimer.rs | 0 {src => riscv-peripheral/src}/aclint/sswi.rs | 0 {src => riscv-peripheral/src}/common.rs | 0 {src => riscv-peripheral/src}/hal.rs | 0 {src => riscv-peripheral/src}/hal/aclint.rs | 0 {src => riscv-peripheral/src}/hal_async.rs | 0 {src => riscv-peripheral/src}/hal_async/aclint.rs | 0 {src => riscv-peripheral/src}/lib.rs | 4 ++-- {src => riscv-peripheral/src}/macros.rs | 0 {src => riscv-peripheral/src}/plic.rs | 0 {src => riscv-peripheral/src}/plic/claim.rs | 0 {src => riscv-peripheral/src}/plic/enables.rs | 0 {src => riscv-peripheral/src}/plic/pendings.rs | 0 {src => riscv-peripheral/src}/plic/priorities.rs | 0 {src => riscv-peripheral/src}/plic/threshold.rs | 0 24 files changed, 4 insertions(+), 4 deletions(-) rename {.github => riscv-peripheral/.github}/workflows/clippy.yml (100%) rename {.github => riscv-peripheral/.github}/workflows/rust.yml (100%) rename {.github => riscv-peripheral/.github}/workflows/rustfmt.yml (100%) rename .gitignore => riscv-peripheral/.gitignore (100%) rename Cargo.toml => riscv-peripheral/Cargo.toml (84%) rename README.md => riscv-peripheral/README.md (100%) rename {examples => riscv-peripheral/examples}/e310x.rs (100%) rename {src => riscv-peripheral/src}/aclint.rs (100%) rename {src => riscv-peripheral/src}/aclint/mswi.rs (100%) rename {src => riscv-peripheral/src}/aclint/mtimer.rs (100%) rename {src => riscv-peripheral/src}/aclint/sswi.rs (100%) rename {src => riscv-peripheral/src}/common.rs (100%) rename {src => riscv-peripheral/src}/hal.rs (100%) rename {src => riscv-peripheral/src}/hal/aclint.rs (100%) rename {src => riscv-peripheral/src}/hal_async.rs (100%) rename {src => riscv-peripheral/src}/hal_async/aclint.rs (100%) rename {src => riscv-peripheral/src}/lib.rs (77%) rename {src => riscv-peripheral/src}/macros.rs (100%) rename {src => riscv-peripheral/src}/plic.rs (100%) rename {src => riscv-peripheral/src}/plic/claim.rs (100%) rename {src => riscv-peripheral/src}/plic/enables.rs (100%) rename {src => riscv-peripheral/src}/plic/pendings.rs (100%) rename {src => riscv-peripheral/src}/plic/priorities.rs (100%) rename {src => riscv-peripheral/src}/plic/threshold.rs (100%) diff --git a/.github/workflows/clippy.yml b/riscv-peripheral/.github/workflows/clippy.yml similarity index 100% rename from .github/workflows/clippy.yml rename to riscv-peripheral/.github/workflows/clippy.yml diff --git a/.github/workflows/rust.yml b/riscv-peripheral/.github/workflows/rust.yml similarity index 100% rename from .github/workflows/rust.yml rename to riscv-peripheral/.github/workflows/rust.yml diff --git a/.github/workflows/rustfmt.yml b/riscv-peripheral/.github/workflows/rustfmt.yml similarity index 100% rename from .github/workflows/rustfmt.yml rename to riscv-peripheral/.github/workflows/rustfmt.yml diff --git a/.gitignore b/riscv-peripheral/.gitignore similarity index 100% rename from .gitignore rename to riscv-peripheral/.gitignore diff --git a/Cargo.toml b/riscv-peripheral/Cargo.toml similarity index 84% rename from Cargo.toml rename to riscv-peripheral/Cargo.toml index bfd778ba..1eabf062 100644 --- a/Cargo.toml +++ b/riscv-peripheral/Cargo.toml @@ -7,11 +7,11 @@ edition = "2021" [dependencies] embedded-hal = "1.0.0-rc.2" -embedded-hal-async = { version = "1.0.0-rc.1", optional = true } +# embedded-hal-async = { version = "1.0.0-rc.1", optional = true } riscv = { git = "https://github.com/rust-embedded/riscv", branch = "master" } [features] -hal-async = ["embedded-hal-async"] +# hal-async = ["embedded-hal-async"] [package.metadata.docs.rs] default-target = "riscv64imac-unknown-none-elf" diff --git a/README.md b/riscv-peripheral/README.md similarity index 100% rename from README.md rename to riscv-peripheral/README.md diff --git a/examples/e310x.rs b/riscv-peripheral/examples/e310x.rs similarity index 100% rename from examples/e310x.rs rename to riscv-peripheral/examples/e310x.rs diff --git a/src/aclint.rs b/riscv-peripheral/src/aclint.rs similarity index 100% rename from src/aclint.rs rename to riscv-peripheral/src/aclint.rs diff --git a/src/aclint/mswi.rs b/riscv-peripheral/src/aclint/mswi.rs similarity index 100% rename from src/aclint/mswi.rs rename to riscv-peripheral/src/aclint/mswi.rs diff --git a/src/aclint/mtimer.rs b/riscv-peripheral/src/aclint/mtimer.rs similarity index 100% rename from src/aclint/mtimer.rs rename to riscv-peripheral/src/aclint/mtimer.rs diff --git a/src/aclint/sswi.rs b/riscv-peripheral/src/aclint/sswi.rs similarity index 100% rename from src/aclint/sswi.rs rename to riscv-peripheral/src/aclint/sswi.rs diff --git a/src/common.rs b/riscv-peripheral/src/common.rs similarity index 100% rename from src/common.rs rename to riscv-peripheral/src/common.rs diff --git a/src/hal.rs b/riscv-peripheral/src/hal.rs similarity index 100% rename from src/hal.rs rename to riscv-peripheral/src/hal.rs diff --git a/src/hal/aclint.rs b/riscv-peripheral/src/hal/aclint.rs similarity index 100% rename from src/hal/aclint.rs rename to riscv-peripheral/src/hal/aclint.rs diff --git a/src/hal_async.rs b/riscv-peripheral/src/hal_async.rs similarity index 100% rename from src/hal_async.rs rename to riscv-peripheral/src/hal_async.rs diff --git a/src/hal_async/aclint.rs b/riscv-peripheral/src/hal_async/aclint.rs similarity index 100% rename from src/hal_async/aclint.rs rename to riscv-peripheral/src/hal_async/aclint.rs diff --git a/src/lib.rs b/riscv-peripheral/src/lib.rs similarity index 77% rename from src/lib.rs rename to riscv-peripheral/src/lib.rs index 8bfd17b3..c0ada40f 100644 --- a/src/lib.rs +++ b/riscv-peripheral/src/lib.rs @@ -7,8 +7,8 @@ pub use riscv; // re-export riscv crate to allow macros to use it pub mod common; // common definitions for all peripherals pub mod hal; // trait implementations for embedded-hal -#[cfg(feature = "hal-async")] -pub mod hal_async; // async trait implementations for embedded-hal + // #[cfg(feature = "hal-async")] + // pub mod hal_async; // async trait implementations for embedded-hal pub mod macros; // macros for easing the definition of peripherals in PACs pub mod aclint; // ACLINT and CLINT peripherals diff --git a/src/macros.rs b/riscv-peripheral/src/macros.rs similarity index 100% rename from src/macros.rs rename to riscv-peripheral/src/macros.rs diff --git a/src/plic.rs b/riscv-peripheral/src/plic.rs similarity index 100% rename from src/plic.rs rename to riscv-peripheral/src/plic.rs diff --git a/src/plic/claim.rs b/riscv-peripheral/src/plic/claim.rs similarity index 100% rename from src/plic/claim.rs rename to riscv-peripheral/src/plic/claim.rs diff --git a/src/plic/enables.rs b/riscv-peripheral/src/plic/enables.rs similarity index 100% rename from src/plic/enables.rs rename to riscv-peripheral/src/plic/enables.rs diff --git a/src/plic/pendings.rs b/riscv-peripheral/src/plic/pendings.rs similarity index 100% rename from src/plic/pendings.rs rename to riscv-peripheral/src/plic/pendings.rs diff --git a/src/plic/priorities.rs b/riscv-peripheral/src/plic/priorities.rs similarity index 100% rename from src/plic/priorities.rs rename to riscv-peripheral/src/plic/priorities.rs diff --git a/src/plic/threshold.rs b/riscv-peripheral/src/plic/threshold.rs similarity index 100% rename from src/plic/threshold.rs rename to riscv-peripheral/src/plic/threshold.rs From 36a264c35839db6cb3ee4eaad36be2be229a1722 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rom=C3=A1n=20C=C3=A1rdenas?= Date: Thu, 7 Dec 2023 12:56:10 +0100 Subject: [PATCH 17/25] riscv-peripheral crate --- .github/workflows/changelog.yaml | 10 ++ .github/workflows/riscv-peripheral.yaml | 61 +++++++++++ Cargo.toml | 1 + README.md | 3 + riscv-pac/CHANGELOG.md | 4 + riscv-pac/README.md | 6 +- riscv-peripheral/.github/workflows/clippy.yml | 44 -------- riscv-peripheral/.github/workflows/rust.yml | 22 ---- .../.github/workflows/rustfmt.yml | 18 --- riscv-peripheral/.gitignore | 17 --- riscv-peripheral/CHANGELOG.md | 12 ++ riscv-peripheral/Cargo.toml | 3 +- riscv-peripheral/README.md | 34 +++++- riscv-peripheral/examples/e310x.rs | 33 ++---- riscv-peripheral/src/aclint.rs | 27 +---- riscv-peripheral/src/macros.rs | 13 +-- riscv-peripheral/src/plic.rs | 103 ++---------------- 17 files changed, 155 insertions(+), 256 deletions(-) create mode 100644 .github/workflows/riscv-peripheral.yaml delete mode 100644 riscv-peripheral/.github/workflows/clippy.yml delete mode 100644 riscv-peripheral/.github/workflows/rust.yml delete mode 100644 riscv-peripheral/.github/workflows/rustfmt.yml delete mode 100644 riscv-peripheral/.gitignore create mode 100644 riscv-peripheral/CHANGELOG.md diff --git a/.github/workflows/changelog.yaml b/.github/workflows/changelog.yaml index 86541174..0a3c2f81 100644 --- a/.github/workflows/changelog.yaml +++ b/.github/workflows/changelog.yaml @@ -23,6 +23,8 @@ jobs: - 'riscv-rt/**' riscv-pac: - 'riscv-pac/**' + riscv-peripheral: + - 'riscv-peripheral/**' - name: Check for CHANGELOG.md (riscv) if: steps.changes.outputs.riscv == 'true' @@ -47,3 +49,11 @@ jobs: changeLogPath: ./riscv-pac/CHANGELOG.md skipLabels: 'skip changelog' missingUpdateErrorMessage: 'Please add a changelog entry in the riscv-pac/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/riscv-peripheral.yaml b/.github/workflows/riscv-peripheral.yaml new file mode 100644 index 00000000..c9a3c744 --- /dev/null +++ b/.github/workflows/riscv-peripheral.yaml @@ -0,0 +1,61 @@ +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.61.0 + toolchain: [ stable, nightly, 1.61.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 ] + 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 b97d6de3..b08e6641 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,5 +3,6 @@ resolver = "2" members = [ "riscv", "riscv-pac", + "riscv-peripheral", "riscv-rt", ] diff --git a/README.md b/README.md index d694f26c..feeb188c 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 @@ -22,6 +23,8 @@ Conduct][CoC], the maintainer of this crate, the [RISC-V team][team], promises 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 [team]: https://github.com/rust-embedded/wg#the-risc-v-team [CoC]: CODE_OF_CONDUCT.md diff --git a/riscv-pac/CHANGELOG.md b/riscv-pac/CHANGELOG.md index d17dc4a1..f40a9aae 100644 --- a/riscv-pac/CHANGELOG.md +++ b/riscv-pac/CHANGELOG.md @@ -10,3 +10,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Added - Add `InterruptNumber`, `PriorityNumber`, and `HartIdNumber` traits. + +### Changed + +- Update `README.md` diff --git a/riscv-pac/README.md b/riscv-pac/README.md index 61c50447..9e96ac74 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) diff --git a/riscv-peripheral/.github/workflows/clippy.yml b/riscv-peripheral/.github/workflows/clippy.yml deleted file mode 100644 index 44d91413..00000000 --- a/riscv-peripheral/.github/workflows/clippy.yml +++ /dev/null @@ -1,44 +0,0 @@ -on: - push: - branches: [ main ] - pull_request: - merge_group: - -name: Lints compliance check - -env: - CLIPPY_PARAMS: -W clippy::all -W clippy::pedantic -W clippy::nursery -W clippy::cargo - -jobs: - clippy: - strategy: - matrix: - toolchain: [ stable, nightly ] - cargo_flags: [ --all-features, --no-default-features ] - include: - # Nightly is only for reference and allowed to fail - - toolchain: nightly - experimental: true - # async traits are still not supported in stable - - toolchain: stable - cargo_flags: --all-features - experimental: true - runs-on: ubuntu-latest - continue-on-error: ${{ matrix.experimental || false }} - steps: - - uses: actions/checkout@v3 - - uses: dtolnay/rust-toolchain@master - with: - toolchain: ${{ matrix.toolchain }} - components: clippy - - name: Run clippy - run: cargo clippy --all ${{ matrix.cargo_flags }} -- -D warnings - - # Job to check that all the lint checks succeeded - clippy-check: - needs: - - clippy - runs-on: ubuntu-latest - if: always() - steps: - - run: jq --exit-status 'all(.result == "success")' <<< '${{ toJson(needs) }}' diff --git a/riscv-peripheral/.github/workflows/rust.yml b/riscv-peripheral/.github/workflows/rust.yml deleted file mode 100644 index 31000a27..00000000 --- a/riscv-peripheral/.github/workflows/rust.yml +++ /dev/null @@ -1,22 +0,0 @@ -name: Rust - -on: - push: - branches: [ "main" ] - pull_request: - branches: [ "main" ] - -env: - CARGO_TERM_COLOR: always - -jobs: - build: - - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v3 - - name: Build - run: cargo build --verbose - - name: Run tests - run: cargo test --verbose diff --git a/riscv-peripheral/.github/workflows/rustfmt.yml b/riscv-peripheral/.github/workflows/rustfmt.yml deleted file mode 100644 index 3d792dc7..00000000 --- a/riscv-peripheral/.github/workflows/rustfmt.yml +++ /dev/null @@ -1,18 +0,0 @@ -on: - push: - branches: [ main ] - pull_request: - merge_group: - -name: Code formatting check - -jobs: - rustfmt: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: dtolnay/rust-toolchain@stable - with: - components: rustfmt - - name: Run Rustfmt - run: cargo fmt --all -- --check --verbose diff --git a/riscv-peripheral/.gitignore b/riscv-peripheral/.gitignore deleted file mode 100644 index 6cfa27d8..00000000 --- a/riscv-peripheral/.gitignore +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Cargo -# will have compiled files and executables -debug/ -target/ - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html -Cargo.lock - -# These are backup files generated by rustfmt -**/*.rs.bk - -# MSVC Windows builds of rustc generate these, which store debugging information -*.pdb - -.DS_Store -.vscode/ diff --git a/riscv-peripheral/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 index 1eabf062..2b16c935 100644 --- a/riscv-peripheral/Cargo.toml +++ b/riscv-peripheral/Cargo.toml @@ -8,7 +8,8 @@ edition = "2021" [dependencies] embedded-hal = "1.0.0-rc.2" # embedded-hal-async = { version = "1.0.0-rc.1", optional = true } -riscv = { git = "https://github.com/rust-embedded/riscv", branch = "master" } +riscv = { path = "../riscv", version = "0.10" } +riscv-pac = { path = "../riscv-pac", version = "0.1.0" } [features] # hal-async = ["embedded-hal-async"] diff --git a/riscv-peripheral/README.md b/riscv-peripheral/README.md index fffd8329..289cdf96 100644 --- a/riscv-peripheral/README.md +++ b/riscv-peripheral/README.md @@ -1,8 +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` -> Standard RISC-V peripherals for embedded systems written in Rust +> 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.61 and up. It *might* compile with older versions but that may change in any new patch release. + +## License + +Copyright 2023-2024s [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 index df21cf11..c3f4be6c 100644 --- a/riscv-peripheral/examples/e310x.rs +++ b/riscv-peripheral/examples/e310x.rs @@ -1,7 +1,8 @@ -use riscv_peripheral::{ - aclint::HartIdNumber, - plic::{ContextNumber, InterruptNumber, PriorityNumber}, -}; +//! 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)] @@ -28,25 +29,6 @@ unsafe impl HartIdNumber for HartId { } } -unsafe impl ContextNumber for HartId { - const MAX_CONTEXT_NUMBER: u16 = 0; - - #[inline] - fn number(self) -> u16 { - self as _ - } - - #[inline] - fn from_number(number: u16) -> Result { - if number > Self::MAX_CONTEXT_NUMBER { - Err(number) - } else { - // SAFETY: valid context number - Ok(unsafe { core::mem::transmute(number) }) - } - } -} - #[derive(Copy, Clone, Debug, PartialEq, Eq)] #[repr(u16)] pub enum Interrupt { @@ -162,4 +144,9 @@ riscv_peripheral::clint_codegen!( msips [msip0=(HartId::H0,"`H0`")], ); +riscv_peripheral::plic_codegen!( + base 0x0C00_0000, + ctxs [ctx0=(HartId::H0,"`H0`")], +); + fn main() {} diff --git a/riscv-peripheral/src/aclint.rs b/riscv-peripheral/src/aclint.rs index c19b6446..5aa08239 100644 --- a/riscv-peripheral/src/aclint.rs +++ b/riscv-peripheral/src/aclint.rs @@ -7,32 +7,7 @@ pub mod mswi; pub mod mtimer; pub mod sswi; -/// Trait for enums of HART IDs in (A)CLINT peripherals. -/// -/// # Note -/// -/// If your target only has one HART (HART ID 0), you don't need to implement this trait. -/// Instead, you can access directly to the base registers through the `(A)CLINT` structs. -/// -/// # Safety -/// -/// * This trait must only be implemented on a PAC of a target with a PLIC peripheral. -/// * This trait must only be implemented on enums of HART IDs. -/// * Each enum variant must represent a distinct value (no duplicates are permitted). -/// * Each enum variant must always return the same value (do not change at runtime). -/// * All the HART ID numbers must be less than or equal to `MAX_HART_ID_NUMBER`. -/// * `MAX_HART_ID_NUMBER` must coincide with the highest allowed HART ID number. -pub unsafe trait HartIdNumber: Copy { - /// Highest number assigned to a HART ID. - const MAX_HART_ID_NUMBER: u16; - - /// Converts a HART Id to its corresponding number. - fn number(self) -> u16; - - /// Tries to convert a number to a valid HART ID. - /// If the conversion fails, it returns an error with the number back. - fn from_number(value: u16) -> Result; -} +pub use riscv_pac::HartIdNumber; // re-export useful riscv-pac traits /// Trait for a CLINT peripheral. /// diff --git a/riscv-peripheral/src/macros.rs b/riscv-peripheral/src/macros.rs index 38571dc2..16b28a6f 100644 --- a/riscv-peripheral/src/macros.rs +++ b/riscv-peripheral/src/macros.rs @@ -23,7 +23,7 @@ /// /// let mswi = CLINT::mswi(); // MSWI peripheral /// let mtimer = CLINT::mtimer(); // MTIMER peripheral -/// let delay = CLINT::delay(); // For the `embedded_hal::delay::DelayUs` and `embedded_hal_async::delay::DelayUs` traits +/// let delay = CLINT::delay(); // For the `embedded_hal::delay::DelayNs` trait /// ``` /// /// ## Base address and per-HART mtimecmp registers @@ -205,8 +205,7 @@ macro_rules! clint_codegen { /// /// # Note /// - /// You must export the `riscv_peripheral::hal::delay::DelayUs` trait in order to use delay methods. - /// You must export the `riscv_peripheral::hal_async::delay::DelayUs` trait in order to use async delay methods. + /// 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()) @@ -303,10 +302,10 @@ macro_rules! plic_codegen { $crate::plic::PLIC::::pendings() } - /// Returns the context proxy of a given PLIC context. + /// Returns the context proxy of a given PLIC HART context. #[inline] - pub fn ctx(context: C) -> $crate::plic::CTX { - $crate::plic::PLIC::::ctx(context) + pub fn ctx(hart_id: H) -> $crate::plic::CTX { + $crate::plic::PLIC::::ctx(hart_id) } } $crate::plic_codegen!($($tail)*); @@ -314,7 +313,7 @@ macro_rules! plic_codegen { (ctxs [$($fn:ident = ($ctx:expr , $sctx:expr)),+], $($tail:tt)*) => { impl PLIC { $( - #[doc = "Returns a PLIC context proxy for context "] + #[doc = "Returns a PLIC context proxy for context of HART "] #[doc = $sctx] #[doc = "."] #[inline] diff --git a/riscv-peripheral/src/plic.rs b/riscv-peripheral/src/plic.rs index 3185a77e..7f192718 100644 --- a/riscv-peripheral/src/plic.rs +++ b/riscv-peripheral/src/plic.rs @@ -8,92 +8,7 @@ pub mod pendings; pub mod priorities; pub mod threshold; -/// Trait for enums of interrupt numbers. -/// -/// This trait should be implemented by a peripheral access crate (PAC) -/// on its enum of available external interrupts for a specific device. -/// Each variant must convert to a `u16` of its interrupt number. -/// -/// # Note -/// -/// Recall that the interrupt number `0` is reserved as "no interrupt". -/// -/// # Safety -/// -/// * This trait must only be implemented on a PAC of a target with a PLIC peripheral. -/// * This trait must only be implemented on enums of external interrupts. -/// * Each enum variant must represent a distinct value (no duplicates are permitted), -/// * Each enum variant must always return the same value (do not change at runtime). -/// * All the interrupt numbers must be less than or equal to `MAX_INTERRUPT_NUMBER`. -/// * `MAX_INTERRUPT_NUMBER` must coincide with the highest allowed interrupt number. -pub unsafe trait InterruptNumber: Copy { - /// Highest number assigned to an interrupt source. - const MAX_INTERRUPT_NUMBER: u16; - - /// Converts an interrupt source to its corresponding number. - fn number(self) -> u16; - - /// Tries to convert a number to a valid interrupt source. - /// If the conversion fails, it returns an error with the number back. - fn from_number(value: u16) -> Result; -} - -/// Trait for enums of priority levels. -/// -/// This trait should be implemented by a peripheral access crate (PAC) -/// on its enum of available priority numbers for a specific device. -/// Each variant must convert to a `u8` of its priority level. -/// -/// # Note -/// -/// Recall that the priority number `0` is reserved as "never interrupt". -/// -/// # Safety -/// -/// * This trait must only be implemented on a PAC of a target with a PLIC peripheral. -/// * This trait must only be implemented on enums of priority levels. -/// * Each enum variant must represent a distinct value (no duplicates are permitted). -/// * Each enum variant must always return the same value (do not change at runtime). -/// * There must be a valid priority number set to 0 (i.e., never interrupt). -/// * All the priority level numbers must be less than or equal to `MAX_PRIORITY_NUMBER`. -/// * `MAX_PRIORITY_NUMBER` must coincide with the highest allowed priority number. -pub unsafe trait PriorityNumber: Copy { - /// Number assigned to the highest priority level. - const MAX_PRIORITY_NUMBER: u8; - - /// Converts a priority level to its corresponding number. - fn number(self) -> u8; - - /// Tries to convert a number to a valid priority level. - /// If the conversion fails, it returns an error with the number back. - fn from_number(value: u8) -> Result; -} - -/// Trait for enums of PLIC contexts. -/// -/// This trait should be implemented by a peripheral access crate (PAC) -/// on its enum of available contexts for a specific device. -/// Each variant must convert to a `u16` of its context number. -/// -/// # Safety -/// -/// * This trait must only be implemented on a PAC of a target with a PLIC peripheral. -/// * This trait must only be implemented on enums of contexts. -/// * Each enum variant must represent a distinct value (no duplicates are permitted), -/// * Each anum variant must always return the same value (do not change at runtime). -/// * All the context numbers must be less than or equal to `MAX_CONTEXT_NUMBER`. -/// * `MAX_CONTEXT_NUMBER` must coincide with the highest allowed context number. -pub unsafe trait ContextNumber: Copy { - /// Highest number assigned to a context. - const MAX_CONTEXT_NUMBER: u16; - - /// Converts an context to its corresponding number. - fn number(self) -> u16; - - /// Tries to convert a number to a valid context. - /// If the conversion fails, it returns an error with the number back. - fn from_number(value: u16) -> Result; -} +pub use riscv_pac::{HartIdNumber, InterruptNumber, PriorityNumber}; // re-export useful riscv-pac traits /// Trait for a PLIC peripheral. /// @@ -144,11 +59,11 @@ impl PLIC

{ unsafe { pendings::PENDINGS::new(P::BASE + Self::PENDINGS_OFFSET) } } - /// Returns a proxy to access to all the PLIC registers of a given context. + /// Returns a proxy to access to all the PLIC registers of a given HART context. #[inline] - pub fn ctx(context: C) -> CTX

{ + pub fn ctx(hart_id: H) -> CTX

{ // SAFETY: valid context number - unsafe { CTX::new(context.number()) } + unsafe { CTX::new(hart_id.number()) } } } @@ -216,7 +131,7 @@ impl CTX

{ #[cfg(test)] pub(crate) mod test { - use super::{ContextNumber, InterruptNumber, PriorityNumber}; + use super::{HartIdNumber, InterruptNumber, PriorityNumber}; #[derive(Clone, Copy, Debug, Eq, PartialEq)] #[repr(u16)] @@ -282,8 +197,8 @@ pub(crate) mod test { } } - unsafe impl ContextNumber for Context { - const MAX_CONTEXT_NUMBER: u16 = 2; + unsafe impl HartIdNumber for Context { + const MAX_HART_ID_NUMBER: u16 = 2; #[inline] fn number(self) -> u16 { @@ -292,7 +207,7 @@ pub(crate) mod test { #[inline] fn from_number(number: u16) -> Result { - if number > Self::MAX_CONTEXT_NUMBER { + if number > Self::MAX_HART_ID_NUMBER { Err(number) } else { // SAFETY: valid context number @@ -359,7 +274,7 @@ pub(crate) mod test { assert_eq!(priorities.address(), 0x0C00_0000); assert_eq!(pendings.address(), 0x0C00_1000); - for i in 0..=Context::MAX_CONTEXT_NUMBER { + for i in 0..=Context::MAX_HART_ID_NUMBER { let context = Context::from_number(i).unwrap(); let i = i as usize; From 82de9216f7a12a8542c02264bc18318bfa7802a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rom=C3=A1n=20C=C3=A1rdenas?= Date: Thu, 14 Dec 2023 14:20:11 +0100 Subject: [PATCH 18/25] store waker --- .github/workflows/clippy.yaml | 3 +- .github/workflows/riscv-peripheral.yaml | 10 ++++--- riscv-peripheral/Cargo.toml | 4 +-- riscv-peripheral/src/hal_async/aclint.rs | 37 +++++++++++++++++++----- riscv-peripheral/src/lib.rs | 4 +-- 5 files changed, 42 insertions(+), 16 deletions(-) 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 index c9a3c744..6e6959d3 100644 --- a/.github/workflows/riscv-peripheral.yaml +++ b/.github/workflows/riscv-peripheral.yaml @@ -33,8 +33,9 @@ jobs: 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 + # not yet, let's wait for 1.75.0 + # - 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: @@ -47,8 +48,9 @@ jobs: - 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 + # not yet, let's wait for 1.75.0 + # - name: Build (all features) + # run: cargo test --package riscv-peripheral --all-features # Job to check that all the builds succeeded build-check: diff --git a/riscv-peripheral/Cargo.toml b/riscv-peripheral/Cargo.toml index 2b16c935..de322887 100644 --- a/riscv-peripheral/Cargo.toml +++ b/riscv-peripheral/Cargo.toml @@ -7,12 +7,12 @@ edition = "2021" [dependencies] embedded-hal = "1.0.0-rc.2" -# embedded-hal-async = { version = "1.0.0-rc.1", optional = true } +embedded-hal-async = { version = "1.0.0-rc.2", optional = true } riscv = { path = "../riscv", version = "0.10" } riscv-pac = { path = "../riscv-pac", version = "0.1.0" } [features] -# hal-async = ["embedded-hal-async"] +hal-async = ["embedded-hal-async"] [package.metadata.docs.rs] default-target = "riscv64imac-unknown-none-elf" diff --git a/riscv-peripheral/src/hal_async/aclint.rs b/riscv-peripheral/src/hal_async/aclint.rs index 03af1250..6f08b424 100644 --- a/riscv-peripheral/src/hal_async/aclint.rs +++ b/riscv-peripheral/src/hal_async/aclint.rs @@ -2,23 +2,29 @@ use crate::aclint::mtimer::MTIME; pub use crate::hal::aclint::Delay; -pub use crate::hal_async::delay::DelayUs; +pub use crate::hal_async::delay::DelayNs; use core::{ future::Future, pin::Pin, - task::{Context, Poll}, + task::{Context, Poll, Waker}, }; struct DelayAsync { mtime: MTIME, t0: u64, n_ticks: u64, + waker: Option, } impl DelayAsync { pub fn new(mtime: MTIME, n_ticks: u64) -> Self { let t0 = mtime.read(); - Self { mtime, t0, n_ticks } + Self { + mtime, + t0, + n_ticks, + waker: None, + } } } @@ -26,15 +32,32 @@ impl Future for DelayAsync { type Output = (); #[inline] - fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll { + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { match self.mtime.read().wrapping_sub(self.t0) < self.n_ticks { - true => Poll::Pending, - false => Poll::Ready(()), + true => { + self.get_mut().waker = Some(cx.waker().clone()); + Poll::Pending + } + false => { + if let Some(waker) = self.get_mut().waker.take() { + waker.wake(); + } else { + // corner case: delay expired before polling for the first time + cx.waker().wake_by_ref(); + }; + Poll::Ready(()) + } } } } -impl DelayUs for Delay { +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.get_mtime(), 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; diff --git a/riscv-peripheral/src/lib.rs b/riscv-peripheral/src/lib.rs index c0ada40f..8bfd17b3 100644 --- a/riscv-peripheral/src/lib.rs +++ b/riscv-peripheral/src/lib.rs @@ -7,8 +7,8 @@ pub use riscv; // re-export riscv crate to allow macros to use it pub mod common; // common definitions for all peripherals pub mod hal; // trait implementations for embedded-hal - // #[cfg(feature = "hal-async")] - // pub mod hal_async; // async trait implementations for embedded-hal +#[cfg(feature = "hal-async")] +pub mod hal_async; // async trait implementations for embedded-hal pub mod macros; // macros for easing the definition of peripherals in PACs pub mod aclint; // ACLINT and CLINT peripherals From 552063c5507ff2a0c8d543989be8f68628dd4e81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rom=C3=A1n=20C=C3=A1rdenas?= Date: Fri, 15 Dec 2023 12:41:50 +0100 Subject: [PATCH 19/25] Interrupt-driven async HAL implementation --- riscv-peripheral/Cargo.toml | 6 +- riscv-peripheral/examples/e310x.rs | 10 + riscv-peripheral/src/aclint/mswi.rs | 13 ++ riscv-peripheral/src/aclint/mtimer.rs | 13 ++ riscv-peripheral/src/hal_async.rs | 1 + riscv-peripheral/src/hal_async/aclint.rs | 248 ++++++++++++++++++++--- riscv-peripheral/src/lib.rs | 2 +- riscv-peripheral/src/macros.rs | 39 ++++ riscv-peripheral/src/plic.rs | 13 ++ 9 files changed, 313 insertions(+), 32 deletions(-) diff --git a/riscv-peripheral/Cargo.toml b/riscv-peripheral/Cargo.toml index de322887..d18803bd 100644 --- a/riscv-peripheral/Cargo.toml +++ b/riscv-peripheral/Cargo.toml @@ -6,13 +6,13 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -embedded-hal = "1.0.0-rc.2" -embedded-hal-async = { version = "1.0.0-rc.2", optional = true } +embedded-hal = "1.0.0-rc.3" +embedded-hal-async = { version = "1.0.0-rc.3", optional = true } riscv = { path = "../riscv", version = "0.10" } riscv-pac = { path = "../riscv-pac", version = "0.1.0" } [features] -hal-async = ["embedded-hal-async"] +aclint-hal-async = ["embedded-hal-async"] [package.metadata.docs.rs] default-target = "riscv64imac-unknown-none-elf" diff --git a/riscv-peripheral/examples/e310x.rs b/riscv-peripheral/examples/e310x.rs index c3f4be6c..7b80cb84 100644 --- a/riscv-peripheral/examples/e310x.rs +++ b/riscv-peripheral/examples/e310x.rs @@ -137,6 +137,16 @@ unsafe impl PriorityNumber for Priority { } } +#[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, diff --git a/riscv-peripheral/src/aclint/mswi.rs b/riscv-peripheral/src/aclint/mswi.rs index 3af4af75..4e85121a 100644 --- a/riscv-peripheral/src/aclint/mswi.rs +++ b/riscv-peripheral/src/aclint/mswi.rs @@ -35,6 +35,19 @@ impl MSWI { // 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); diff --git a/riscv-peripheral/src/aclint/mtimer.rs b/riscv-peripheral/src/aclint/mtimer.rs index 71445675..5e5e1c1c 100644 --- a/riscv-peripheral/src/aclint/mtimer.rs +++ b/riscv-peripheral/src/aclint/mtimer.rs @@ -37,6 +37,19 @@ impl MTIMER { // 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. diff --git a/riscv-peripheral/src/hal_async.rs b/riscv-peripheral/src/hal_async.rs index 5cd093c8..fdb657e9 100644 --- a/riscv-peripheral/src/hal_async.rs +++ b/riscv-peripheral/src/hal_async.rs @@ -2,4 +2,5 @@ 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 index 6f08b424..b53a79a9 100644 --- a/riscv-peripheral/src/hal_async/aclint.rs +++ b/riscv-peripheral/src/hal_async/aclint.rs @@ -1,29 +1,216 @@ //! Asynchronous delay implementation for the (A)CLINT peripheral. -use crate::aclint::mtimer::MTIME; -pub use crate::hal::aclint::Delay; +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}, }; -struct DelayAsync { +extern "Rust" { + /// Returns the `MTIMER` register for the given HART ID. + /// This is necessary for [`MachineExternal`] to obtain the corresponding `MTIMER` register. + /// + /// # Safety + /// + /// Do not call this function directly. It is only meant to be called by [`MachineExternal`]. + fn _riscv_peripheral_aclint_mtimer(hart_id: usize) -> MTIMER; + + /// Tries to push a new timer to the 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. + /// + /// # Note + /// + /// the [`Delay`] reference allows to access the `MTIME` and `MTIMECMP` registers, + /// as well as handy information such as the HART ID or the clock frequency of the `MTIMER` peripheral. + /// + /// # Safety + /// + /// Do not call this function directly. It is only meant to be called by [`DelayAsync`]. + fn _riscv_peripheral_push_timer(hart_id: usize, delay: &Delay, t: Timer) -> Result<(), Timer>; + + /// Pops a expired timer from the timer queue assigned to the given HART ID. + /// If the queue is empty, it returns `Err(None)`. + /// Alternatively, if the queue is not empty but the earliest timer has not expired yet, + /// it returns `Err(Some(next_expires))` where `next_expires` is the tick at which this timer expires. + /// + /// # Safety + /// + /// It is extremely important that this function only returns a timer that has expired. + /// Otherwise, the timer will be lost and the waker will never be called. + /// + /// Do not call this function directly. It is only meant to be called by [`MachineExternal`] and [`DelayAsync`]. + fn _riscv_peripheral_pop_timer(hart_id: usize, current_tick: u64) + -> Result>; +} + +/// Machine-level timer interrupt handler. +/// This handler is triggered whenever the `MTIME` register reaches the value of the `MTIMECMP` register. +#[no_mangle] +#[allow(non_snake_case)] +fn MachineExternal() { + let hart_id = riscv::register::mhartid::read(); + let mtimer = unsafe { _riscv_peripheral_aclint_mtimer(hart_id) }; + let (mtime, mtimercmp) = (mtimer.mtime, mtimer.mtimecmp_mhartid()); + schedule_machine_external(hart_id, mtime, mtimercmp); +} + +fn schedule_machine_external(hart_id: usize, mtime: MTIME, mtimercmp: MTIMECMP) { + unsafe { riscv::register::mie::clear_mtimer() }; // disable machine timer interrupts to avoid reentrancy + loop { + let current_tick = mtime.read(); + let timer = unsafe { _riscv_peripheral_pop_timer(hart_id, current_tick) }; + match timer { + Ok(timer) => { + debug_assert!(timer.expires() <= current_tick); + timer.wake(); + } + Err(e) => { + if let Some(next_expires) = e { + 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 again + } else { + mtimercmp.write(u64::MAX); // write max to clear and "disable" the interrupt + } + break; + } + } + } +} + +/// Asynchronous delay implementation for (A)CLINT peripherals. +#[derive(Clone)] +pub struct Delay { mtime: MTIME, - t0: u64, - n_ticks: u64, - waker: Option, + hart_id: usize, + mtimecmp: MTIMECMP, + freq: usize, +} + +impl Delay { + /// Creates a new `Delay` instance. + #[inline] + pub fn new(mtimer: MTIMER, hart_id: H, freq: usize) -> Self { + Self { + mtime: mtimer.mtime, + hart_id: hart_id.number() as _, + mtimecmp: mtimer.mtimecmp(hart_id), + freq, + } + } + + /// Creates a new `Delay` instance for the current HART. + /// This function determines the current HART ID by reading the [`riscv::register::mhartid`] CSR. + /// + /// # Note + /// + /// This function can only be used in M-mode. For S-mode, use [`Delay::new_mhartid`] instead. + #[inline] + pub fn new_mhartid(mtimer: MTIMER, freq: usize) -> Self { + let hart_id = riscv::register::mhartid::read(); + Self { + mtime: mtimer.mtime, + hart_id, + mtimecmp: mtimer.mtimecmp_mhartid(), + 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 + } + + /// Returns the `MTIMECMP` register. + #[inline] + pub const fn get_mtimecmp(&self) -> MTIMECMP { + self.mtimecmp + } + + /// Returns the hart ID. + #[inline] + pub const fn get_hart_id(&self) -> usize { + self.hart_id + } +} + +/// Timer queue entry. +#[derive(Debug)] +pub struct Timer { + expires: u64, + waker: Waker, +} + +impl Timer { + /// Creates a new timer queue entry. + #[inline] + pub fn new(expires: u64, waker: Waker) -> Self { + Self { expires, waker } + } + + /// Returns the tick at which the timer expires. + #[inline] + pub const fn expires(&self) -> u64 { + self.expires + } + + /// Wakes the waker associated with this timer. + #[inline] + pub fn wake(&self) { + self.waker.wake_by_ref(); + } +} + +impl PartialEq for Timer { + fn eq(&self, other: &Self) -> bool { + 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 { + delay: Delay, + expires: u64, + pushed: bool, } impl DelayAsync { - pub fn new(mtime: MTIME, n_ticks: u64) -> Self { - let t0 = mtime.read(); + pub fn new(delay: Delay, n_ticks: u64) -> Self { + let t0 = delay.mtime.read(); + let expires = t0.wrapping_add(n_ticks); Self { - mtime, - t0, - n_ticks, - waker: None, + delay, + expires, + pushed: false, } } } @@ -32,21 +219,26 @@ impl Future for DelayAsync { type Output = (); #[inline] - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - match self.mtime.read().wrapping_sub(self.t0) < self.n_ticks { - true => { - self.get_mut().waker = Some(cx.waker().clone()); - Poll::Pending - } - false => { - if let Some(waker) = self.get_mut().waker.take() { - waker.wake(); - } else { - // corner case: delay expired before polling for the first time - cx.waker().wake_by_ref(); + 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.expires, cx.waker().clone()); + unsafe { + _riscv_peripheral_push_timer(self.delay.hart_id, &self.delay, timer) + .expect("timer queue is full"); }; - Poll::Ready(()) + // we also need to schedule the interrupt if the timer we just pushed is the earliest one + schedule_machine_external( + self.delay.hart_id, + self.delay.mtime, + self.delay.mtimecmp, + ); } + Poll::Pending + } else { + Poll::Ready(()) } } } @@ -55,18 +247,18 @@ 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.get_mtime(), n_ticks).await; + DelayAsync::new(self.clone(), 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.get_mtime(), n_ticks).await; + DelayAsync::new(self.clone(), n_ticks).await; } #[inline] async fn delay_ms(&mut self, ms: u32) { let n_ticks = ms as u64 * self.get_freq() as u64 / 1_000; - DelayAsync::new(self.get_mtime(), n_ticks).await; + DelayAsync::new(self.clone(), n_ticks).await; } } diff --git a/riscv-peripheral/src/lib.rs b/riscv-peripheral/src/lib.rs index 8bfd17b3..38d6be0a 100644 --- a/riscv-peripheral/src/lib.rs +++ b/riscv-peripheral/src/lib.rs @@ -7,7 +7,7 @@ pub use riscv; // re-export riscv crate to allow macros to use it pub mod common; // common definitions for all peripherals pub mod hal; // trait implementations for embedded-hal -#[cfg(feature = "hal-async")] +#[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 diff --git a/riscv-peripheral/src/macros.rs b/riscv-peripheral/src/macros.rs index 16b28a6f..6f53b70b 100644 --- a/riscv-peripheral/src/macros.rs +++ b/riscv-peripheral/src/macros.rs @@ -213,6 +213,34 @@ macro_rules! clint_codegen { } $crate::clint_codegen!($($tail)*); }; + (async_delay, $($tail:tt)*) => { + impl CLINT { + /// Asynchronous delay implementation for CLINT peripherals. + /// You must specify which HART ID you want to use for the delay. + /// + /// # Note + /// + /// You must export the `riscv_peripheral::hal_async::delay::DelayNs` trait in order to use delay methods. + #[inline] + pub fn async_delay(hart_id: H) -> $crate::hal_async::aclint::Delay { + $crate::hal_async::aclint::Delay::new(Self::mtimer(), hart_id, Self::freq()) + } + + /// Asynchronous delay implementation for CLINT peripherals. + /// This function determines the current HART ID by reading the [`riscv::register::mhartid`] CSR. + /// + /// # Note + /// + /// You must export the `riscv_peripheral::hal_async::delay::DelayNs` trait in order to use delay methods. + /// + /// This function can only be used in M-mode. For S-mode, use [`CLINT::async_delay`] instead. + #[inline] + pub fn async_delay_mhartid() -> $crate::hal_async::aclint::Delay { + $crate::hal_async::aclint::Delay::new_mhartid(Self::mtimer(), Self::freq()) + } + } + $crate::clint_codegen!($($tail)*); + }; (msips [$($fn:ident = ($hart:expr , $shart:expr)),+], $($tail:tt)*) => { impl CLINT { $( @@ -307,6 +335,17 @@ macro_rules! plic_codegen { 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)*); }; diff --git a/riscv-peripheral/src/plic.rs b/riscv-peripheral/src/plic.rs index 7f192718..0619b0f7 100644 --- a/riscv-peripheral/src/plic.rs +++ b/riscv-peripheral/src/plic.rs @@ -65,6 +65,19 @@ impl PLIC

{ // 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. From 5b012c0978e16e00781fbb3d2757cba8a7c4e4d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rom=C3=A1n=20C=C3=A1rdenas?= Date: Tue, 19 Dec 2023 13:47:14 +0100 Subject: [PATCH 20/25] more flexible async implementation --- riscv-peripheral/Cargo.toml | 1 + riscv-peripheral/src/hal_async/aclint.rs | 231 ++++++++++++----------- riscv-peripheral/src/lib.rs | 7 +- riscv-peripheral/src/macros.rs | 21 +-- 4 files changed, 139 insertions(+), 121 deletions(-) diff --git a/riscv-peripheral/Cargo.toml b/riscv-peripheral/Cargo.toml index d18803bd..1903ec40 100644 --- a/riscv-peripheral/Cargo.toml +++ b/riscv-peripheral/Cargo.toml @@ -15,6 +15,7 @@ riscv-pac = { path = "../riscv-pac", version = "0.1.0" } 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", diff --git a/riscv-peripheral/src/hal_async/aclint.rs b/riscv-peripheral/src/hal_async/aclint.rs index b53a79a9..088805e9 100644 --- a/riscv-peripheral/src/hal_async/aclint.rs +++ b/riscv-peripheral/src/hal_async/aclint.rs @@ -1,4 +1,22 @@ //! 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(hart_id: usize, current_tick: u64) -> Option`: +//! This function pops all the expired timers from a timer queue assigned to the given 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; @@ -21,101 +39,76 @@ extern "Rust" { /// Tries to push a new timer to the 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. /// - /// # Note - /// - /// the [`Delay`] reference allows to access the `MTIME` and `MTIMECMP` registers, - /// as well as handy information such as the HART ID or the clock frequency of the `MTIMER` peripheral. - /// /// # Safety /// /// Do not call this function directly. It is only meant to be called by [`DelayAsync`]. - fn _riscv_peripheral_push_timer(hart_id: usize, delay: &Delay, t: Timer) -> Result<(), Timer>; + fn _riscv_peripheral_aclint_push_timer(t: Timer) -> Result<(), Timer>; - /// Pops a expired timer from the timer queue assigned to the given HART ID. - /// If the queue is empty, it returns `Err(None)`. + /// Pops all the expired timers from the timer queue assigned to the given 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 `Err(Some(next_expires))` where `next_expires` is the tick at which this timer expires. + /// it returns `Some(next_expires)` where `next_expires` is the tick at which this timer expires. /// /// # Safety /// - /// It is extremely important that this function only returns a timer that has expired. - /// Otherwise, the timer will be lost and the waker will never be called. - /// /// Do not call this function directly. It is only meant to be called by [`MachineExternal`] and [`DelayAsync`]. - fn _riscv_peripheral_pop_timer(hart_id: usize, current_tick: u64) - -> Result>; + fn _riscv_peripheral_aclint_wake_timers(hart_id: usize, current_tick: u64) -> Option; } -/// Machine-level timer interrupt handler. -/// This handler is triggered whenever the `MTIME` register reaches the value of the `MTIMECMP` register. +/// 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 MachineExternal() { + // recover the MTIME and MTIMECMP registers for the current HART let hart_id = riscv::register::mhartid::read(); let mtimer = unsafe { _riscv_peripheral_aclint_mtimer(hart_id) }; let (mtime, mtimercmp) = (mtimer.mtime, mtimer.mtimecmp_mhartid()); - schedule_machine_external(hart_id, mtime, mtimercmp); + // schedule the next machine timer interrupt + schedule_machine_timer(hart_id, mtime, mtimercmp); } -fn schedule_machine_external(hart_id: usize, mtime: MTIME, mtimercmp: MTIMECMP) { +/// Schedules the next machine timer interrupt for the given HART ID according to the timer queue. +fn schedule_machine_timer(hart_id: usize, mtime: MTIME, mtimercmp: MTIMECMP) { unsafe { riscv::register::mie::clear_mtimer() }; // disable machine timer interrupts to avoid reentrancy - loop { - let current_tick = mtime.read(); - let timer = unsafe { _riscv_peripheral_pop_timer(hart_id, current_tick) }; - match timer { - Ok(timer) => { - debug_assert!(timer.expires() <= current_tick); - timer.wake(); - } - Err(e) => { - if let Some(next_expires) = e { - 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 again - } else { - mtimercmp.write(u64::MAX); // write max to clear and "disable" the interrupt - } - break; - } - } + let current_tick = mtime.read(); + if let Some(next_expires) = + unsafe { _riscv_peripheral_aclint_wake_timers(hart_id, 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 again if necessary } } /// 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 { - mtime: MTIME, hart_id: usize, - mtimecmp: MTIMECMP, freq: usize, + mtime: MTIME, + mtimecmp: MTIMECMP, } impl Delay { - /// Creates a new `Delay` instance. - #[inline] - pub fn new(mtimer: MTIMER, hart_id: H, freq: usize) -> Self { - Self { - mtime: mtimer.mtime, - hart_id: hart_id.number() as _, - mtimecmp: mtimer.mtimecmp(hart_id), - freq, - } - } - /// Creates a new `Delay` instance for the current HART. - /// This function determines the current HART ID by reading the [`riscv::register::mhartid`] CSR. - /// - /// # Note - /// - /// This function can only be used in M-mode. For S-mode, use [`Delay::new_mhartid`] instead. #[inline] - pub fn new_mhartid(mtimer: MTIMER, freq: usize) -> Self { + pub fn new(freq: usize) -> Self { let hart_id = riscv::register::mhartid::read(); + let mtimer = unsafe { _riscv_peripheral_aclint_mtimer(hart_id) }; + let (mtime, mtimecmp) = (mtimer.mtime, mtimer.mtimecmp_mhartid()); Self { - mtime: mtimer.mtime, hart_id, - mtimecmp: mtimer.mtimecmp_mhartid(), freq, + mtime, + mtimecmp, } } @@ -130,29 +123,37 @@ impl Delay { pub fn set_freq(&mut self, freq: usize) { self.freq = freq; } +} - /// Returns the `MTIME` register. +impl DelayNs for Delay { #[inline] - pub const fn get_mtime(&self) -> MTIME { - self.mtime + 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; } - /// Returns the `MTIMECMP` register. #[inline] - pub const fn get_mtimecmp(&self) -> MTIMECMP { - self.mtimecmp + 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; } - /// Returns the hart ID. #[inline] - pub const fn get_hart_id(&self) -> usize { - self.hart_id + 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 { + hart_id: usize, + freq: usize, + mtime: MTIME, + mtimecmp: MTIMECMP, expires: u64, waker: Waker, } @@ -160,8 +161,46 @@ pub struct Timer { impl Timer { /// Creates a new timer queue entry. #[inline] - pub fn new(expires: u64, waker: Waker) -> Self { - Self { expires, waker } + const fn new( + hart_id: usize, + freq: usize, + mtime: MTIME, + mtimecmp: MTIMECMP, + expires: u64, + waker: Waker, + ) -> Self { + Self { + hart_id, + freq, + mtime, + mtimecmp, + expires, + waker, + } + } + + /// Returns the HART ID associated with this timer. + #[inline] + pub const fn hart_id(&self) -> usize { + self.hart_id + } + + /// 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. @@ -170,16 +209,16 @@ impl Timer { self.expires } - /// Wakes the waker associated with this timer. + /// Returns the waker associated with this timer. #[inline] - pub fn wake(&self) { - self.waker.wake_by_ref(); + pub fn waker(&self) -> Waker { + self.waker.clone() } } impl PartialEq for Timer { fn eq(&self, other: &Self) -> bool { - self.expires == other.expires + self.hart_id == other.hart_id && self.freq == other.freq && self.expires == other.expires } } @@ -197,14 +236,14 @@ impl PartialOrd for Timer { } } -struct DelayAsync { - delay: Delay, +struct DelayAsync<'a> { + delay: &'a Delay, expires: u64, pushed: bool, } -impl DelayAsync { - pub fn new(delay: Delay, n_ticks: u64) -> Self { +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 { @@ -215,7 +254,7 @@ impl DelayAsync { } } -impl Future for DelayAsync { +impl<'a> Future for DelayAsync<'a> { type Output = (); #[inline] @@ -224,17 +263,19 @@ impl Future for DelayAsync { if !self.pushed { // we only push the timer to the queue the first time we poll self.pushed = true; - let timer = Timer::new(self.expires, cx.waker().clone()); - unsafe { - _riscv_peripheral_push_timer(self.delay.hart_id, &self.delay, timer) - .expect("timer queue is full"); - }; - // we also need to schedule the interrupt if the timer we just pushed is the earliest one - schedule_machine_external( + let timer = Timer::new( self.delay.hart_id, + 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.hart_id, self.delay.mtime, self.delay.mtimecmp); } Poll::Pending } else { @@ -242,23 +283,3 @@ impl Future for DelayAsync { } } } - -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.clone(), 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.clone(), 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.clone(), n_ticks).await; - } -} diff --git a/riscv-peripheral/src/lib.rs b/riscv-peripheral/src/lib.rs index 38d6be0a..f582f249 100644 --- a/riscv-peripheral/src/lib.rs +++ b/riscv-peripheral/src/lib.rs @@ -1,4 +1,9 @@ -//! Standard RISC-V peripherals for embedded systems written in Rust +//! 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] diff --git a/riscv-peripheral/src/macros.rs b/riscv-peripheral/src/macros.rs index 6f53b70b..f4c6e8b4 100644 --- a/riscv-peripheral/src/macros.rs +++ b/riscv-peripheral/src/macros.rs @@ -216,27 +216,18 @@ macro_rules! clint_codegen { (async_delay, $($tail:tt)*) => { impl CLINT { /// Asynchronous delay implementation for CLINT peripherals. - /// You must specify which HART ID you want to use for the delay. - /// - /// # Note - /// - /// You must export the `riscv_peripheral::hal_async::delay::DelayNs` trait in order to use delay methods. - #[inline] - pub fn async_delay(hart_id: H) -> $crate::hal_async::aclint::Delay { - $crate::hal_async::aclint::Delay::new(Self::mtimer(), hart_id, Self::freq()) - } - - /// Asynchronous delay implementation for CLINT peripherals. - /// This function determines the current HART ID by reading the [`riscv::register::mhartid`] CSR. /// /// # Note /// /// You must export the `riscv_peripheral::hal_async::delay::DelayNs` trait in order to use delay methods. /// - /// This function can only be used in M-mode. For S-mode, use [`CLINT::async_delay`] instead. + /// 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_mhartid() -> $crate::hal_async::aclint::Delay { - $crate::hal_async::aclint::Delay::new_mhartid(Self::mtimer(), Self::freq()) + pub fn async_delay() -> $crate::hal_async::aclint::Delay { + $crate::hal_async::aclint::Delay::new(Self::freq()) } } $crate::clint_codegen!($($tail)*); From e29948c8c8209dbd3b57dea7a725e02e14ac6a85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rom=C3=A1n=20C=C3=A1rdenas?= Date: Wed, 10 Jan 2024 19:45:46 +0100 Subject: [PATCH 21/25] use e-h-1 --- riscv-peripheral/Cargo.toml | 4 ++-- riscv-semihosting/CHANGELOG.md | 1 + riscv-semihosting/src/lib.rs | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/riscv-peripheral/Cargo.toml b/riscv-peripheral/Cargo.toml index 1903ec40..8c674775 100644 --- a/riscv-peripheral/Cargo.toml +++ b/riscv-peripheral/Cargo.toml @@ -6,8 +6,8 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -embedded-hal = "1.0.0-rc.3" -embedded-hal-async = { version = "1.0.0-rc.3", optional = true } +embedded-hal = "1.0.0" +embedded-hal-async = { version = "1.0.0", optional = true } riscv = { path = "../riscv", version = "0.10" } riscv-pac = { path = "../riscv-pac", version = "0.1.0" } diff --git a/riscv-semihosting/CHANGELOG.md b/riscv-semihosting/CHANGELOG.md index 64a1a597..1666f91b 100644 --- a/riscv-semihosting/CHANGELOG.md +++ b/riscv-semihosting/CHANGELOG.md @@ -5,6 +5,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] +- Address clippy warnings - Add recommendation for `semihosting` in README.md. - Bug fixes - Moved to the `riscv` Cargo workspace diff --git a/riscv-semihosting/src/lib.rs b/riscv-semihosting/src/lib.rs index 7a22b89a..5a413ff9 100644 --- a/riscv-semihosting/src/lib.rs +++ b/riscv-semihosting/src/lib.rs @@ -216,7 +216,7 @@ pub unsafe fn syscall1(_nr: usize, _arg: usize) -> usize { #[cfg(all(riscv, not(feature = "no-semihosting")))] () => { let mut nr = _nr; - let mut arg = _arg; + let arg = _arg; // The instructions below must always be uncompressed, otherwise // it will be treated as a regular break, hence the norvc option. // From 2531e682f3b12c73a55002530138abcd0b078a67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rom=C3=A1n=20C=C3=A1rdenas?= Date: Mon, 15 Jan 2024 11:48:33 +0100 Subject: [PATCH 22/25] MSRV is now 1.75 --- .github/workflows/riscv-peripheral.yaml | 14 ++++++-------- riscv-pac/CHANGELOG.md | 2 ++ riscv-pac/Cargo.toml | 2 +- riscv-pac/README.md | 2 +- riscv-peripheral/Cargo.toml | 2 +- riscv-peripheral/README.md | 4 ++-- riscv-peripheral/src/hal_async/aclint.rs | 8 ++++---- 7 files changed, 17 insertions(+), 17 deletions(-) diff --git a/.github/workflows/riscv-peripheral.yaml b/.github/workflows/riscv-peripheral.yaml index 6e6959d3..998f8bce 100644 --- a/.github/workflows/riscv-peripheral.yaml +++ b/.github/workflows/riscv-peripheral.yaml @@ -11,8 +11,8 @@ jobs: build-riscv: strategy: matrix: - # All generated code should be running on stable now, MRSV is 1.61.0 - toolchain: [ stable, nightly, 1.61.0 ] + # 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 @@ -33,9 +33,8 @@ jobs: targets: ${{ matrix.target }} - name: Build (no features) run: cargo build --package riscv-peripheral --target ${{ matrix.target }} - # not yet, let's wait for 1.75.0 - # - name: Build (all features) - # run: cargo build --package riscv-peripheral --target ${{ matrix.target }} --all-features + - 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: @@ -48,9 +47,8 @@ jobs: - uses: dtolnay/rust-toolchain@stable - name: Build (no features) run: cargo test --package riscv-peripheral - # not yet, let's wait for 1.75.0 - # - name: Build (all features) - # run: cargo test --package riscv-peripheral --all-features + - name: Build (all features) + run: cargo test --package riscv-peripheral --all-features # Job to check that all the builds succeeded build-check: diff --git a/riscv-pac/CHANGELOG.md b/riscv-pac/CHANGELOG.md index abf386ab..e804f61e 100644 --- a/riscv-pac/CHANGELOG.md +++ b/riscv-pac/CHANGELOG.md @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] +- Fix crates.io badge links + ## [v0.1.0] - 2024-01-14 ### Added 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 9e96ac74..fada9925 100644 --- a/riscv-pac/README.md +++ b/riscv-pac/README.md @@ -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/Cargo.toml b/riscv-peripheral/Cargo.toml index 8c674775..6da7a9fa 100644 --- a/riscv-peripheral/Cargo.toml +++ b/riscv-peripheral/Cargo.toml @@ -8,7 +8,7 @@ edition = "2021" [dependencies] embedded-hal = "1.0.0" embedded-hal-async = { version = "1.0.0", optional = true } -riscv = { path = "../riscv", version = "0.10" } +riscv = { path = "../riscv", version = "0.11.0" } riscv-pac = { path = "../riscv-pac", version = "0.1.0" } [features] diff --git a/riscv-peripheral/README.md b/riscv-peripheral/README.md index 289cdf96..940f4bef 100644 --- a/riscv-peripheral/README.md +++ b/riscv-peripheral/README.md @@ -11,12 +11,12 @@ This project is developed and maintained by the [RISC-V team][team]. ## Minimum Supported Rust Version (MSRV) -This crate is guaranteed to compile on stable Rust 1.61 and up. It *might* +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-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/src/hal_async/aclint.rs b/riscv-peripheral/src/hal_async/aclint.rs index 088805e9..aa425b10 100644 --- a/riscv-peripheral/src/hal_async/aclint.rs +++ b/riscv-peripheral/src/hal_async/aclint.rs @@ -29,11 +29,11 @@ use core::{ extern "Rust" { /// Returns the `MTIMER` register for the given HART ID. - /// This is necessary for [`MachineExternal`] to obtain the corresponding `MTIMER` register. + /// 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 [`MachineExternal`]. + /// Do not call this function directly. It is only meant to be called by [`MachineTimer`]. fn _riscv_peripheral_aclint_mtimer(hart_id: usize) -> MTIMER; /// Tries to push a new timer to the timer queue assigned to the given HART ID. @@ -51,7 +51,7 @@ extern "Rust" { /// /// # Safety /// - /// Do not call this function directly. It is only meant to be called by [`MachineExternal`] and [`DelayAsync`]. + /// Do not call this function directly. It is only meant to be called by [`MachineTimer`] and [`DelayAsync`]. fn _riscv_peripheral_aclint_wake_timers(hart_id: usize, current_tick: u64) -> Option; } @@ -59,7 +59,7 @@ extern "Rust" { /// register reaches the value of the `MTIMECMP` register of the current HART. #[no_mangle] #[allow(non_snake_case)] -fn MachineExternal() { +fn MachineTimer() { // recover the MTIME and MTIMECMP registers for the current HART let hart_id = riscv::register::mhartid::read(); let mtimer = unsafe { _riscv_peripheral_aclint_mtimer(hart_id) }; From f4e29c480c816e46d642e109e87764f226019f5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rom=C3=A1n=20C=C3=A1rdenas?= Date: Mon, 15 Jan 2024 12:26:30 +0100 Subject: [PATCH 23/25] async stuff (WIP) --- riscv-peripheral/src/hal_async/aclint.rs | 28 +++++++++++------------- riscv-peripheral/src/macros.rs | 6 +++++ 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/riscv-peripheral/src/hal_async/aclint.rs b/riscv-peripheral/src/hal_async/aclint.rs index aa425b10..dc84f11b 100644 --- a/riscv-peripheral/src/hal_async/aclint.rs +++ b/riscv-peripheral/src/hal_async/aclint.rs @@ -11,11 +11,12 @@ //! 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. +//! This function is implemented by the [`crate::clint_codegen`] macro when asyn_delay is provided. //! - `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(hart_id: usize, current_tick: u64) -> Option`: -//! This function pops all the expired timers from a timer queue assigned to the given HART ID and wakes their associated wakers. +//! - `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}; @@ -28,13 +29,13 @@ use core::{ }; extern "Rust" { - /// Returns the `MTIMER` register for the given HART ID. + /// 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(hart_id: usize) -> MTIMER; + fn _riscv_peripheral_aclint_mtimer() -> MTIMER; /// Tries to push a new timer to the 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. @@ -44,7 +45,7 @@ extern "Rust" { /// 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 given HART ID and wakes their associated wakers. + /// Pops all the expired timers from the timer queue assigned to 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. @@ -52,7 +53,7 @@ extern "Rust" { /// # Safety /// /// Do not call this function directly. It is only meant to be called by [`MachineTimer`] and [`DelayAsync`]. - fn _riscv_peripheral_aclint_wake_timers(hart_id: usize, current_tick: u64) -> Option; + fn _riscv_peripheral_aclint_wake_timers(current_tick: u64) -> Option; } /// Machine-level timer interrupt handler. This handler is triggered whenever the `MTIME` @@ -61,20 +62,17 @@ extern "Rust" { #[allow(non_snake_case)] fn MachineTimer() { // recover the MTIME and MTIMECMP registers for the current HART - let hart_id = riscv::register::mhartid::read(); - let mtimer = unsafe { _riscv_peripheral_aclint_mtimer(hart_id) }; + let mtimer = unsafe { _riscv_peripheral_aclint_mtimer() }; let (mtime, mtimercmp) = (mtimer.mtime, mtimer.mtimecmp_mhartid()); // schedule the next machine timer interrupt - schedule_machine_timer(hart_id, mtime, mtimercmp); + 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(hart_id: usize, mtime: MTIME, mtimercmp: MTIMECMP) { +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(hart_id, current_tick) } - { + 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 again if necessary @@ -102,7 +100,7 @@ impl Delay { #[inline] pub fn new(freq: usize) -> Self { let hart_id = riscv::register::mhartid::read(); - let mtimer = unsafe { _riscv_peripheral_aclint_mtimer(hart_id) }; + let mtimer = unsafe { _riscv_peripheral_aclint_mtimer() }; let (mtime, mtimecmp) = (mtimer.mtime, mtimer.mtimecmp_mhartid()); Self { hart_id, @@ -275,7 +273,7 @@ impl<'a> Future for DelayAsync<'a> { _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.hart_id, self.delay.mtime, self.delay.mtimecmp); + schedule_machine_timer(self.delay.mtime, self.delay.mtimecmp); } Poll::Pending } else { diff --git a/riscv-peripheral/src/macros.rs b/riscv-peripheral/src/macros.rs index f4c6e8b4..1d96339f 100644 --- a/riscv-peripheral/src/macros.rs +++ b/riscv-peripheral/src/macros.rs @@ -214,6 +214,12 @@ macro_rules! clint_codegen { $crate::clint_codegen!($($tail)*); }; (async_delay, $($tail:tt)*) => { + + #[no_mangle] + const fn _riscv_peripheral_aclint_mtimer() -> $crate::aclint::mtimer::MTIMER { + CLINT::mtimer() + } + impl CLINT { /// Asynchronous delay implementation for CLINT peripherals. /// From e9cd65d8471abd778acb6143d0664a33a256c1b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rom=C3=A1n=20C=C3=A1rdenas?= Date: Mon, 15 Jan 2024 13:10:26 +0100 Subject: [PATCH 24/25] added example for async delays with CLINT --- riscv-peripheral/Cargo.toml | 3 ++ riscv-peripheral/examples/e310x.rs | 42 ++++++++++++++++++++++++ riscv-peripheral/src/hal_async/aclint.rs | 28 ++++------------ riscv-peripheral/src/macros.rs | 6 ---- 4 files changed, 52 insertions(+), 27 deletions(-) diff --git a/riscv-peripheral/Cargo.toml b/riscv-peripheral/Cargo.toml index 6da7a9fa..d0d4e499 100644 --- a/riscv-peripheral/Cargo.toml +++ b/riscv-peripheral/Cargo.toml @@ -11,6 +11,9 @@ 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"] diff --git a/riscv-peripheral/examples/e310x.rs b/riscv-peripheral/examples/e310x.rs index 7b80cb84..c0801f24 100644 --- a/riscv-peripheral/examples/e310x.rs +++ b/riscv-peripheral/examples/e310x.rs @@ -159,4 +159,46 @@ riscv_peripheral::plic_codegen!( 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/hal_async/aclint.rs b/riscv-peripheral/src/hal_async/aclint.rs index dc84f11b..c7cd7ab7 100644 --- a/riscv-peripheral/src/hal_async/aclint.rs +++ b/riscv-peripheral/src/hal_async/aclint.rs @@ -11,7 +11,6 @@ //! 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. -//! This function is implemented by the [`crate::clint_codegen`] macro when asyn_delay is provided. //! - `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. @@ -37,7 +36,7 @@ extern "Rust" { /// 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 given HART ID. + /// 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 @@ -45,10 +44,10 @@ extern "Rust" { /// 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 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. + /// 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 /// @@ -75,7 +74,7 @@ fn schedule_machine_timer(mtime: MTIME, mtimercmp: MTIMECMP) { 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 again if necessary + unsafe { riscv::register::mie::set_mtimer() }; // enable machine timer interrupts } } @@ -89,7 +88,6 @@ fn schedule_machine_timer(mtime: MTIME, mtimercmp: MTIMECMP) { /// Additionally, the rest of the application must not modify the [`MTIMER`] register assigned to the current HART. #[derive(Clone)] pub struct Delay { - hart_id: usize, freq: usize, mtime: MTIME, mtimecmp: MTIMECMP, @@ -99,11 +97,9 @@ impl Delay { /// Creates a new `Delay` instance for the current HART. #[inline] pub fn new(freq: usize) -> Self { - let hart_id = riscv::register::mhartid::read(); let mtimer = unsafe { _riscv_peripheral_aclint_mtimer() }; let (mtime, mtimecmp) = (mtimer.mtime, mtimer.mtimecmp_mhartid()); Self { - hart_id, freq, mtime, mtimecmp, @@ -148,7 +144,6 @@ impl DelayNs for Delay { /// this entry provides the necessary information to adapt it to the timer queue implementation. #[derive(Debug)] pub struct Timer { - hart_id: usize, freq: usize, mtime: MTIME, mtimecmp: MTIMECMP, @@ -160,7 +155,6 @@ impl Timer { /// Creates a new timer queue entry. #[inline] const fn new( - hart_id: usize, freq: usize, mtime: MTIME, mtimecmp: MTIMECMP, @@ -168,7 +162,6 @@ impl Timer { waker: Waker, ) -> Self { Self { - hart_id, freq, mtime, mtimecmp, @@ -177,12 +170,6 @@ impl Timer { } } - /// Returns the HART ID associated with this timer. - #[inline] - pub const fn hart_id(&self) -> usize { - self.hart_id - } - /// Returns the frequency of the [`MTIME`] register associated with this timer. #[inline] pub const fn freq(&self) -> usize { @@ -216,7 +203,7 @@ impl Timer { impl PartialEq for Timer { fn eq(&self, other: &Self) -> bool { - self.hart_id == other.hart_id && self.freq == other.freq && self.expires == other.expires + self.freq == other.freq && self.expires == other.expires } } @@ -262,7 +249,6 @@ impl<'a> Future for DelayAsync<'a> { // we only push the timer to the queue the first time we poll self.pushed = true; let timer = Timer::new( - self.delay.hart_id, self.delay.freq, self.delay.mtime, self.delay.mtimecmp, diff --git a/riscv-peripheral/src/macros.rs b/riscv-peripheral/src/macros.rs index 1d96339f..f4c6e8b4 100644 --- a/riscv-peripheral/src/macros.rs +++ b/riscv-peripheral/src/macros.rs @@ -214,12 +214,6 @@ macro_rules! clint_codegen { $crate::clint_codegen!($($tail)*); }; (async_delay, $($tail:tt)*) => { - - #[no_mangle] - const fn _riscv_peripheral_aclint_mtimer() -> $crate::aclint::mtimer::MTIMER { - CLINT::mtimer() - } - impl CLINT { /// Asynchronous delay implementation for CLINT peripherals. /// From 358a15264d34313bc10984cd748e234c41bc4fc5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rom=C3=A1n=20C=C3=A1rdenas=20Rodr=C3=ADguez?= Date: Tue, 13 Feb 2024 13:59:15 +0100 Subject: [PATCH 25/25] tweak CI --- .github/workflows/riscv-peripheral.yaml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/riscv-peripheral.yaml b/.github/workflows/riscv-peripheral.yaml index 998f8bce..8797a8a4 100644 --- a/.github/workflows/riscv-peripheral.yaml +++ b/.github/workflows/riscv-peripheral.yaml @@ -40,7 +40,10 @@ jobs: build-others: strategy: matrix: - os: [ macos-latest, ubuntu-latest, windows-latest ] + 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