From 14afaf46c1f4a88bcb5ad63dfc73ef38b0f93f18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rom=C3=A1n=20C=C3=A1rdenas=20Rodr=C3=ADguez?= Date: Fri, 19 Jul 2024 17:28:19 +0200 Subject: [PATCH 01/14] New riscv-pac --- riscv-pac/CHANGELOG.md | 7 + riscv-pac/Cargo.toml | 4 +- riscv-pac/src/lib.rs | 240 +++++++++++++++++++++++++++-- riscv-peripheral/CHANGELOG.md | 4 + riscv-peripheral/Cargo.toml | 2 +- riscv-peripheral/examples/e310x.rs | 22 ++- riscv-peripheral/src/plic.rs | 38 ++--- riscv/CHANGELOG.md | 1 + riscv/Cargo.toml | 2 +- 9 files changed, 271 insertions(+), 49 deletions(-) diff --git a/riscv-pac/CHANGELOG.md b/riscv-pac/CHANGELOG.md index 0303047f..1d1813cc 100644 --- a/riscv-pac/CHANGELOG.md +++ b/riscv-pac/CHANGELOG.md @@ -10,6 +10,13 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Added - Add `result` module for `Error` and `Result` types +- Add `ExceptionNumber` trait. +- Classify interrupt numbers in `CoreInterruptNumber` and `ExternalInterruptNumber`. +- Added simple tests to illustrate how to implement all the provided traits. + +### Changed + +- `InterruptNumber` trait now expects `usize` ## [v0.1.1] - 2024-02-15 diff --git a/riscv-pac/Cargo.toml b/riscv-pac/Cargo.toml index b1cb2ae4..8a3f585d 100644 --- a/riscv-pac/Cargo.toml +++ b/riscv-pac/Cargo.toml @@ -1,8 +1,8 @@ [package] name = "riscv-pac" -version = "0.1.1" +version = "0.2.0" edition = "2021" -rust-version = "1.60" +rust-version = "1.61" repository = "https://github.com/rust-embedded/riscv" authors = ["The RISC-V Team "] categories = ["embedded", "hardware-support", "no-std"] diff --git a/riscv-pac/src/lib.rs b/riscv-pac/src/lib.rs index 4da1170a..935d5200 100644 --- a/riscv-pac/src/lib.rs +++ b/riscv-pac/src/lib.rs @@ -4,37 +4,86 @@ pub mod result; use result::Result; -/// Trait for enums of target-specific external interrupt numbers. +/// Trait for enums of target-specific exception 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. +/// This trait should be implemented by a peripheral access crate (PAC) on its enum of available +/// exceptions for a specific device. Alternatively, the `riscv` crate provides a default +/// implementation for the RISC-V ISA. Each variant must convert to a `usize` of its exception number. /// /// # Safety /// -/// * This trait must only be implemented on a PAC of a RISC-V target. -/// * This trait must only be implemented on enums of external interrupts. +/// * This trait must only be implemented on the `riscv` crate or on a PAC of a RISC-V target. +/// * This trait must only be implemented on enums of exceptions. +/// * 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 exception numbers must be less than or equal to `MAX_EXCEPTION_NUMBER`. +/// * `MAX_EXCEPTION_NUMBER` must coincide with the highest allowed exception number. +pub unsafe trait ExceptionNumber: Copy { + /// Highest number assigned to an exception. + const MAX_EXCEPTION_NUMBER: usize; + + /// Converts an exception to its corresponding number. + fn number(self) -> usize; + + /// Tries to convert a number to a valid exception. + /// If the conversion fails, it returns an error with the number back. + fn from_number(value: usize) -> Result; +} + +/// Trait for enums of target-specific interrupt numbers. +/// +/// This trait should be implemented by a peripheral access crate (PAC) on its enum of available +/// interrupts for a specific device. Alternatively, the `riscv` crate provides a default +/// implementation for the RISC-V ISA. Each variant must convert to a `usize` of its interrupt number. +/// +/// # Safety +/// +/// * This trait must only be implemented on the `riscv` crate or on a PAC of a RISC-V target. +/// * This trait must only be implemented on enums of 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; + const MAX_INTERRUPT_NUMBER: usize; /// Converts an interrupt source to its corresponding number. - fn number(self) -> u16; + fn number(self) -> usize; /// 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; + fn from_number(value: usize) -> Result; } +/// Marker trait for enums of target-specific core interrupt numbers. +/// +/// Core interrupts are interrupts are retrieved from the `mcause` CSR. Usually, vectored mode is +/// only available for core interrupts. The `riscv` crate provides a default implementation for +/// the RISC-V ISA. However, a PAC may override the default implementation if the target has a +/// different interrupt numbering scheme (e.g., ESP32C3). +/// +/// # Safety +/// +/// Each enum variant must represent a valid core interrupt number read from the `mcause` CSR. +pub unsafe trait CoreInterruptNumber: InterruptNumber {} + +/// Marker trait for enums of target-specific external interrupt numbers. +/// +/// External interrupts are interrupts caused by external sources (e.g., GPIO, UART, SPI). +/// External interrupts are **not** retrieved from the `mcause` CSR. +/// Instead, RISC-V processors have a single core interrupt for all external interrupts. +/// An additional peripheral (e.g., PLIC) is used to multiplex the external interrupts. +/// +/// # Safety +/// +/// Each enum variant must represent a valid external interrupt number. +pub unsafe trait ExternalInterruptNumber: InterruptNumber {} + /// 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. +/// 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. /// /// # Safety /// @@ -58,9 +107,8 @@ pub unsafe trait PriorityNumber: Copy { /// Trait for enums of HART identifiers. /// -/// This trait should be implemented by a peripheral access crate (PAC) -/// on its enum of available HARTs for a specific device. -/// Each variant must convert to a `u16` of its HART ID number. +/// This trait should be implemented by a peripheral access crate (PAC) on its enum of available +/// HARTs for a specific device. Each variant must convert to a `u16` of its HART ID number. /// /// # Safety /// @@ -81,3 +129,165 @@ pub unsafe trait HartIdNumber: Copy { /// If the conversion fails, it returns an error with the number back. fn from_number(value: u16) -> Result; } + +#[cfg(test)] +mod test { + use super::*; + use crate::result::Error; + + #[derive(Clone, Copy, Debug, Eq, PartialEq)] + enum Exception { + E1 = 1, + E3 = 3, + } + + #[derive(Clone, Copy, Debug, Eq, PartialEq)] + enum Interrupt { + I1 = 1, + I2 = 2, + I4 = 4, + } + + #[derive(Clone, Copy, Debug, Eq, PartialEq)] + enum Priority { + P0 = 0, + P1 = 1, + P2 = 2, + P3 = 3, + } + + #[derive(Clone, Copy, Debug, Eq, PartialEq)] + enum HartId { + H0 = 0, + H1 = 1, + H2 = 2, + } + + unsafe impl ExceptionNumber for Exception { + const MAX_EXCEPTION_NUMBER: usize = Self::E3 as usize; + + #[inline] + fn number(self) -> usize { + self as _ + } + + #[inline] + fn from_number(number: usize) -> Result { + match number { + 1 => Ok(Exception::E1), + 3 => Ok(Exception::E3), + _ => Err(Error::InvalidVariant(number)), + } + } + } + + unsafe impl InterruptNumber for Interrupt { + const MAX_INTERRUPT_NUMBER: usize = Self::I4 as usize; + + #[inline] + fn number(self) -> usize { + self as _ + } + + #[inline] + fn from_number(number: usize) -> Result { + match number { + 1 => Ok(Interrupt::I1), + 2 => Ok(Interrupt::I2), + 4 => Ok(Interrupt::I4), + _ => Err(Error::InvalidVariant(number)), + } + } + } + + unsafe impl PriorityNumber for Priority { + const MAX_PRIORITY_NUMBER: u8 = Self::P3 as u8; + + #[inline] + fn number(self) -> u8 { + self as _ + } + + #[inline] + fn from_number(number: u8) -> Result { + match number { + 0 => Ok(Priority::P0), + 1 => Ok(Priority::P1), + 2 => Ok(Priority::P2), + 3 => Ok(Priority::P3), + _ => Err(Error::InvalidVariant(number as _)), + } + } + } + + unsafe impl HartIdNumber for HartId { + const MAX_HART_ID_NUMBER: u16 = Self::H2 as u16; + + #[inline] + fn number(self) -> u16 { + self as _ + } + + #[inline] + fn from_number(number: u16) -> Result { + match number { + 0 => Ok(HartId::H0), + 1 => Ok(HartId::H1), + 2 => Ok(HartId::H2), + _ => Err(Error::InvalidVariant(number as _)), + } + } + } + + #[test] + fn check_exception_enum() { + assert_eq!(Exception::E1.number(), 1); + assert_eq!(Exception::E3.number(), 3); + + assert_eq!(Exception::from_number(0), Err(Error::InvalidVariant(0))); + assert_eq!(Exception::from_number(1), Ok(Exception::E1)); + assert_eq!(Exception::from_number(2), Err(Error::InvalidVariant(2))); + assert_eq!(Exception::from_number(3), Ok(Exception::E3)); + assert_eq!(Exception::from_number(4), Err(Error::InvalidVariant(4))); + } + + #[test] + fn check_interrupt_enum() { + assert_eq!(Interrupt::I1.number(), 1); + assert_eq!(Interrupt::I2.number(), 2); + assert_eq!(Interrupt::I4.number(), 4); + + assert_eq!(Interrupt::from_number(0), Err(Error::InvalidVariant(0))); + assert_eq!(Interrupt::from_number(1), Ok(Interrupt::I1)); + assert_eq!(Interrupt::from_number(2), Ok(Interrupt::I2)); + assert_eq!(Interrupt::from_number(3), Err(Error::InvalidVariant(3))); + assert_eq!(Interrupt::from_number(4), Ok(Interrupt::I4)); + assert_eq!(Interrupt::from_number(5), Err(Error::InvalidVariant(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(Error::InvalidVariant(4))); + } + + #[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(Error::InvalidVariant(3))); + } +} diff --git a/riscv-peripheral/CHANGELOG.md b/riscv-peripheral/CHANGELOG.md index 3feb9601..15d7754e 100644 --- a/riscv-peripheral/CHANGELOG.md +++ b/riscv-peripheral/CHANGELOG.md @@ -11,6 +11,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - use `riscv-pac` result types for trait implementations +### Changed + +- Bump `riscv-pac` version + ### Fixed - `clippy` fixes diff --git a/riscv-peripheral/Cargo.toml b/riscv-peripheral/Cargo.toml index e54efa9f..c11ec08a 100644 --- a/riscv-peripheral/Cargo.toml +++ b/riscv-peripheral/Cargo.toml @@ -17,7 +17,7 @@ license = "ISC" embedded-hal = "1.0.0" embedded-hal-async = { version = "1.0.0", optional = true } riscv = { path = "../riscv", version = "0.11.1" } -riscv-pac = { path = "../riscv-pac", version = "0.1.1" } +riscv-pac = { path = "../riscv-pac", version = "0.2.0" } [dev-dependencies] heapless = "0.8.0" diff --git a/riscv-peripheral/examples/e310x.rs b/riscv-peripheral/examples/e310x.rs index 6f608b6c..eae1da07 100644 --- a/riscv-peripheral/examples/e310x.rs +++ b/riscv-peripheral/examples/e310x.rs @@ -21,17 +21,15 @@ unsafe impl HartIdNumber for HartId { #[inline] fn from_number(number: u16) -> Result { - if number > Self::MAX_HART_ID_NUMBER { - Err(Error::InvalidVariant(number as usize)) - } else { - // SAFETY: valid context number - Ok(unsafe { core::mem::transmute(number) }) + match number { + 0 => Ok(Self::H0), + _ => Err(Error::InvalidVariant(number as usize)), } } } #[derive(Copy, Clone, Debug, PartialEq, Eq)] -#[repr(u16)] +#[repr(usize)] pub enum Interrupt { WATCHDOG = 1, RTC = 2, @@ -88,20 +86,20 @@ pub enum Interrupt { } unsafe impl InterruptNumber for Interrupt { - const MAX_INTERRUPT_NUMBER: u16 = 52; + const MAX_INTERRUPT_NUMBER: usize = 52; #[inline] - fn number(self) -> u16 { + fn number(self) -> usize { self as _ } #[inline] - fn from_number(number: u16) -> Result { + fn from_number(number: usize) -> Result { if number == 0 || number > Self::MAX_INTERRUPT_NUMBER { - Err(Error::InvalidVariant(number as usize)) + Err(Error::InvalidVariant(number)) } else { // SAFETY: valid interrupt number - Ok(unsafe { core::mem::transmute(number) }) + Ok(unsafe { core::mem::transmute::(number) }) } } } @@ -133,7 +131,7 @@ unsafe impl PriorityNumber for Priority { Err(Error::InvalidVariant(number as usize)) } else { // SAFETY: valid priority number - Ok(unsafe { core::mem::transmute(number) }) + Ok(unsafe { core::mem::transmute::(number) }) } } } diff --git a/riscv-peripheral/src/plic.rs b/riscv-peripheral/src/plic.rs index 116689a6..6fa0d311 100644 --- a/riscv-peripheral/src/plic.rs +++ b/riscv-peripheral/src/plic.rs @@ -175,20 +175,21 @@ pub(crate) mod test { } unsafe impl InterruptNumber for Interrupt { - const MAX_INTERRUPT_NUMBER: u16 = 4; + const MAX_INTERRUPT_NUMBER: usize = 4; #[inline] - fn number(self) -> u16 { + fn number(self) -> usize { self as _ } #[inline] - fn from_number(number: u16) -> Result { - if number > Self::MAX_INTERRUPT_NUMBER || number == 0 { - Err(Error::InvalidVariant(number as usize)) - } else { - // SAFETY: valid interrupt number - Ok(unsafe { core::mem::transmute(number) }) + fn from_number(number: usize) -> Result { + match number { + 1 => Ok(Interrupt::I1), + 2 => Ok(Interrupt::I2), + 3 => Ok(Interrupt::I3), + 4 => Ok(Interrupt::I4), + _ => Err(Error::InvalidVariant(number)), } } } @@ -203,11 +204,12 @@ pub(crate) mod test { #[inline] fn from_number(number: u8) -> Result { - if number > Self::MAX_PRIORITY_NUMBER { - Err(Error::InvalidVariant(number as usize)) - } else { - // SAFETY: valid priority number - Ok(unsafe { core::mem::transmute(number) }) + match number { + 0 => Ok(Priority::P0), + 1 => Ok(Priority::P1), + 2 => Ok(Priority::P2), + 3 => Ok(Priority::P3), + _ => Err(Error::InvalidVariant(number as usize)), } } } @@ -222,11 +224,11 @@ pub(crate) mod test { #[inline] fn from_number(number: u16) -> Result { - if number > Self::MAX_HART_ID_NUMBER { - Err(Error::InvalidVariant(number as usize)) - } else { - // SAFETY: valid context number - Ok(unsafe { core::mem::transmute(number) }) + match number { + 0 => Ok(Context::C0), + 1 => Ok(Context::C1), + 2 => Ok(Context::C2), + _ => Err(Error::InvalidVariant(number as usize)), } } } diff --git a/riscv/CHANGELOG.md b/riscv/CHANGELOG.md index 7b2aa2fb..019a6dbe 100644 --- a/riscv/CHANGELOG.md +++ b/riscv/CHANGELOG.md @@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Add `Mcounteren` in-memory update functions - Add `Mstatus` vector extension support - Add fallible counterparts to all functions that `panic` +- Add `riscv-pac` as a dependency ### Fixed diff --git a/riscv/Cargo.toml b/riscv/Cargo.toml index a783bb1c..6ce8e246 100644 --- a/riscv/Cargo.toml +++ b/riscv/Cargo.toml @@ -26,4 +26,4 @@ critical-section-single-hart = ["critical-section/restore-state-bool"] [dependencies] critical-section = "1.1.2" embedded-hal = "1.0.0" -riscv-pac = { path = "../riscv-pac", version = "0.1.1", default-features = false } +riscv-pac = { path = "../riscv-pac", version = "0.2.0" } From 819e058b16a9becb052bbdc914c21f6924aae1f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rom=C3=A1n=20C=C3=A1rdenas=20Rodr=C3=ADguez?= Date: Fri, 19 Jul 2024 17:52:07 +0200 Subject: [PATCH 02/14] Add riscv-peripheral changes as well --- riscv-pac/Cargo.toml | 2 +- riscv-peripheral/CHANGELOG.md | 2 +- riscv-peripheral/Cargo.toml | 2 +- riscv-peripheral/examples/e310x.rs | 16 ++++---- riscv-peripheral/src/lib.rs | 1 + riscv-peripheral/src/macros.rs | 51 +++++++++++++++++-------- riscv-peripheral/src/plic.rs | 6 ++- riscv-peripheral/src/plic/claim.rs | 8 ++-- riscv-peripheral/src/plic/enables.rs | 30 +++++++-------- riscv-peripheral/src/plic/pendings.rs | 10 ++--- riscv-peripheral/src/plic/priorities.rs | 17 ++++----- 11 files changed, 83 insertions(+), 62 deletions(-) diff --git a/riscv-pac/Cargo.toml b/riscv-pac/Cargo.toml index 8a3f585d..b5ba86b2 100644 --- a/riscv-pac/Cargo.toml +++ b/riscv-pac/Cargo.toml @@ -2,7 +2,7 @@ name = "riscv-pac" version = "0.2.0" edition = "2021" -rust-version = "1.61" +rust-version = "1.60" repository = "https://github.com/rust-embedded/riscv" authors = ["The RISC-V Team "] categories = ["embedded", "hardware-support", "no-std"] diff --git a/riscv-peripheral/CHANGELOG.md b/riscv-peripheral/CHANGELOG.md index 15d7754e..bef9d5e3 100644 --- a/riscv-peripheral/CHANGELOG.md +++ b/riscv-peripheral/CHANGELOG.md @@ -13,7 +13,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Changed -- Bump `riscv-pac` version +- `PLIC` now expects interrupt enums to implement the `riscv_pac::ExternalInterruptNumber` trait. ### Fixed diff --git a/riscv-peripheral/Cargo.toml b/riscv-peripheral/Cargo.toml index c11ec08a..ec9f929f 100644 --- a/riscv-peripheral/Cargo.toml +++ b/riscv-peripheral/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "riscv-peripheral" -version = "0.1.0" +version = "0.2.0" edition = "2021" rust-version = "1.75" repository = "https://github.com/rust-embedded/riscv" diff --git a/riscv-peripheral/examples/e310x.rs b/riscv-peripheral/examples/e310x.rs index eae1da07..54e69c28 100644 --- a/riscv-peripheral/examples/e310x.rs +++ b/riscv-peripheral/examples/e310x.rs @@ -2,17 +2,18 @@ //! This is a simple example of how to use the `riscv-peripheral` crate to generate //! peripheral definitions for a target. -use riscv_pac::result::{Error, Result}; -use riscv_pac::{HartIdNumber, InterruptNumber, PriorityNumber}; +use riscv_pac::{ + result::{Error, Result}, + ExternalInterruptNumber, HartIdNumber, InterruptNumber, PriorityNumber, +}; -#[repr(u16)] #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum HartId { H0 = 0, } unsafe impl HartIdNumber for HartId { - const MAX_HART_ID_NUMBER: u16 = 0; + const MAX_HART_ID_NUMBER: u16 = Self::H0 as u16; #[inline] fn number(self) -> u16 { @@ -86,7 +87,7 @@ pub enum Interrupt { } unsafe impl InterruptNumber for Interrupt { - const MAX_INTERRUPT_NUMBER: usize = 52; + const MAX_INTERRUPT_NUMBER: usize = Self::I2C0 as usize; #[inline] fn number(self) -> usize { @@ -104,8 +105,9 @@ unsafe impl InterruptNumber for Interrupt { } } +unsafe impl ExternalInterruptNumber for Interrupt {} + #[derive(Copy, Clone, Debug, PartialEq, Eq)] -#[repr(u8)] pub enum Priority { P0 = 0, P1 = 1, @@ -118,7 +120,7 @@ pub enum Priority { } unsafe impl PriorityNumber for Priority { - const MAX_PRIORITY_NUMBER: u8 = 7; + const MAX_PRIORITY_NUMBER: u8 = Self::P7 as u8; #[inline] fn number(self) -> u8 { diff --git a/riscv-peripheral/src/lib.rs b/riscv-peripheral/src/lib.rs index ca33cd19..b34b63a8 100644 --- a/riscv-peripheral/src/lib.rs +++ b/riscv-peripheral/src/lib.rs @@ -9,6 +9,7 @@ #![no_std] pub use riscv; // re-export riscv crate to allow macros to use it +pub use riscv_pac::result; // re-export the result module pub mod common; // common definitions for all peripherals pub mod hal; // trait implementations for embedded-hal diff --git a/riscv-peripheral/src/macros.rs b/riscv-peripheral/src/macros.rs index eec2ac27..faf093f1 100644 --- a/riscv-peripheral/src/macros.rs +++ b/riscv-peripheral/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 4 different argument types: +/// This macro expects 5 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. +/// - Async flag (**OPTIONAL**): It enables the `async_delay` method of the `CLINT struct`. +/// You must activate the `embedded-hal-async` feature to use this flag. /// - 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. /// @@ -17,24 +19,20 @@ /// ## Base address only /// /// ``` -/// use riscv_peripheral::clint_codegen; -/// -/// clint_codegen!(base 0x0200_0000, freq 32_768,); // do not forget the ending comma! +/// riscv_peripheral::clint_codegen!(base 0x0200_0000, freq 32_768,); // do not forget the ending comma! /// -/// let mswi = CLINT::mswi(); // MSWI peripheral +/// let mswi = CLINT::mswi(); // MSWI peripheral /// let mtimer = CLINT::mtimer(); // MTIMER peripheral -/// let delay = CLINT::delay(); // For the `embedded_hal::delay::DelayNs` trait +/// let delay = CLINT::delay(); // For the `embedded_hal::delay::DelayNs` trait /// ``` /// /// ## Base address and per-HART mtimecmp registers /// /// ``` -/// use riscv_peripheral::clint_codegen; /// use riscv_pac::result::{Error, Result}; /// /// /// 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` @@ -42,16 +40,16 @@ /// 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(Error::InvalidVariant(number as usize)) -/// } else { -/// // SAFETY: valid context number -/// Ok(unsafe { core::mem::transmute(number) }) +/// match number { +/// 0 => Ok(HartId::H0), +/// 1 => Ok(HartId::H1), +/// 2 => Ok(HartId::H2), +/// _ => Err(Error::InvalidVariant(number as _)), /// } /// } /// } /// -/// clint_codegen!( +/// riscv_peripheral::clint_codegen!( /// base 0x0200_0000, /// mtimecmps [mtimecmp0 = (HartId::H0, "`H0`"), mtimecmp1 = (HartId::H1, "`H1`"), mtimecmp2 = (HartId::H2, "`H2`")], /// msips [msip0=(HartId::H0,"`H0`"), msip1=(HartId::H1,"`H1`"), msip2=(HartId::H2,"`H2`")], // do not forget the ending comma! @@ -206,7 +204,7 @@ macro_rules! clint_codegen { /// /// # Note /// - /// You must export the `riscv_peripheral::hal::delay::DelayNs` trait in order to use delay methods. + /// You must export the [`embedded_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()) @@ -220,7 +218,7 @@ macro_rules! clint_codegen { /// /// # Note /// - /// You must export the `riscv_peripheral::hal_async::delay::DelayNs` trait in order to use delay methods. + /// You must export the [`embedded_hal_async::delay::DelayNs`] trait in order to use delay methods. /// /// This implementation relies on the machine-level timer interrupts to wake futures. /// Therefore, it needs to schedule the machine-level timer interrupts via the `MTIMECMP` register assigned to the current HART. @@ -264,6 +262,27 @@ macro_rules! clint_codegen { } /// Macro to create interfaces to PLIC peripherals in PACs. +/// The resulting struct will be named `PLIC`, and will provide safe access to the PLIC registers. +/// +/// This macro expects 2 different argument types: +/// +/// - Base address (**MANDATORY**): base address of the PLIC peripheral of the target. +/// - Per-HART contexts (**OPTIONAL**): a list of `ctx` contexts for easing access to per-HART PLIC contexts. +/// +/// Check the examples below for more details about the usage and syntax of this macro. +/// +/// # Example +/// +/// ## Base address only +/// +/// ``` +/// use riscv_peripheral::clint_codegen; +/// +/// riscv_peripheral::plic_codegen!(base 0x0C00_0000,); // do not forget the ending comma! +/// +/// let priorities = PLIC::priorities(); // Priorities registers +/// let pendings = PLIC::pendings(); // Pendings registers +/// ``` #[macro_export] macro_rules! plic_codegen { () => { diff --git a/riscv-peripheral/src/plic.rs b/riscv-peripheral/src/plic.rs index 6fa0d311..6f5ee98f 100644 --- a/riscv-peripheral/src/plic.rs +++ b/riscv-peripheral/src/plic.rs @@ -145,11 +145,11 @@ impl CTX

{ #[cfg(test)] pub(crate) mod test { - use super::{HartIdNumber, InterruptNumber, PriorityNumber}; use riscv_pac::result::{Error, Result}; + use riscv_pac::{ExternalInterruptNumber, HartIdNumber, InterruptNumber, PriorityNumber}; #[derive(Clone, Copy, Debug, Eq, PartialEq)] - #[repr(u16)] + #[repr(usize)] pub(crate) enum Interrupt { I1 = 1, I2 = 2, @@ -194,6 +194,8 @@ pub(crate) mod test { } } + unsafe impl ExternalInterruptNumber for Interrupt {} + unsafe impl PriorityNumber for Priority { const MAX_PRIORITY_NUMBER: u8 = 3; diff --git a/riscv-peripheral/src/plic/claim.rs b/riscv-peripheral/src/plic/claim.rs index dbf5b378..d99a543d 100644 --- a/riscv-peripheral/src/plic/claim.rs +++ b/riscv-peripheral/src/plic/claim.rs @@ -1,6 +1,7 @@ //! Interrupt claim/complete register -use crate::{common::unsafe_peripheral, plic::InterruptNumber}; +use crate::common::unsafe_peripheral; +use riscv_pac::ExternalInterruptNumber; unsafe_peripheral!(CLAIM, u32, RW); @@ -8,7 +9,7 @@ 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 { + pub fn claim(self) -> Option { match self.register.read() { 0 => None, i => Some(I::from_number(i as _).unwrap()), @@ -22,7 +23,7 @@ impl CLAIM { /// 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) { + pub fn complete(self, source: I) { self.register.write(source.number() as _) } } @@ -31,6 +32,7 @@ impl CLAIM { mod test { use super::super::test::Interrupt; use super::*; + use riscv_pac::InterruptNumber; #[test] fn test_claim() { diff --git a/riscv-peripheral/src/plic/enables.rs b/riscv-peripheral/src/plic/enables.rs index 03ab9a35..9891b32c 100644 --- a/riscv-peripheral/src/plic/enables.rs +++ b/riscv-peripheral/src/plic/enables.rs @@ -1,9 +1,7 @@ //! Interrupt enables register of a PLIC context. -use crate::{ - common::{Reg, RW}, - plic::InterruptNumber, -}; +use crate::common::{Reg, RW}; +use riscv_pac::ExternalInterruptNumber; /// Enables register of a PLIC context. #[derive(Clone, Copy, Debug, Eq, PartialEq)] @@ -31,8 +29,8 @@ impl ENABLES { /// 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; + pub fn is_enabled(self, source: I) -> bool { + let source = source.number(); let offset = (source / u32::BITS as usize) as _; // SAFETY: valid interrupt number let reg: Reg = unsafe { Reg::new(self.ptr.offset(offset)) }; @@ -49,8 +47,8 @@ impl ENABLES { /// /// * Enabling an interrupt source can break mask-based critical sections. #[inline] - pub unsafe fn enable(self, source: I) { - let source = source.number() as usize; + pub unsafe fn enable(self, source: I) { + let source = source.number(); let offset = (source / u32::BITS as usize) as _; // SAFETY: valid interrupt number let reg: Reg = unsafe { Reg::new(self.ptr.offset(offset)) }; @@ -70,12 +68,12 @@ impl ENABLES { /// * 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( + pub unsafe fn atomic_enable( self, source: I, order: core::sync::atomic::Ordering, ) { - let source = source.number() as usize; + let source = source.number(); let offset = (source / u32::BITS as usize) as _; // SAFETY: valid interrupt number let reg: Reg = unsafe { Reg::new(self.ptr.offset(offset)) }; @@ -88,8 +86,8 @@ impl ENABLES { /// /// 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; + pub fn disable(self, source: I) { + let source = source.number(); let offset = (source / u32::BITS as usize) as _; // SAFETY: valid interrupt number let reg: Reg = unsafe { Reg::new(self.ptr.offset(offset)) }; @@ -108,12 +106,12 @@ impl ENABLES { /// * 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( + pub unsafe fn atomic_disable( self, source: I, order: core::sync::atomic::Ordering, ) { - let source = source.number() as usize; + let source = source.number(); let offset = (source / u32::BITS as usize) as _; // SAFETY: valid interrupt number let reg: Reg = unsafe { Reg::new(self.ptr.offset(offset)) }; @@ -126,7 +124,7 @@ impl ENABLES { /// ///* Enabling all interrupt sources can break mask-based critical sections. #[inline] - pub unsafe fn enable_all(self) { + 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)) }; @@ -136,7 +134,7 @@ impl ENABLES { /// Disables all the external interrupt sources for the PLIC context. #[inline] - pub fn disable_all(self) { + 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)) }; diff --git a/riscv-peripheral/src/plic/pendings.rs b/riscv-peripheral/src/plic/pendings.rs index 8185d9b8..594991da 100644 --- a/riscv-peripheral/src/plic/pendings.rs +++ b/riscv-peripheral/src/plic/pendings.rs @@ -1,9 +1,7 @@ //! Interrupt pending bits register. -use crate::{ - common::{Reg, RO}, - plic::InterruptNumber, -}; +use crate::common::{Reg, RO}; +use riscv_pac::ExternalInterruptNumber; /// Interrupts pending bits register. #[derive(Clone, Copy, Debug, Eq, PartialEq)] @@ -31,8 +29,8 @@ impl PENDINGS { /// 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; + pub fn is_pending(self, source: I) -> bool { + let source = source.number(); let offset = (source / u32::BITS as usize) as _; // SAFETY: valid interrupt number let reg: Reg = unsafe { Reg::new(self.ptr.offset(offset)) }; diff --git a/riscv-peripheral/src/plic/priorities.rs b/riscv-peripheral/src/plic/priorities.rs index a9726838..b5e44857 100644 --- a/riscv-peripheral/src/plic/priorities.rs +++ b/riscv-peripheral/src/plic/priorities.rs @@ -1,9 +1,7 @@ //! Interrupts Priorities register. -use crate::{ - common::{Reg, RW}, - plic::{InterruptNumber, PriorityNumber}, -}; +use crate::common::{Reg, RW}; +use riscv_pac::{ExternalInterruptNumber, PriorityNumber}; /// Interrupts priorities register. #[derive(Clone, Copy, Debug, Eq, PartialEq)] @@ -31,9 +29,9 @@ impl PRIORITIES { /// Returns the priority assigned to a given interrupt source. #[inline] - pub fn get_priority(self, source: I) -> P { + pub fn get_priority(self, source: I) -> P { // SAFETY: valid interrupt number - let reg: Reg = unsafe { Reg::new(self.ptr.offset(source.number() as _)) }; + let reg: Reg = unsafe { Reg::new(self.ptr.add(source.number())) }; P::from_number(reg.read() as _).unwrap() } @@ -43,13 +41,13 @@ impl PRIORITIES { /// /// Changing the priority level can break priority-based critical sections. #[inline] - pub unsafe fn set_priority( + 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 _)) }; + let reg: Reg = unsafe { Reg::new(self.ptr.add(source.number())) }; reg.write(priority.number() as _); } @@ -60,7 +58,7 @@ impl PRIORITIES { /// Priority level 0 is reserved for "no interrupt". /// Thus, this method effectively disables the all the external interrupts. #[inline] - pub fn reset(self) { + 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)) }; @@ -73,6 +71,7 @@ impl PRIORITIES { mod test { use super::super::test::{Interrupt, Priority}; use super::*; + use riscv_pac::InterruptNumber; #[test] fn test_priorities() { From a67367e40db4554d295a1b9c6bb06ac741d2b9aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rom=C3=A1n=20C=C3=A1rdenas=20Rodr=C3=ADguez?= Date: Sun, 21 Jul 2024 12:59:34 +0200 Subject: [PATCH 03/14] fix clippy messages --- riscv/CHANGELOG.md | 1 + riscv/src/asm.rs | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/riscv/CHANGELOG.md b/riscv/CHANGELOG.md index 019a6dbe..1e7add27 100644 --- a/riscv/CHANGELOG.md +++ b/riscv/CHANGELOG.md @@ -26,6 +26,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Fixed `sip::set_ssoft` and `sip::clear_ssoft` using wrong address - Fixed assignment in `mstatus` unit tests. +- delay implementation does not use binary labels in inline assembly. ## [v0.11.1] - 2024-02-15 diff --git a/riscv/src/asm.rs b/riscv/src/asm.rs index 682e83ae..b29e65db 100644 --- a/riscv/src/asm.rs +++ b/riscv/src/asm.rs @@ -149,9 +149,9 @@ pub fn delay(cycles: u32) { () => unsafe { let real_cyc = 1 + cycles / 2; core::arch::asm!( - "1:", + "2:", "addi {0}, {0}, -1", - "bne {0}, zero, 1b", + "bne {0}, zero, 2b", inout(reg) real_cyc => _, options(nomem, nostack), ) From a929ae1ebfa9c720dab01609f1e1e9a62928ef18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rom=C3=A1n=20C=C3=A1rdenas=20Rodr=C3=ADguez?= Date: Sat, 27 Jul 2024 17:03:23 +0200 Subject: [PATCH 04/14] Push all the changes together --- .github/workflows/riscv-semihosting.yaml | 4 +- .github/workflows/riscv.yaml | 4 +- riscv-peripheral/Cargo.toml | 2 +- riscv-peripheral/src/aclint.rs | 2 +- riscv-rt/CHANGELOG.md | 8 +- riscv-rt/Cargo.toml | 4 +- riscv-rt/macros/Cargo.toml | 1 + riscv-rt/src/asm.rs | 32 -- riscv-rt/src/exceptions.rs | 53 ++++ riscv-rt/src/interrupts.rs | 67 +++++ riscv-rt/src/lib.rs | 112 +------ riscv-semihosting/CHANGELOG.md | 1 + riscv-semihosting/Cargo.toml | 2 +- riscv/CHANGELOG.md | 4 + riscv/Cargo.toml | 9 +- riscv/README.md | 2 +- riscv/macros/Cargo.toml | 21 ++ riscv/macros/src/lib.rs | 366 +++++++++++++++++++++++ riscv/src/interrupt.rs | 205 +++---------- riscv/src/interrupt/machine.rs | 272 +++++++++++++++++ riscv/src/interrupt/supervisor.rs | 259 ++++++++++++++++ riscv/src/lib.rs | 4 + riscv/src/register/mcause.rs | 118 +------- riscv/src/register/scause.rs | 118 +------- riscv/tests/test.rs | 6 + riscv/tests/ui/fail_empty_macro.rs | 9 + riscv/tests/ui/fail_empty_macro.stderr | 7 + riscv/tests/ui/fail_no_unsafe.rs | 9 + riscv/tests/ui/fail_no_unsafe.stderr | 5 + riscv/tests/ui/fail_unknown_trait.rs | 9 + riscv/tests/ui/fail_unknown_trait.stderr | 5 + riscv/tests/ui/pass_test.rs | 116 +++++++ 32 files changed, 1316 insertions(+), 520 deletions(-) create mode 100644 riscv-rt/src/exceptions.rs create mode 100644 riscv-rt/src/interrupts.rs create mode 100644 riscv/macros/Cargo.toml create mode 100644 riscv/macros/src/lib.rs create mode 100644 riscv/src/interrupt/machine.rs create mode 100644 riscv/src/interrupt/supervisor.rs create mode 100644 riscv/tests/test.rs create mode 100644 riscv/tests/ui/fail_empty_macro.rs create mode 100644 riscv/tests/ui/fail_empty_macro.stderr create mode 100644 riscv/tests/ui/fail_no_unsafe.rs create mode 100644 riscv/tests/ui/fail_no_unsafe.stderr create mode 100644 riscv/tests/ui/fail_unknown_trait.rs create mode 100644 riscv/tests/ui/fail_unknown_trait.stderr create mode 100644 riscv/tests/ui/pass_test.rs diff --git a/.github/workflows/riscv-semihosting.yaml b/.github/workflows/riscv-semihosting.yaml index 4f6da8b0..3ba711ec 100644 --- a/.github/workflows/riscv-semihosting.yaml +++ b/.github/workflows/riscv-semihosting.yaml @@ -11,8 +11,8 @@ jobs: build-riscv: strategy: matrix: - # All generated code should be running on stable now, MRSV is 1.60.0 - toolchain: [ stable, nightly, 1.60.0 ] + # 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 diff --git a/.github/workflows/riscv.yaml b/.github/workflows/riscv.yaml index cd03b872..21aac5f9 100644 --- a/.github/workflows/riscv.yaml +++ b/.github/workflows/riscv.yaml @@ -11,8 +11,8 @@ jobs: build-riscv: strategy: matrix: - # All generated code should be running on stable now, MRSV is 1.60.0 - toolchain: [ stable, nightly, 1.60.0 ] + # 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 diff --git a/riscv-peripheral/Cargo.toml b/riscv-peripheral/Cargo.toml index ec9f929f..8922e21f 100644 --- a/riscv-peripheral/Cargo.toml +++ b/riscv-peripheral/Cargo.toml @@ -16,7 +16,7 @@ license = "ISC" [dependencies] embedded-hal = "1.0.0" embedded-hal-async = { version = "1.0.0", optional = true } -riscv = { path = "../riscv", version = "0.11.1" } +riscv = { path = "../riscv", version = "0.12.0" } riscv-pac = { path = "../riscv-pac", version = "0.2.0" } [dev-dependencies] diff --git a/riscv-peripheral/src/aclint.rs b/riscv-peripheral/src/aclint.rs index 873daeda..51d139d4 100644 --- a/riscv-peripheral/src/aclint.rs +++ b/riscv-peripheral/src/aclint.rs @@ -87,7 +87,7 @@ pub(crate) mod test { Err(Error::InvalidVariant(number as usize)) } else { // SAFETY: valid context number - Ok(unsafe { core::mem::transmute(number) }) + Ok(unsafe { core::mem::transmute::(number) }) } } } diff --git a/riscv-rt/CHANGELOG.md b/riscv-rt/CHANGELOG.md index d224e5cd..3b451d3e 100644 --- a/riscv-rt/CHANGELOG.md +++ b/riscv-rt/CHANGELOG.md @@ -9,10 +9,12 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Added +- Add `no-exceptions` feature to opt-out the default implementation of `_dispatch_exception` +- Add `no-interrupts` feature to opt-out the default implementation of `_dispatch_core_interrupt` - Add `pre_init_trap` to detect early errors during the boot process. - Add `v-trap` feature to enable interrupt handling in vectored mode. - Add `interrupt` proc macro to help defining interrupt handlers. -If `v-trap` feature is enabled, this macro also generates its corresponding trap. + If `v-trap` feature is enabled, this macro also generates its corresponding trap. - Add `u-boot` feature, so that you can start your elf binary with u-boot and work with passed arguments. @@ -22,8 +24,8 @@ work with passed arguments. - Use `weak` symbols for functions such as `_mp_hook` or `_start_trap` - `abort` is now `weak`, so it is possible to link third-party libraries including this symbol. - Made `cfg` variable selection more robust for custom targets -- `_start_trap_rust` now only deals with exceptions. When an interrupt is detected, it now calls -to `_dispatch_interrupt`. +- `_start_trap_rust` now relies on `_dispatch_exception` and `_dispatch_core_interrupt`. + This change allows more flexibility for targets with non-standard exceptions and interrupts. - Upgrade rust-version to 1.61 - Update `syn` to version 2.0 diff --git a/riscv-rt/Cargo.toml b/riscv-rt/Cargo.toml index eb2e212f..f07da9e9 100644 --- a/riscv-rt/Cargo.toml +++ b/riscv-rt/Cargo.toml @@ -13,7 +13,7 @@ edition = "2021" links = "riscv-rt" # Prevent multiple versions of riscv-rt being linked [dependencies] -riscv = {path = "../riscv", version = "0.11.1"} +riscv = { path = "../riscv", version = "0.12.0" } riscv-rt-macros = { path = "macros", version = "0.2.1" } [dev-dependencies] @@ -24,3 +24,5 @@ s-mode = ["riscv-rt-macros/s-mode"] single-hart = [] v-trap = ["riscv-rt-macros/v-trap"] u-boot = ["riscv-rt-macros/u-boot", "single-hart"] +no-interrupts = [] +no-exceptions = [] diff --git a/riscv-rt/macros/Cargo.toml b/riscv-rt/macros/Cargo.toml index b7dba6fb..c842c4d6 100644 --- a/riscv-rt/macros/Cargo.toml +++ b/riscv-rt/macros/Cargo.toml @@ -11,6 +11,7 @@ license = "MIT OR Apache-2.0" name = "riscv-rt-macros" repository = "https://github.com/rust-embedded/riscv" version = "0.2.1" +edition = "2021" [lib] proc-macro = true diff --git a/riscv-rt/src/asm.rs b/riscv-rt/src/asm.rs index 7883c446..bc177e7e 100644 --- a/riscv-rt/src/asm.rs +++ b/riscv-rt/src/asm.rs @@ -287,38 +287,6 @@ riscv_rt_macros::vectored_interrupt_trap_riscv32!(); #[cfg(all(riscv64, feature = "v-trap"))] riscv_rt_macros::vectored_interrupt_trap_riscv64!(); -#[cfg(feature = "v-trap")] -cfg_global_asm!( - // Set the vector mode to vectored. - r#".section .trap, "ax" - .weak _vector_table - .type _vector_table, @function - - .option push - .balign 0x4 // TODO check if this is the correct alignment - .option norelax - .option norvc - - _vector_table: - j _start_trap // Interrupt 0 is used for exceptions - j _start_SupervisorSoft_trap - j _start_DefaultHandler_trap // Interrupt 2 is reserved - j _start_MachineSoft_trap - j _start_DefaultHandler_trap // Interrupt 4 is reserved - j _start_SupervisorTimer_trap - j _start_DefaultHandler_trap // Interrupt 6 is reserved - j _start_MachineTimer_trap - j _start_DefaultHandler_trap // Interrupt 8 is reserved - j _start_SupervisorExternal_trap - j _start_DefaultHandler_trap // Interrupt 10 is reserved - j _start_MachineExternal_trap - - // default table does not include the remaining interrupts. - // Targets with extra interrupts should override this table. - - .option pop"#, -); - #[rustfmt::skip] global_asm!( ".section .text.abort diff --git a/riscv-rt/src/exceptions.rs b/riscv-rt/src/exceptions.rs new file mode 100644 index 00000000..41d8595f --- /dev/null +++ b/riscv-rt/src/exceptions.rs @@ -0,0 +1,53 @@ +use crate::TrapFrame; + +extern "C" { + fn InstructionMisaligned(trap_frame: &TrapFrame); + fn InstructionFault(trap_frame: &TrapFrame); + fn IllegalInstruction(trap_frame: &TrapFrame); + fn Breakpoint(trap_frame: &TrapFrame); + fn LoadMisaligned(trap_frame: &TrapFrame); + fn LoadFault(trap_frame: &TrapFrame); + fn StoreMisaligned(trap_frame: &TrapFrame); + fn StoreFault(trap_frame: &TrapFrame); + fn UserEnvCall(trap_frame: &TrapFrame); + fn SupervisorEnvCall(trap_frame: &TrapFrame); + fn MachineEnvCall(trap_frame: &TrapFrame); + fn InstructionPageFault(trap_frame: &TrapFrame); + fn LoadPageFault(trap_frame: &TrapFrame); + fn StorePageFault(trap_frame: &TrapFrame); + fn ExceptionHandler(trap_frame: &TrapFrame); +} + +#[doc(hidden)] +#[no_mangle] +pub static __EXCEPTIONS: [Option; 16] = [ + Some(InstructionMisaligned), + Some(InstructionFault), + Some(IllegalInstruction), + Some(Breakpoint), + Some(LoadMisaligned), + Some(LoadFault), + Some(StoreMisaligned), + Some(StoreFault), + Some(UserEnvCall), + Some(SupervisorEnvCall), + None, + Some(MachineEnvCall), + Some(InstructionPageFault), + Some(LoadPageFault), + None, + Some(StorePageFault), +]; + +#[export_name = "_dispatch_exception"] +#[inline] +unsafe extern "C" fn dispatch_exception(trap_frame: &TrapFrame, code: usize) { + if code < __EXCEPTIONS.len() { + match &__EXCEPTIONS[code] { + Some(handler) => handler(trap_frame), + None => ExceptionHandler(trap_frame), + } + } else { + ExceptionHandler(trap_frame); + } +} diff --git a/riscv-rt/src/interrupts.rs b/riscv-rt/src/interrupts.rs new file mode 100644 index 00000000..c2314fde --- /dev/null +++ b/riscv-rt/src/interrupts.rs @@ -0,0 +1,67 @@ +extern "C" { + fn SupervisorSoft(); + fn MachineSoft(); + fn SupervisorTimer(); + fn MachineTimer(); + fn SupervisorExternal(); + fn MachineExternal(); + fn DefaultHandler(); +} + +#[doc(hidden)] +#[no_mangle] +pub static __CORE_INTERRUPTS: [Option; 12] = [ + None, + Some(SupervisorSoft), + None, + Some(MachineSoft), + None, + Some(SupervisorTimer), + None, + Some(MachineTimer), + None, + Some(SupervisorExternal), + None, + Some(MachineExternal), +]; + +#[export_name = "_dispatch_core_interrupt"] +#[inline] +unsafe extern "C" fn dispatch_core_interrupt(code: usize) { + if code < __CORE_INTERRUPTS.len() { + match &__CORE_INTERRUPTS[code] { + Some(handler) => handler(), + None => DefaultHandler(), + } + } else { + DefaultHandler(); + } +} + +#[cfg(all(riscv, feature = "v-trap"))] +core::arch::global_asm!( + r#" .section .trap, "ax" + .weak _vector_table + .type _vector_table, @function + + .option push + .balign 0x4 // TODO check if this is the correct alignment + .option norelax + .option norvc + + _vector_table: + j _start_trap // Interrupt 0 is used for exceptions + j _start_SupervisorSoft_trap + j _start_DefaultHandler_trap // Interrupt 2 is reserved + j _start_MachineSoft_trap + j _start_DefaultHandler_trap // Interrupt 4 is reserved + j _start_SupervisorTimer_trap + j _start_DefaultHandler_trap // Interrupt 6 is reserved + j _start_MachineTimer_trap + j _start_DefaultHandler_trap // Interrupt 8 is reserved + j _start_SupervisorExternal_trap + j _start_DefaultHandler_trap // Interrupt 10 is reserved + j _start_MachineExternal_trap + + .option pop"# +); diff --git a/riscv-rt/src/lib.rs b/riscv-rt/src/lib.rs index c4572dc4..d147d74e 100644 --- a/riscv-rt/src/lib.rs +++ b/riscv-rt/src/lib.rs @@ -473,6 +473,12 @@ #[cfg(riscv)] mod asm; +#[cfg(not(feature = "no-exceptions"))] +mod exceptions; + +#[cfg(not(feature = "no-interrupts"))] +mod interrupts; + #[cfg(feature = "s-mode")] use riscv::register::scause as xcause; @@ -532,108 +538,12 @@ pub struct TrapFrame { #[export_name = "_start_trap_rust"] pub unsafe extern "C" fn start_trap_rust(trap_frame: *const TrapFrame) { extern "C" { - fn ExceptionHandler(trap_frame: &TrapFrame); - fn _dispatch_interrupt(code: usize); + fn _dispatch_core_interrupt(code: usize); + fn _dispatch_exception(trap_frame: &TrapFrame, code: usize); } - let cause = xcause::read(); - let code = cause.code(); - - if cause.is_exception() { - let trap_frame = &*trap_frame; - if code < __EXCEPTIONS.len() { - let h = &__EXCEPTIONS[code]; - if let Some(handler) = h { - handler(trap_frame); - } else { - ExceptionHandler(trap_frame); - } - } else { - ExceptionHandler(trap_frame); - } - } else { - _dispatch_interrupt(code); + match xcause::read().cause() { + xcause::Trap::Interrupt(code) => _dispatch_core_interrupt(code), + xcause::Trap::Exception(code) => _dispatch_exception(&*trap_frame, code), } } - -extern "C" { - fn InstructionMisaligned(trap_frame: &TrapFrame); - fn InstructionFault(trap_frame: &TrapFrame); - fn IllegalInstruction(trap_frame: &TrapFrame); - fn Breakpoint(trap_frame: &TrapFrame); - fn LoadMisaligned(trap_frame: &TrapFrame); - fn LoadFault(trap_frame: &TrapFrame); - fn StoreMisaligned(trap_frame: &TrapFrame); - fn StoreFault(trap_frame: &TrapFrame); - fn UserEnvCall(trap_frame: &TrapFrame); - fn SupervisorEnvCall(trap_frame: &TrapFrame); - fn MachineEnvCall(trap_frame: &TrapFrame); - fn InstructionPageFault(trap_frame: &TrapFrame); - fn LoadPageFault(trap_frame: &TrapFrame); - fn StorePageFault(trap_frame: &TrapFrame); -} - -#[doc(hidden)] -#[no_mangle] -pub static __EXCEPTIONS: [Option; 16] = [ - Some(InstructionMisaligned), - Some(InstructionFault), - Some(IllegalInstruction), - Some(Breakpoint), - Some(LoadMisaligned), - Some(LoadFault), - Some(StoreMisaligned), - Some(StoreFault), - Some(UserEnvCall), - Some(SupervisorEnvCall), - None, - Some(MachineEnvCall), - Some(InstructionPageFault), - Some(LoadPageFault), - None, - Some(StorePageFault), -]; - -#[export_name = "_dispatch_interrupt"] -unsafe extern "C" fn dispatch_interrupt(code: usize) { - extern "C" { - fn DefaultHandler(); - } - - if code < __INTERRUPTS.len() { - let h = &__INTERRUPTS[code]; - if let Some(handler) = h { - handler(); - } else { - DefaultHandler(); - } - } else { - DefaultHandler(); - } -} - -extern "C" { - fn SupervisorSoft(); - fn MachineSoft(); - fn SupervisorTimer(); - fn MachineTimer(); - fn SupervisorExternal(); - fn MachineExternal(); -} - -#[doc(hidden)] -#[no_mangle] -pub static __INTERRUPTS: [Option; 12] = [ - None, - Some(SupervisorSoft), - None, - Some(MachineSoft), - None, - Some(SupervisorTimer), - None, - Some(MachineTimer), - None, - Some(SupervisorExternal), - None, - Some(MachineExternal), -]; diff --git a/riscv-semihosting/CHANGELOG.md b/riscv-semihosting/CHANGELOG.md index a4ef0e74..c9d61fe6 100644 --- a/riscv-semihosting/CHANGELOG.md +++ b/riscv-semihosting/CHANGELOG.md @@ -7,6 +7,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Changed +- Bump riscv dependency version - Made `cfg` variable selection more robust for custom targets - Fixed debug::exit() on riscv64 QEMU simulation - Fixed an ambiguous link in the generated crate documentation. diff --git a/riscv-semihosting/Cargo.toml b/riscv-semihosting/Cargo.toml index 8b715436..4163186e 100644 --- a/riscv-semihosting/Cargo.toml +++ b/riscv-semihosting/Cargo.toml @@ -24,4 +24,4 @@ default = ["jlink-quirks"] [dependencies] critical-section = "1.0.0" -riscv = { path = "../riscv", version = "0.11.0" } +riscv = { path = "../riscv", version = "0.12.0" } diff --git a/riscv/CHANGELOG.md b/riscv/CHANGELOG.md index 1e7add27..7d1ede5f 100644 --- a/riscv/CHANGELOG.md +++ b/riscv/CHANGELOG.md @@ -9,6 +9,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Added +- riscv-macros crate for non-standard interrupt enums. +- Bump MSRV to 1.61. +- Implementation of `riscv-pac` traits for `Interrupt` and `Exception` enums. +- Tests for the `riscv-pac` trait implementations of `Interrupt` and `Exception` enums. - Add `Mcause::from(usize)` for use in unit tests - Add `Mstatus::from(usize)` for use in unit tests - Add `Mstatus.bits()` diff --git a/riscv/Cargo.toml b/riscv/Cargo.toml index 6ce8e246..eb0b4cfd 100644 --- a/riscv/Cargo.toml +++ b/riscv/Cargo.toml @@ -1,8 +1,8 @@ [package] name = "riscv" -version = "0.11.1" +version = "0.12.0" edition = "2021" -rust-version = "1.60" +rust-version = "1.61" repository = "https://github.com/rust-embedded/riscv" authors = ["The RISC-V Team "] categories = ["embedded", "hardware-support", "no-std"] @@ -20,6 +20,7 @@ targets = [ ] [features] +default = ["riscv-macros"] s-mode = [] critical-section-single-hart = ["critical-section/restore-state-bool"] @@ -27,3 +28,7 @@ critical-section-single-hart = ["critical-section/restore-state-bool"] critical-section = "1.1.2" embedded-hal = "1.0.0" riscv-pac = { path = "../riscv-pac", version = "0.2.0" } +riscv-macros = { path = "macros", version = "0.1.0", optional = true } + +[dev-dependencies] +trybuild = "1.0" diff --git a/riscv/README.md b/riscv/README.md index f67ac78c..2a958981 100644 --- a/riscv/README.md +++ b/riscv/README.md @@ -11,7 +11,7 @@ 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.60 and up. It *might* +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 diff --git a/riscv/macros/Cargo.toml b/riscv/macros/Cargo.toml new file mode 100644 index 00000000..94ef7090 --- /dev/null +++ b/riscv/macros/Cargo.toml @@ -0,0 +1,21 @@ +[package] +authors = [ + "The RISC-V Team ", +] +categories = ["embedded", "no-std"] +description = "Procedural macros re-exported in `riscv`" +documentation = "https://docs.rs/riscv" +keywords = ["riscv", "register", "peripheral"] +license = "MIT OR Apache-2.0" +name = "riscv-macros" +repository = "https://github.com/rust-embedded/riscv" +version = "0.1.0" +edition = "2021" + +[lib] +proc-macro = true + +[dependencies] +proc-macro2 = "1.0" +quote = "1.0" +syn = { version = "2.0" } diff --git a/riscv/macros/src/lib.rs b/riscv/macros/src/lib.rs new file mode 100644 index 00000000..984979f7 --- /dev/null +++ b/riscv/macros/src/lib.rs @@ -0,0 +1,366 @@ +use proc_macro::TokenStream; +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; +use std::collections::HashMap; +use std::str::FromStr; +use syn::{ + parse::{Parse, ParseStream}, + parse_macro_input, + spanned::Spanned, + Data, DeriveInput, Ident, Token, +}; + +/// Traits that can be implemented using the `pac_enum` macro +enum PacTrait { + Exception, + Interrupt(InterruptType), + Priority, + HartId, +} + +impl PacTrait { + /// Returns a token stream representing the trait name + fn trait_name(&self) -> TokenStream2 { + match self { + Self::Exception => quote!(ExceptionNumber), + Self::Interrupt(_) => quote!(InterruptNumber), + Self::Priority => quote!(PriorityNumber), + Self::HartId => quote!(HartIdNumber), + } + } + + /// Returns a token stream representing the data type used to represent the number + fn num_type(&self) -> TokenStream2 { + match self { + Self::Exception => quote!(usize), + Self::Interrupt(_) => quote!(usize), + Self::Priority => quote!(u8), + Self::HartId => quote!(u16), + } + } + + /// Returns a token stream representing the name of the constant that holds the maximum number + fn const_name(&self) -> TokenStream2 { + match self { + Self::Exception => quote!(MAX_EXCEPTION_NUMBER), + Self::Interrupt(_) => quote!(MAX_INTERRUPT_NUMBER), + Self::Priority => quote!(MAX_PRIORITY_NUMBER), + Self::HartId => quote!(MAX_HART_ID_NUMBER), + } + } +} + +impl Parse for PacTrait { + fn parse(input: ParseStream) -> syn::Result { + input.parse::()?; + let trait_name: TokenStream2 = input.parse()?; + match trait_name.to_string().as_str() { + "ExceptionNumber" => Ok(Self::Exception), + "CoreInterruptNumber" => Ok(Self::Interrupt(InterruptType::Core)), + "ExternalInterruptNumber" => Ok(Self::Interrupt(InterruptType::External)), + "PriorityNumber" => Ok(Self::Priority), + "HartIdNumber" => Ok(Self::HartId), + _ => Err(syn::Error::new( + trait_name.span(), + "Unknown trait name. Expected: 'ExceptionNumber', 'CoreInterruptNumber', 'ExternalInterruptNumber', 'PriorityNumber', or 'HartIdNumber'", + )), + } + } +} + +/// Marker traits for interrupts +enum InterruptType { + Core, + External, +} + +impl InterruptType { + /// Returns a token stream representing the name of the marker trait + fn marker_trait_name(&self) -> TokenStream2 { + match self { + Self::Core => quote!(CoreInterruptNumber), + Self::External => quote!(ExternalInterruptNumber), + } + } + + /// Returns a token stream representing the name of the array of interrupt service routines + fn isr_array_name(&self) -> TokenStream2 { + match self { + Self::Core => quote!(__CORE_INTERRUPTS), + Self::External => quote!(__EXTERNAL_INTERRUPTS), + } + } + + /// Returns a token stream representing the name of the interrupt dispatch function + fn dispatch_fn_name(&self) -> TokenStream2 { + match self { + Self::Core => quote!(_dispatch_core_interrupt), + Self::External => quote!(_dispatch_external_interrupt), + } + } +} + +/// Struct containing the information needed to implement the `riscv-pac` traits for an enum +struct PacEnumItem { + /// The name of the enum + name: Ident, + /// The maximum discriminant value + max_number: usize, + /// A map from discriminant values to variant names + numbers: HashMap, +} + +impl PacEnumItem { + fn new(input: &DeriveInput) -> Self { + let name = input.ident.clone(); + let (mut numbers, mut max_number) = (HashMap::new(), 0); + + let variants = match &input.data { + Data::Enum(data) => &data.variants, + _ => panic!("Input is not an enum"), + }; + for v in variants.iter() { + let ident = v.ident.clone(); + let value = match &v.discriminant { + Some(d) => match &d.1 { + syn::Expr::Lit(expr_lit) => match &expr_lit.lit { + syn::Lit::Int(lit_int) => match lit_int.base10_parse::() { + Ok(num) => num, + Err(_) => { + panic!("All variant discriminants must be unsigned integers") + } + }, + _ => panic!("All variant discriminants must be unsigned integers"), + }, + _ => panic!("All variant discriminants must be unsigned integers"), + }, + _ => panic!("Variant must have a discriminant"), + }; + + if numbers.insert(value, ident).is_some() { + panic!("Duplicate discriminant value"); + } + if value > max_number { + max_number = value; + } + } + + Self { + name, + max_number, + numbers, + } + } + + /// Returns a token stream representing the maximum discriminant value of the enum + fn max_discriminant(&self) -> TokenStream2 { + TokenStream2::from_str(&format!("{}", self.max_number)).unwrap() + } + + /// Returns a vector of token streams representing the valid matches in the `pac::from_number` function + fn valid_matches(&self) -> Vec { + self.numbers + .iter() + .map(|(num, ident)| { + TokenStream2::from_str(&format!("{num} => Ok(Self::{ident})")).unwrap() + }) + .collect() + } + + /// Returns a vector of token streams representing the interrupt handler functions + fn interrupt_handlers(&self) -> Vec { + self.numbers + .values() + .map(|ident| { + quote! { fn #ident () } + }) + .collect() + } + + /// Returns a sorted vector of token streams representing all the elements of the interrupt array. + /// If an interrupt number is not present in the enum, the corresponding element is `None`. + /// Otherwise, it is `Some()`. + fn interrupt_array(&self) -> Vec { + let mut vectors = vec![]; + for i in 0..=self.max_number { + if let Some(ident) = self.numbers.get(&i) { + vectors.push(quote! { Some(#ident) }); + } else { + vectors.push(quote! { None }); + } + } + vectors + } + + fn vector_table(&self) -> TokenStream2 { + let mut asm = String::from( + r#" +#[cfg(all(feature = "v-trap", any(target_arch = "riscv32", target_arch = "riscv64")))] +core::arch::global_asm!(" + .section .trap, \"ax\" + .global _vector_table + .type _vector_table, @function + + .option push + .balign 0x4 // TODO check if this is the correct alignment + .option norelax + .option norvc + + _vector_table: + j _start_trap // Interrupt 0 is used for exceptions +"#, + ); + + for i in 1..=self.max_number { + if let Some(ident) = self.numbers.get(&i) { + asm.push_str(&format!(" j _start_{ident}_trap\n")); + } else { + asm.push_str(&format!( + " j _start_DefaultHandler_trap // Interrupt {i} is reserved\n" + )); + } + } + + asm.push_str( + r#" .option pop" +);"#, + ); + + TokenStream2::from_str(&asm).unwrap() + } + + /// Returns a vector of token streams representing the trait implementations for + /// the enum. If the trait is an interrupt trait, the implementation also includes + /// the interrupt handler functions and the interrupt array. + fn impl_trait(&self, attr: &PacTrait) -> Vec { + let mut res = vec![]; + + let name = &self.name; + + let trait_name = attr.trait_name(); + let num_type = attr.num_type(); + let const_name = attr.const_name(); + + let max_discriminant = self.max_discriminant(); + let valid_matches = self.valid_matches(); + + // Push the trait implementation + res.push(quote! { + unsafe impl riscv::#trait_name for #name { + const #const_name: #num_type = #max_discriminant; + + #[inline] + fn number(self) -> #num_type { + self as _ + } + + #[inline] + fn from_number(number: #num_type) -> riscv::result::Result { + match number { + #(#valid_matches,)* + _ => Err(riscv::result::Error::InvalidVariant(number as _)), + } + } + } + }); + + // Interrupt traits require additional code + if let PacTrait::Interrupt(interrupt_type) = attr { + let marker_trait_name = interrupt_type.marker_trait_name(); + + let isr_array_name = interrupt_type.isr_array_name(); + let dispatch_fn_name = interrupt_type.dispatch_fn_name(); + + // Push the marker trait implementation + res.push(quote! { unsafe impl riscv::#marker_trait_name for #name {} }); + + let interrupt_handlers = self.interrupt_handlers(); + let interrupt_array = self.interrupt_array(); + + // Push the interrupt handler functions and the interrupt array + res.push(quote! { + extern "C" { + #(#interrupt_handlers;)* + } + + #[no_mangle] + pub static #isr_array_name: [Option; #max_discriminant + 1] = [ + #(#interrupt_array),* + ]; + + #[no_mangle] + unsafe extern "C" fn #dispatch_fn_name(code: usize) { + extern "C" { + fn DefaultHandler(); + } + + if code < #isr_array_name.len() { + match &#isr_array_name[code] { + Some(handler) => handler(), + None => DefaultHandler(), + } + } else { + DefaultHandler(); + } + } + }); + + if let InterruptType::Core = interrupt_type { + res.push(self.vector_table()); + } + } + + res + } +} + +/// Attribute-like macro that implements the traits of the `riscv-pac` crate for a given enum. +/// +/// As these traits are unsafe, the macro must be called with the `unsafe` keyword followed by the trait name. +/// In this way, we warn callers that they must comply with the requirements of the trait. +/// +/// The trait name must be one of `ExceptionNumber`, `InterruptNumber`, `PriorityNumber`, or `HartIdNumber`. +/// Marker traits `CoreInterruptNumber` and `ExternalInterruptNumber` cannot be implemented using this macro. +/// +/// # Safety +/// +/// The struct to be implemented must comply with the requirements of the specified trait. +/// +/// # Example +/// +/// ```rust +/// use riscv::*; +/// +/// #[repr(usize)] +/// #[pac_enum(unsafe ExceptionNumber)] +/// #[derive(Clone, Copy, Debug, Eq, PartialEq)] +/// enum Exception { +/// E1 = 1, +/// E3 = 3, +/// } +/// +/// fn main() { +/// assert_eq!(Exception::E1.number(), 1); +/// assert_eq!(Exception::E3.number(), 3); +/// +/// assert_eq!(Exception::from_number(1), Ok(Exception::E1)); +/// assert_eq!(Exception::from_number(2), Err(2)); +/// assert_eq!(Exception::from_number(3), Ok(Exception::E3)); +/// +/// assert_eq!(Exception::MAX_EXCEPTION_NUMBER, 3); +/// } +///``` +#[proc_macro_attribute] +pub fn pac_enum(attr: TokenStream, item: TokenStream) -> TokenStream { + let input = parse_macro_input!(item as DeriveInput); + let pac_enum = PacEnumItem::new(&input); + + let attr = parse_macro_input!(attr as PacTrait); + + let trait_impl = pac_enum.impl_trait(&attr); + quote! { + #input + #(#trait_impl)* + } + .into() +} diff --git a/riscv/src/interrupt.rs b/riscv/src/interrupt.rs index b6fcae4a..697d0345 100644 --- a/riscv/src/interrupt.rs +++ b/riscv/src/interrupt.rs @@ -2,188 +2,67 @@ // NOTE: Adapted from cortex-m/src/interrupt.rs -pub mod machine { - use crate::register::{mepc, mstatus}; +use crate::result::Result; - /// Disables all interrupts in the current hart (machine mode). - #[inline] - pub fn disable() { - // SAFETY: It is safe to disable interrupts - unsafe { mstatus::clear_mie() } - } +// re-export useful riscv-pac traits +pub use riscv_pac::{CoreInterruptNumber, ExceptionNumber, InterruptNumber}; - /// Enables all the interrupts in the current hart (machine mode). - /// - /// # Safety - /// - /// Do not call this function inside a critical section. - #[inline] - pub unsafe fn enable() { - mstatus::set_mie() - } +pub mod machine; +pub mod supervisor; - /// Execute closure `f` with interrupts disabled in the current hart (machine mode). - /// - /// This method does not synchronise multiple harts, so it is not suitable for - /// using as a critical section. See the `critical-section` crate for a cross-platform - /// way to enter a critical section which provides a `CriticalSection` token. - /// - /// This crate provides an implementation for `critical-section` suitable for single-hart systems, - /// based on disabling all interrupts. It can be enabled with the `critical-section-single-hart` feature. - #[inline] - pub fn free(f: F) -> R - where - F: FnOnce() -> R, - { - let mstatus = mstatus::read(); +#[cfg(not(feature = "s-mode"))] +pub use machine::*; +#[cfg(feature = "s-mode")] +pub use supervisor::*; - // disable interrupts - disable(); +/// Trap Cause +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum Trap { + Interrupt(I), + Exception(E), +} - let r = f(); +/// Trap Error +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum TrapError { + InvalidInterrupt(usize), + InvalidException(usize), +} - // If the interrupts were active before our `disable` call, then re-enable - // them. Otherwise, keep them disabled - if mstatus.mie() { - unsafe { enable() }; +impl Trap { + /// Converts a target-specific trap cause to a generic trap cause + #[inline] + pub fn from(trap: Trap) -> Self { + match trap { + Trap::Interrupt(interrupt) => Trap::Interrupt(interrupt.number()), + Trap::Exception(exception) => Trap::Exception(exception.number()), } - - r } - /// Execute closure `f` with interrupts enabled in the current hart (machine mode). - /// - /// This method is assumed to be called within an interrupt handler, and allows - /// nested interrupts to occur. After the closure `f` is executed, the [`mstatus`] - /// and [`mepc`] registers are properly restored to their previous values. - /// - /// # Safety - /// - /// - Do not call this function inside a critical section. - /// - This method is assumed to be called within an interrupt handler. - /// - Make sure to clear the interrupt flag that caused the interrupt before calling - /// this method. Otherwise, the interrupt will be re-triggered before executing `f`. + /// Tries to convert the generic trap cause to a target-specific trap cause #[inline] - pub unsafe fn nested(f: F) -> R + pub fn try_into(self) -> Result> where - F: FnOnce() -> R, + I: CoreInterruptNumber, + E: ExceptionNumber, { - let mstatus = mstatus::read(); - let mepc = mepc::read(); - - // enable interrupts to allow nested interrupts - enable(); - - let r = f(); - - // If the interrupts were inactive before our `enable` call, then re-disable - // them. Otherwise, keep them enabled - if !mstatus.mie() { - disable(); + match self { + Trap::Interrupt(code) => Ok(Trap::Interrupt(I::from_number(code)?)), + Trap::Exception(code) => Ok(Trap::Exception(E::from_number(code)?)), } - - // Restore MSTATUS.PIE, MSTATUS.MPP, and SEPC - if mstatus.mpie() { - mstatus::set_mpie(); - } - mstatus::set_mpp(mstatus.mpp()); - mepc::write(mepc); - - r } } -pub mod supervisor { - use crate::register::{sepc, sstatus}; - - /// Disables all interrupts in the current hart (supervisor mode). - #[inline] - pub fn disable() { - // SAFETY: It is safe to disable interrupts - unsafe { sstatus::clear_sie() } - } - /// Enables all the interrupts in the current hart (supervisor mode). - /// - /// # Safety - /// - /// Do not call this function inside a critical section. +impl Trap { + /// Converts a target-specific trap cause to a generic trap cause #[inline] - pub unsafe fn enable() { - sstatus::set_sie() + pub fn into(self) -> Trap { + Trap::from(self) } - /// Execute closure `f` with interrupts disabled in the current hart (supervisor mode). - /// - /// This method does not synchronise multiple harts, so it is not suitable for - /// using as a critical section. See the `critical-section` crate for a cross-platform - /// way to enter a critical section which provides a `CriticalSection` token. - /// - /// This crate provides an implementation for `critical-section` suitable for single-hart systems, - /// based on disabling all interrupts. It can be enabled with the `critical-section-single-hart` feature. + /// Tries to convert the generic trap cause to a target-specific trap cause #[inline] - pub fn free(f: F) -> R - where - F: FnOnce() -> R, - { - let sstatus = sstatus::read(); - - // disable interrupts - disable(); - - let r = f(); - - // If the interrupts were active before our `disable` call, then re-enable - // them. Otherwise, keep them disabled - if sstatus.sie() { - unsafe { enable() }; - } - - r - } - - /// Execute closure `f` with interrupts enabled in the current hart (supervisor mode). - /// - /// This method is assumed to be called within an interrupt handler, and allows - /// nested interrupts to occur. After the closure `f` is executed, the [`sstatus`] - /// and [`sepc`] registers are properly restored to their previous values. - /// - /// # Safety - /// - /// - Do not call this function inside a critical section. - /// - This method is assumed to be called within an interrupt handler. - /// - Make sure to clear the interrupt flag that caused the interrupt before calling - /// this method. Otherwise, the interrupt will be re-triggered before executing `f`. - #[inline] - pub unsafe fn nested(f: F) -> R - where - F: FnOnce() -> R, - { - let sstatus = sstatus::read(); - let sepc = sepc::read(); - - // enable interrupts to allow nested interrupts - enable(); - - let r = f(); - - // If the interrupts were inactive before our `enable` call, then re-disable - // them. Otherwise, keep them enabled - if !sstatus.sie() { - disable(); - } - - // Restore SSTATUS.SPIE, SSTATUS.SPP, and SEPC - if sstatus.spie() { - sstatus::set_spie(); - } - sstatus::set_spp(sstatus.spp()); - sepc::write(sepc); - - r + pub fn try_from(trap: Trap) -> Result { + trap.try_into() } } - -#[cfg(not(feature = "s-mode"))] -pub use machine::*; -#[cfg(feature = "s-mode")] -pub use supervisor::*; diff --git a/riscv/src/interrupt/machine.rs b/riscv/src/interrupt/machine.rs new file mode 100644 index 00000000..d88ceb46 --- /dev/null +++ b/riscv/src/interrupt/machine.rs @@ -0,0 +1,272 @@ +use crate::{ + interrupt::Trap, + register::{mcause, mepc, mstatus}, +}; +use riscv_pac::{ + result::{Error, Result}, + CoreInterruptNumber, ExceptionNumber, InterruptNumber, +}; + +/// Standard M-mode RISC-V interrupts +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[repr(usize)] +pub enum Interrupt { + SupervisorSoft = 1, + MachineSoft = 3, + SupervisorTimer = 5, + MachineTimer = 7, + SupervisorExternal = 9, + MachineExternal = 11, +} + +/// SAFETY: `Interrupt` represents the standard RISC-V interrupts +unsafe impl InterruptNumber for Interrupt { + const MAX_INTERRUPT_NUMBER: usize = Self::MachineExternal as usize; + + #[inline] + fn number(self) -> usize { + self as usize + } + + #[inline] + fn from_number(value: usize) -> Result { + match value { + 1 => Ok(Self::SupervisorSoft), + 3 => Ok(Self::MachineSoft), + 5 => Ok(Self::SupervisorTimer), + 7 => Ok(Self::MachineTimer), + 9 => Ok(Self::SupervisorExternal), + 11 => Ok(Self::MachineExternal), + _ => Err(Error::InvalidVariant(value)), + } + } +} + +/// SAFETY: `Interrupt` represents the standard RISC-V core interrupts +unsafe impl CoreInterruptNumber for Interrupt {} + +/// Standard M-mode RISC-V exceptions +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[repr(usize)] +pub enum Exception { + InstructionMisaligned = 0, + InstructionFault = 1, + IllegalInstruction = 2, + Breakpoint = 3, + LoadMisaligned = 4, + LoadFault = 5, + StoreMisaligned = 6, + StoreFault = 7, + UserEnvCall = 8, + SupervisorEnvCall = 9, + MachineEnvCall = 11, + InstructionPageFault = 12, + LoadPageFault = 13, + StorePageFault = 15, +} + +/// SAFETY: `Exception` represents the standard RISC-V exceptions +unsafe impl ExceptionNumber for Exception { + const MAX_EXCEPTION_NUMBER: usize = Self::StorePageFault as usize; + + #[inline] + fn number(self) -> usize { + self as usize + } + + #[inline] + fn from_number(value: usize) -> Result { + match value { + 0 => Ok(Self::InstructionMisaligned), + 1 => Ok(Self::InstructionFault), + 2 => Ok(Self::IllegalInstruction), + 3 => Ok(Self::Breakpoint), + 4 => Ok(Self::LoadMisaligned), + 5 => Ok(Self::LoadFault), + 6 => Ok(Self::StoreMisaligned), + 7 => Ok(Self::StoreFault), + 8 => Ok(Self::UserEnvCall), + 9 => Ok(Self::SupervisorEnvCall), + 11 => Ok(Self::MachineEnvCall), + 12 => Ok(Self::InstructionPageFault), + 13 => Ok(Self::LoadPageFault), + 15 => Ok(Self::StorePageFault), + _ => Err(Error::InvalidVariant(value)), + } + } +} + +/// Disables all interrupts in the current hart (machine mode). +#[inline] +pub fn disable() { + // SAFETY: It is safe to disable interrupts + unsafe { mstatus::clear_mie() } +} + +/// Enables all the interrupts in the current hart (machine mode). +/// +/// # Safety +/// +/// Do not call this function inside a critical section. +#[inline] +pub unsafe fn enable() { + mstatus::set_mie() +} + +/// Retrieves the cause of a trap in the current hart (machine mode). +/// +/// This function expects the target-specific interrupt and exception types. +/// If the raw cause is not a valid interrupt or exception for the target, it returns an error. +#[inline] +pub fn try_cause() -> Result> { + mcause::read().cause().try_into() +} + +/// Retrieves the cause of a trap in the current hart (machine mode). +/// +/// This function expects the target-specific interrupt and exception types. +/// If the raw cause is not a valid interrupt or exception for the target, it panics. +#[inline] +pub fn cause() -> Trap { + try_cause().unwrap() +} + +/// Execute closure `f` with interrupts disabled in the current hart (machine mode). +/// +/// This method does not synchronise multiple harts, so it is not suitable for +/// using as a critical section. See the `critical-section` crate for a cross-platform +/// way to enter a critical section which provides a `CriticalSection` token. +/// +/// This crate provides an implementation for `critical-section` suitable for single-hart systems, +/// based on disabling all interrupts. It can be enabled with the `critical-section-single-hart` feature. +#[inline] +pub fn free(f: F) -> R +where + F: FnOnce() -> R, +{ + let mstatus = mstatus::read(); + + // disable interrupts + disable(); + + let r = f(); + + // If the interrupts were active before our `disable` call, then re-enable + // them. Otherwise, keep them disabled + if mstatus.mie() { + unsafe { enable() }; + } + + r +} + +/// Execute closure `f` with interrupts enabled in the current hart (machine mode). +/// +/// This method is assumed to be called within an interrupt handler, and allows +/// nested interrupts to occur. After the closure `f` is executed, the [`mstatus`] +/// and [`mepc`] registers are properly restored to their previous values. +/// +/// # Safety +/// +/// - Do not call this function inside a critical section. +/// - This method is assumed to be called within an interrupt handler. +/// - Make sure to clear the interrupt flag that caused the interrupt before calling +/// this method. Otherwise, the interrupt will be re-triggered before executing `f`. +#[inline] +pub unsafe fn nested(f: F) -> R +where + F: FnOnce() -> R, +{ + let mstatus = mstatus::read(); + let mepc = mepc::read(); + + // enable interrupts to allow nested interrupts + enable(); + + let r = f(); + + // If the interrupts were inactive before our `enable` call, then re-disable + // them. Otherwise, keep them enabled + if !mstatus.mie() { + disable(); + } + + // Restore MSTATUS.PIE, MSTATUS.MPP, and SEPC + if mstatus.mpie() { + mstatus::set_mpie(); + } + mstatus::set_mpp(mstatus.mpp()); + mepc::write(mepc); + + r +} + +#[cfg(test)] +mod test { + use super::*; + use Exception::*; + use Interrupt::*; + + #[test] + fn test_interrupt() { + assert_eq!(Interrupt::from_number(1), Ok(SupervisorSoft)); + assert_eq!(Interrupt::from_number(2), Err(Error::InvalidVariant(2))); + assert_eq!(Interrupt::from_number(3), Ok(MachineSoft)); + assert_eq!(Interrupt::from_number(4), Err(Error::InvalidVariant(4))); + assert_eq!(Interrupt::from_number(5), Ok(SupervisorTimer)); + assert_eq!(Interrupt::from_number(6), Err(Error::InvalidVariant(6))); + assert_eq!(Interrupt::from_number(7), Ok(MachineTimer)); + assert_eq!(Interrupt::from_number(8), Err(Error::InvalidVariant(8))); + assert_eq!(Interrupt::from_number(9), Ok(SupervisorExternal)); + assert_eq!(Interrupt::from_number(10), Err(Error::InvalidVariant(10))); + assert_eq!(Interrupt::from_number(11), Ok(MachineExternal)); + assert_eq!(Interrupt::from_number(12), Err(Error::InvalidVariant(12))); + + assert_eq!(SupervisorSoft.number(), 1); + assert_eq!(MachineSoft.number(), 3); + assert_eq!(SupervisorTimer.number(), 5); + assert_eq!(MachineTimer.number(), 7); + assert_eq!(SupervisorExternal.number(), 9); + assert_eq!(MachineExternal.number(), 11); + + assert_eq!(MachineExternal.number(), Interrupt::MAX_INTERRUPT_NUMBER) + } + + #[test] + fn test_exception() { + assert_eq!(Exception::from_number(0), Ok(InstructionMisaligned)); + assert_eq!(Exception::from_number(1), Ok(InstructionFault)); + assert_eq!(Exception::from_number(2), Ok(IllegalInstruction)); + assert_eq!(Exception::from_number(3), Ok(Breakpoint)); + assert_eq!(Exception::from_number(4), Ok(LoadMisaligned)); + assert_eq!(Exception::from_number(5), Ok(LoadFault)); + assert_eq!(Exception::from_number(6), Ok(StoreMisaligned)); + assert_eq!(Exception::from_number(7), Ok(StoreFault)); + assert_eq!(Exception::from_number(8), Ok(UserEnvCall)); + assert_eq!(Exception::from_number(9), Ok(SupervisorEnvCall)); + assert_eq!(Exception::from_number(10), Err(Error::InvalidVariant(10))); + assert_eq!(Exception::from_number(11), Ok(MachineEnvCall)); + assert_eq!(Exception::from_number(12), Ok(InstructionPageFault)); + assert_eq!(Exception::from_number(13), Ok(LoadPageFault)); + assert_eq!(Exception::from_number(14), Err(Error::InvalidVariant(14))); + assert_eq!(Exception::from_number(15), Ok(StorePageFault)); + assert_eq!(Exception::from_number(16), Err(Error::InvalidVariant(16))); + + assert_eq!(InstructionMisaligned.number(), 0); + assert_eq!(InstructionFault.number(), 1); + assert_eq!(IllegalInstruction.number(), 2); + assert_eq!(Breakpoint.number(), 3); + assert_eq!(LoadMisaligned.number(), 4); + assert_eq!(LoadFault.number(), 5); + assert_eq!(StoreMisaligned.number(), 6); + assert_eq!(StoreFault.number(), 7); + assert_eq!(UserEnvCall.number(), 8); + assert_eq!(SupervisorEnvCall.number(), 9); + assert_eq!(MachineEnvCall.number(), 11); + assert_eq!(InstructionPageFault.number(), 12); + assert_eq!(LoadPageFault.number(), 13); + assert_eq!(StorePageFault.number(), 15); + + assert_eq!(StorePageFault.number(), Exception::MAX_EXCEPTION_NUMBER) + } +} diff --git a/riscv/src/interrupt/supervisor.rs b/riscv/src/interrupt/supervisor.rs new file mode 100644 index 00000000..ed25d236 --- /dev/null +++ b/riscv/src/interrupt/supervisor.rs @@ -0,0 +1,259 @@ +use crate::{ + interrupt::Trap, + register::{scause, sepc, sstatus}, +}; +use riscv_pac::{ + result::{Error, Result}, + CoreInterruptNumber, ExceptionNumber, InterruptNumber, +}; + +/// Interrupt +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +#[repr(usize)] +pub enum Interrupt { + SupervisorSoft = 1, + SupervisorTimer = 5, + SupervisorExternal = 9, +} + +/// SAFETY: `Interrupt` represents the standard RISC-V interrupts +unsafe impl InterruptNumber for Interrupt { + const MAX_INTERRUPT_NUMBER: usize = Self::SupervisorExternal as usize; + + #[inline] + fn number(self) -> usize { + self as usize + } + + #[inline] + fn from_number(value: usize) -> Result { + match value { + 1 => Ok(Self::SupervisorSoft), + 5 => Ok(Self::SupervisorTimer), + 9 => Ok(Self::SupervisorExternal), + _ => Err(Error::InvalidVariant(value)), + } + } +} + +/// SAFETY: `Interrupt` represents the standard RISC-V core interrupts +unsafe impl CoreInterruptNumber for Interrupt {} + +/// Exception +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +#[repr(usize)] +pub enum Exception { + InstructionMisaligned = 0, + InstructionFault = 1, + IllegalInstruction = 2, + Breakpoint = 3, + LoadMisaligned = 4, + LoadFault = 5, + StoreMisaligned = 6, + StoreFault = 7, + UserEnvCall = 8, + SupervisorEnvCall = 9, + InstructionPageFault = 12, + LoadPageFault = 13, + StorePageFault = 15, +} + +/// SAFETY: `Exception` represents the standard RISC-V exceptions +unsafe impl ExceptionNumber for Exception { + const MAX_EXCEPTION_NUMBER: usize = Self::StorePageFault as usize; + + #[inline] + fn number(self) -> usize { + self as usize + } + + #[inline] + fn from_number(value: usize) -> Result { + match value { + 0 => Ok(Self::InstructionMisaligned), + 1 => Ok(Self::InstructionFault), + 2 => Ok(Self::IllegalInstruction), + 3 => Ok(Self::Breakpoint), + 4 => Ok(Self::LoadMisaligned), + 5 => Ok(Self::LoadFault), + 6 => Ok(Self::StoreMisaligned), + 7 => Ok(Self::StoreFault), + 8 => Ok(Self::UserEnvCall), + 9 => Ok(Self::SupervisorEnvCall), + 12 => Ok(Self::InstructionPageFault), + 13 => Ok(Self::LoadPageFault), + 15 => Ok(Self::StorePageFault), + _ => Err(Error::InvalidVariant(value)), + } + } +} + +/// Disables all interrupts in the current hart (supervisor mode). +#[inline] +pub fn disable() { + // SAFETY: It is safe to disable interrupts + unsafe { sstatus::clear_sie() } +} + +/// Enables all the interrupts in the current hart (supervisor mode). +/// +/// # Safety +/// +/// Do not call this function inside a critical section. +#[inline] +pub unsafe fn enable() { + sstatus::set_sie() +} + +/// Retrieves the cause of a trap in the current hart (supervisor mode). +/// +/// This function expects the target-specific interrupt and exception types. +/// If the raw cause is not a valid interrupt or exception for the target, it returns an error. +#[inline] +pub fn try_cause() -> Result> { + scause::read().cause().try_into() +} + +/// Retrieves the cause of a trap in the current hart (supervisor mode). +/// +/// This function expects the target-specific interrupt and exception types. +/// If the raw cause is not a valid interrupt or exception for the target, it panics. +#[inline] +pub fn cause() -> Trap { + try_cause().unwrap() +} + +/// Execute closure `f` with interrupts disabled in the current hart (supervisor mode). +/// +/// This method does not synchronise multiple harts, so it is not suitable for +/// using as a critical section. See the `critical-section` crate for a cross-platform +/// way to enter a critical section which provides a `CriticalSection` token. +/// +/// This crate provides an implementation for `critical-section` suitable for single-hart systems, +/// based on disabling all interrupts. It can be enabled with the `critical-section-single-hart` feature. +#[inline] +pub fn free(f: F) -> R +where + F: FnOnce() -> R, +{ + let sstatus = sstatus::read(); + + // disable interrupts + disable(); + + let r = f(); + + // If the interrupts were active before our `disable` call, then re-enable + // them. Otherwise, keep them disabled + if sstatus.sie() { + unsafe { enable() }; + } + + r +} + +/// Execute closure `f` with interrupts enabled in the current hart (supervisor mode). +/// This method is assumed to be called within an interrupt handler, and allows +/// nested interrupts to occur. After the closure `f` is executed, the [`sstatus`] +/// and [`sepc`] registers are properly restored to their previous values. +/// +/// # Safety +/// +/// - Do not call this function inside a critical section. +/// - This method is assumed to be called within an interrupt handler. +/// - Make sure to clear the interrupt flag that caused the interrupt before calling +/// this method. Otherwise, the interrupt will be re-triggered before executing `f`. +#[inline] +pub unsafe fn nested(f: F) -> R +where + F: FnOnce() -> R, +{ + let sstatus = sstatus::read(); + let sepc = sepc::read(); + + // enable interrupts to allow nested interrupts + enable(); + + let r = f(); + + // If the interrupts were inactive before our `enable` call, then re-disable + // them. Otherwise, keep them enabled + if !sstatus.sie() { + disable(); + } + + // Restore SSTATUS.SPIE, SSTATUS.SPP, and SEPC + if sstatus.spie() { + sstatus::set_spie(); + } + sstatus::set_spp(sstatus.spp()); + sepc::write(sepc); + + r +} + +#[cfg(test)] +mod test { + use super::*; + use Exception::*; + use Interrupt::*; + + #[test] + fn test_interrupt() { + assert_eq!(Interrupt::from_number(1), Ok(SupervisorSoft)); + assert_eq!(Interrupt::from_number(2), Err(Error::InvalidVariant(2))); + assert_eq!(Interrupt::from_number(3), Err(Error::InvalidVariant(3))); + assert_eq!(Interrupt::from_number(4), Err(Error::InvalidVariant(4))); + assert_eq!(Interrupt::from_number(5), Ok(SupervisorTimer)); + assert_eq!(Interrupt::from_number(6), Err(Error::InvalidVariant(6))); + assert_eq!(Interrupt::from_number(7), Err(Error::InvalidVariant(7))); + assert_eq!(Interrupt::from_number(8), Err(Error::InvalidVariant(8))); + assert_eq!(Interrupt::from_number(9), Ok(SupervisorExternal)); + assert_eq!(Interrupt::from_number(10), Err(Error::InvalidVariant(10))); + assert_eq!(Interrupt::from_number(11), Err(Error::InvalidVariant(11))); + assert_eq!(Interrupt::from_number(12), Err(Error::InvalidVariant(12))); + + assert_eq!(SupervisorSoft.number(), 1); + assert_eq!(SupervisorTimer.number(), 5); + assert_eq!(SupervisorExternal.number(), 9); + + assert_eq!(SupervisorExternal.number(), Interrupt::MAX_INTERRUPT_NUMBER) + } + + #[test] + fn test_exception() { + assert_eq!(Exception::from_number(0), Ok(InstructionMisaligned)); + assert_eq!(Exception::from_number(1), Ok(InstructionFault)); + assert_eq!(Exception::from_number(2), Ok(IllegalInstruction)); + assert_eq!(Exception::from_number(3), Ok(Breakpoint)); + assert_eq!(Exception::from_number(4), Ok(LoadMisaligned)); + assert_eq!(Exception::from_number(5), Ok(LoadFault)); + assert_eq!(Exception::from_number(6), Ok(StoreMisaligned)); + assert_eq!(Exception::from_number(7), Ok(StoreFault)); + assert_eq!(Exception::from_number(8), Ok(UserEnvCall)); + assert_eq!(Exception::from_number(9), Ok(SupervisorEnvCall)); + assert_eq!(Exception::from_number(10), Err(Error::InvalidVariant(10))); + assert_eq!(Exception::from_number(11), Err(Error::InvalidVariant(11))); + assert_eq!(Exception::from_number(12), Ok(InstructionPageFault)); + assert_eq!(Exception::from_number(13), Ok(LoadPageFault)); + assert_eq!(Exception::from_number(14), Err(Error::InvalidVariant(14))); + assert_eq!(Exception::from_number(15), Ok(StorePageFault)); + assert_eq!(Exception::from_number(16), Err(Error::InvalidVariant(16))); + + assert_eq!(InstructionMisaligned.number(), 0); + assert_eq!(InstructionFault.number(), 1); + assert_eq!(IllegalInstruction.number(), 2); + assert_eq!(Breakpoint.number(), 3); + assert_eq!(LoadMisaligned.number(), 4); + assert_eq!(LoadFault.number(), 5); + assert_eq!(StoreMisaligned.number(), 6); + assert_eq!(StoreFault.number(), 7); + assert_eq!(UserEnvCall.number(), 8); + assert_eq!(SupervisorEnvCall.number(), 9); + assert_eq!(InstructionPageFault.number(), 12); + assert_eq!(LoadPageFault.number(), 13); + assert_eq!(StorePageFault.number(), 15); + + assert_eq!(StorePageFault.number(), Exception::MAX_EXCEPTION_NUMBER) + } +} diff --git a/riscv/src/lib.rs b/riscv/src/lib.rs index daa859f5..6290c18c 100644 --- a/riscv/src/lib.rs +++ b/riscv/src/lib.rs @@ -40,6 +40,10 @@ pub(crate) mod bits; pub mod delay; pub mod interrupt; pub mod register; + +// Re-export crates of the RISC-V ecosystem +#[cfg(feature = "riscv-macros")] +pub use riscv_macros::*; pub use riscv_pac::*; #[macro_use] diff --git a/riscv/src/register/mcause.rs b/riscv/src/register/mcause.rs index ff7f730b..fde22af4 100644 --- a/riscv/src/register/mcause.rs +++ b/riscv/src/register/mcause.rs @@ -1,5 +1,7 @@ //! mcause register +pub use crate::interrupt::Trap; + /// mcause register #[derive(Clone, Copy, Debug)] pub struct Mcause { @@ -13,109 +15,6 @@ impl From for Mcause { } } -/// Trap Cause -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub enum Trap { - Interrupt(Interrupt), - Exception(Exception), -} - -/// Interrupt -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -#[repr(usize)] -pub enum Interrupt { - SupervisorSoft = 1, - MachineSoft = 3, - SupervisorTimer = 5, - MachineTimer = 7, - SupervisorExternal = 9, - MachineExternal = 11, - Unknown, -} - -/// Exception -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -#[repr(usize)] -pub enum Exception { - InstructionMisaligned = 0, - InstructionFault = 1, - IllegalInstruction = 2, - Breakpoint = 3, - LoadMisaligned = 4, - LoadFault = 5, - StoreMisaligned = 6, - StoreFault = 7, - UserEnvCall = 8, - SupervisorEnvCall = 9, - MachineEnvCall = 11, - InstructionPageFault = 12, - LoadPageFault = 13, - StorePageFault = 15, - Unknown, -} - -impl From for Interrupt { - #[inline] - fn from(nr: usize) -> Self { - match nr { - 1 => Self::SupervisorSoft, - 3 => Self::MachineSoft, - 5 => Self::SupervisorTimer, - 7 => Self::MachineTimer, - 9 => Self::SupervisorExternal, - 11 => Self::MachineExternal, - _ => Self::Unknown, - } - } -} - -impl TryFrom for usize { - type Error = Interrupt; - - #[inline] - fn try_from(value: Interrupt) -> Result { - match value { - Interrupt::Unknown => Err(Self::Error::Unknown), - _ => Ok(value as Self), - } - } -} - -impl From for Exception { - #[inline] - fn from(nr: usize) -> Self { - match nr { - 0 => Self::InstructionMisaligned, - 1 => Self::InstructionFault, - 2 => Self::IllegalInstruction, - 3 => Self::Breakpoint, - 4 => Self::LoadMisaligned, - 5 => Self::LoadFault, - 6 => Self::StoreMisaligned, - 7 => Self::StoreFault, - 8 => Self::UserEnvCall, - 9 => Self::SupervisorEnvCall, - 11 => Self::MachineEnvCall, - 12 => Self::InstructionPageFault, - 13 => Self::LoadPageFault, - 15 => Self::StorePageFault, - _ => Self::Unknown, - } - } -} - -impl TryFrom for usize { - type Error = Exception; - - #[inline] - fn try_from(value: Exception) -> Result { - match value { - Exception::Unknown => Err(Self::Error::Unknown), - _ => Ok(value as Self), - } - } -} - impl Mcause { /// Returns the contents of the register as raw bits #[inline] @@ -129,13 +28,18 @@ impl Mcause { self.bits & !(1 << (usize::BITS as usize - 1)) } - /// Trap Cause + /// Returns the trap cause represented by this register. + /// + /// # Note + /// + /// This method returns a **raw trap cause**, which means that values are represented as `usize`. + /// To get a target-specific trap cause, use [`Trap::try_into`] with your target-specific M-Mode trap cause types. #[inline] - pub fn cause(&self) -> Trap { + pub fn cause(&self) -> Trap { if self.is_interrupt() { - Trap::Interrupt(Interrupt::from(self.code())) + Trap::Interrupt(self.code()) } else { - Trap::Exception(Exception::from(self.code())) + Trap::Exception(self.code()) } } diff --git a/riscv/src/register/scause.rs b/riscv/src/register/scause.rs index 79fa1b9e..ed6ada60 100644 --- a/riscv/src/register/scause.rs +++ b/riscv/src/register/scause.rs @@ -1,106 +1,14 @@ //! scause register +pub use crate::interrupt::Trap; +pub use riscv_pac::{CoreInterruptNumber, ExceptionNumber, InterruptNumber}; // re-export useful riscv-pac traits + /// scause register #[derive(Clone, Copy)] pub struct Scause { bits: usize, } -/// Trap Cause -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub enum Trap { - Interrupt(Interrupt), - Exception(Exception), -} - -/// Interrupt -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -#[repr(usize)] -pub enum Interrupt { - SupervisorSoft = 1, - SupervisorTimer = 5, - SupervisorExternal = 9, - Unknown, -} - -/// Exception -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -#[repr(usize)] -pub enum Exception { - InstructionMisaligned = 0, - InstructionFault = 1, - IllegalInstruction = 2, - Breakpoint = 3, - LoadMisaligned = 4, - LoadFault = 5, - StoreMisaligned = 6, - StoreFault = 7, - UserEnvCall = 8, - SupervisorEnvCall = 9, - InstructionPageFault = 12, - LoadPageFault = 13, - StorePageFault = 15, - Unknown, -} - -impl From for Interrupt { - #[inline] - fn from(nr: usize) -> Self { - match nr { - 1 => Self::SupervisorSoft, - 5 => Self::SupervisorTimer, - 9 => Self::SupervisorExternal, - _ => Self::Unknown, - } - } -} - -impl TryFrom for usize { - type Error = Interrupt; - - #[inline] - fn try_from(value: Interrupt) -> Result { - match value { - Interrupt::Unknown => Err(Self::Error::Unknown), - _ => Ok(value as Self), - } - } -} - -impl From for Exception { - #[inline] - fn from(nr: usize) -> Self { - match nr { - 0 => Self::InstructionMisaligned, - 1 => Self::InstructionFault, - 2 => Self::IllegalInstruction, - 3 => Self::Breakpoint, - 4 => Self::LoadMisaligned, - 5 => Self::LoadFault, - 6 => Self::StoreMisaligned, - 7 => Self::StoreFault, - 8 => Self::UserEnvCall, - 9 => Self::SupervisorEnvCall, - 12 => Self::InstructionPageFault, - 13 => Self::LoadPageFault, - 15 => Self::StorePageFault, - _ => Self::Unknown, - } - } -} - -impl TryFrom for usize { - type Error = Exception; - - #[inline] - fn try_from(value: Exception) -> Result { - match value { - Exception::Unknown => Err(Self::Error::Unknown), - _ => Ok(value as Self), - } - } -} - impl Scause { /// Returns the contents of the register as raw bits #[inline] @@ -114,13 +22,18 @@ impl Scause { self.bits & !(1 << (usize::BITS as usize - 1)) } - /// Trap Cause + /// Returns the trap cause represented by this register. + /// + /// # Note + /// + /// This method returns a **raw trap cause**, which means that values are represented as `usize`. + /// To get a target-specific trap cause, use [`Trap::try_into`] with your target-specific S-Mode trap cause types. #[inline] - pub fn cause(&self) -> Trap { + pub fn cause(&self) -> Trap { if self.is_interrupt() { - Trap::Interrupt(Interrupt::from(self.code())) + Trap::Interrupt(self.code()) } else { - Trap::Exception(Exception::from(self.code())) + Trap::Exception(self.code()) } } @@ -148,13 +61,12 @@ pub unsafe fn write(bits: usize) { /// Set supervisor cause register to corresponding cause. #[inline] -pub unsafe fn set(cause: Trap) { +pub unsafe fn set(cause: Trap) { let bits = match cause { Trap::Interrupt(i) => { - let i = usize::try_from(i).expect("unknown interrupt"); - i | (1 << (usize::BITS as usize - 1)) // interrupt bit is 1 + i.number() | (1 << (usize::BITS as usize - 1)) // interrupt bit is 1 } - Trap::Exception(e) => usize::try_from(e).expect("unknown exception"), + Trap::Exception(e) => e.number(), }; _write(bits); } diff --git a/riscv/tests/test.rs b/riscv/tests/test.rs new file mode 100644 index 00000000..c74b861b --- /dev/null +++ b/riscv/tests/test.rs @@ -0,0 +1,6 @@ +#[test] +fn ui() { + let t = trybuild::TestCases::new(); + t.compile_fail("tests/ui/fail_*.rs"); + t.pass("tests/ui/pass_*.rs"); +} diff --git a/riscv/tests/ui/fail_empty_macro.rs b/riscv/tests/ui/fail_empty_macro.rs new file mode 100644 index 00000000..8891da0a --- /dev/null +++ b/riscv/tests/ui/fail_empty_macro.rs @@ -0,0 +1,9 @@ +#[riscv::pac_enum] +#[derive(Clone, Copy, Debug, PartialEq)] +enum Interrupt { + I1 = 1, + I2 = 2, + I4 = 4, +} + +fn main() {} diff --git a/riscv/tests/ui/fail_empty_macro.stderr b/riscv/tests/ui/fail_empty_macro.stderr new file mode 100644 index 00000000..28c594b7 --- /dev/null +++ b/riscv/tests/ui/fail_empty_macro.stderr @@ -0,0 +1,7 @@ +error: unexpected end of input, expected `unsafe` + --> tests/ui/fail_empty_macro.rs:1:1 + | +1 | #[riscv::pac_enum] + | ^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in the attribute macro `riscv::pac_enum` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/riscv/tests/ui/fail_no_unsafe.rs b/riscv/tests/ui/fail_no_unsafe.rs new file mode 100644 index 00000000..a304fa9e --- /dev/null +++ b/riscv/tests/ui/fail_no_unsafe.rs @@ -0,0 +1,9 @@ +#[riscv::pac_enum(InterruptNumber)] +#[derive(Clone, Copy, Debug, PartialEq)] +enum Interrupt { + I1 = 1, + I2 = 2, + I4 = 4, +} + +fn main() {} diff --git a/riscv/tests/ui/fail_no_unsafe.stderr b/riscv/tests/ui/fail_no_unsafe.stderr new file mode 100644 index 00000000..68fec6f8 --- /dev/null +++ b/riscv/tests/ui/fail_no_unsafe.stderr @@ -0,0 +1,5 @@ +error: expected `unsafe` + --> tests/ui/fail_no_unsafe.rs:1:19 + | +1 | #[riscv::pac_enum(InterruptNumber)] + | ^^^^^^^^^^^^^^^ diff --git a/riscv/tests/ui/fail_unknown_trait.rs b/riscv/tests/ui/fail_unknown_trait.rs new file mode 100644 index 00000000..dc6c5d44 --- /dev/null +++ b/riscv/tests/ui/fail_unknown_trait.rs @@ -0,0 +1,9 @@ +#[riscv::pac_enum(unsafe InterruptNumber)] +#[derive(Clone, Copy, Debug, PartialEq)] +enum Interrupt { + I1 = 1, + I2 = 2, + I4 = 4, +} + +fn main() {} diff --git a/riscv/tests/ui/fail_unknown_trait.stderr b/riscv/tests/ui/fail_unknown_trait.stderr new file mode 100644 index 00000000..337f8548 --- /dev/null +++ b/riscv/tests/ui/fail_unknown_trait.stderr @@ -0,0 +1,5 @@ +error: Unknown trait name. Expected: 'ExceptionNumber', 'CoreInterruptNumber', 'ExternalInterruptNumber', 'PriorityNumber', or 'HartIdNumber' + --> tests/ui/fail_unknown_trait.rs:1:26 + | +1 | #[riscv::pac_enum(unsafe InterruptNumber)] + | ^^^^^^^^^^^^^^^ diff --git a/riscv/tests/ui/pass_test.rs b/riscv/tests/ui/pass_test.rs new file mode 100644 index 00000000..c80ead3e --- /dev/null +++ b/riscv/tests/ui/pass_test.rs @@ -0,0 +1,116 @@ +use riscv::result::Error; +use riscv::*; + +#[pac_enum(unsafe ExceptionNumber)] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +enum Exception { + E1 = 1, + E3 = 3, +} + +#[pac_enum(unsafe CoreInterruptNumber)] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +enum Interrupt { + I1 = 1, + I2 = 2, + I4 = 4, + I7 = 7, +} + +#[pac_enum(unsafe PriorityNumber)] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +enum Priority { + P0 = 0, + P1 = 1, + P2 = 2, + P3 = 3, +} + +#[pac_enum(unsafe HartIdNumber)] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +enum HartId { + H0 = 0, + H1 = 1, + H2 = 2, +} + +mod isr { + #[export_name = "DefaultHandler"] + fn default_handler() {} + + #[export_name = "I1"] + fn i1() {} + + #[export_name = "I2"] + fn i2() {} + + #[export_name = "I4"] + fn i4() {} + + #[export_name = "I7"] + fn i7() {} +} + +fn main() { + assert_eq!(Exception::E1.number(), 1); + assert_eq!(Exception::E3.number(), 3); + + assert_eq!(Exception::from_number(0), Err(Error::InvalidVariant(0))); + assert_eq!(Exception::from_number(1), Ok(Exception::E1)); + assert_eq!(Exception::from_number(2), Err(Error::InvalidVariant(2))); + assert_eq!(Exception::from_number(3), Ok(Exception::E3)); + assert_eq!(Exception::from_number(4), Err(Error::InvalidVariant(4))); + + assert_eq!(Exception::MAX_EXCEPTION_NUMBER, 3); + + assert_eq!(Interrupt::I1.number(), 1); + assert_eq!(Interrupt::I2.number(), 2); + assert_eq!(Interrupt::I4.number(), 4); + assert_eq!(Interrupt::I7.number(), 7); + + assert_eq!(Interrupt::from_number(0), Err(Error::InvalidVariant(0))); + assert_eq!(Interrupt::from_number(1), Ok(Interrupt::I1)); + assert_eq!(Interrupt::from_number(2), Ok(Interrupt::I2)); + assert_eq!(Interrupt::from_number(3), Err(Error::InvalidVariant(3))); + assert_eq!(Interrupt::from_number(4), Ok(Interrupt::I4)); + assert_eq!(Interrupt::from_number(5), Err(Error::InvalidVariant(5))); + assert_eq!(Interrupt::from_number(6), Err(Error::InvalidVariant(6))); + assert_eq!(Interrupt::from_number(7), Ok(Interrupt::I7)); + + assert_eq!(Interrupt::MAX_INTERRUPT_NUMBER, 7); + + assert_eq!(__CORE_INTERRUPTS.len(), Interrupt::MAX_INTERRUPT_NUMBER + 1); + + assert!(__CORE_INTERRUPTS[0].is_none()); + assert!(__CORE_INTERRUPTS[1].is_some()); + assert!(__CORE_INTERRUPTS[2].is_some()); + assert!(__CORE_INTERRUPTS[3].is_none()); + assert!(__CORE_INTERRUPTS[4].is_some()); + assert!(__CORE_INTERRUPTS[5].is_none()); + assert!(__CORE_INTERRUPTS[6].is_none()); + assert!(__CORE_INTERRUPTS[7].is_some()); + + 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(Error::InvalidVariant(4))); + + assert_eq!(Priority::MAX_PRIORITY_NUMBER, 3); + + 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(Error::InvalidVariant(3))); + + assert_eq!(HartId::MAX_HART_ID_NUMBER, 2); +} From e177b0c8138747c227c83312b1da1406d550ff8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rom=C3=A1n=20C=C3=A1rdenas=20Rodr=C3=ADguez?= Date: Sun, 28 Jul 2024 20:03:10 +0200 Subject: [PATCH 05/14] core_interrupt, exception, and external_interrupt macros --- riscv-rt/CHANGELOG.md | 4 +- riscv-rt/Cargo.toml | 1 + riscv-rt/examples/empty.rs | 87 ++++++++++++++++++- riscv-rt/macros/src/lib.rs | 167 ++++++++++++++++++++++++++++++------- riscv-rt/src/lib.rs | 9 +- 5 files changed, 230 insertions(+), 38 deletions(-) diff --git a/riscv-rt/CHANGELOG.md b/riscv-rt/CHANGELOG.md index 3b451d3e..a843ec59 100644 --- a/riscv-rt/CHANGELOG.md +++ b/riscv-rt/CHANGELOG.md @@ -13,7 +13,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Add `no-interrupts` feature to opt-out the default implementation of `_dispatch_core_interrupt` - Add `pre_init_trap` to detect early errors during the boot process. - Add `v-trap` feature to enable interrupt handling in vectored mode. -- Add `interrupt` proc macro to help defining interrupt handlers. +- Add `core_interrupt` proc macro to help defining core interrupt handlers. +- Add `external_interrupt` proc macro to help defining external interrupt handlers. +- Add `exception` proc macro to help defining exception handlers. If `v-trap` feature is enabled, this macro also generates its corresponding trap. - Add `u-boot` feature, so that you can start your elf binary with u-boot and work with passed arguments. diff --git a/riscv-rt/Cargo.toml b/riscv-rt/Cargo.toml index f07da9e9..f6000653 100644 --- a/riscv-rt/Cargo.toml +++ b/riscv-rt/Cargo.toml @@ -14,6 +14,7 @@ links = "riscv-rt" # Prevent multiple versions of riscv-rt being linked [dependencies] riscv = { path = "../riscv", version = "0.12.0" } +riscv-pac = { path = "../riscv-pac", version = "0.2.0" } riscv-rt-macros = { path = "macros", version = "0.2.1" } [dev-dependencies] diff --git a/riscv-rt/examples/empty.rs b/riscv-rt/examples/empty.rs index b770cba0..837b6aea 100644 --- a/riscv-rt/examples/empty.rs +++ b/riscv-rt/examples/empty.rs @@ -4,7 +4,38 @@ extern crate panic_halt; extern crate riscv_rt; -use riscv_rt::{entry, interrupt}; +use riscv_rt::{core_interrupt, entry, exception, external_interrupt}; + +use riscv::{ + interrupt::{Exception, Interrupt}, + result::*, +}; + +/// Just a dummy type to test the `ExternalInterrupt` trait. +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum ExternalInterrupt { + GPIO, + UART, +} + +unsafe impl riscv::InterruptNumber for ExternalInterrupt { + const MAX_INTERRUPT_NUMBER: usize = 1; + + #[inline] + fn number(self) -> usize { + self as usize + } + + #[inline] + fn from_number(value: usize) -> Result { + match value { + 0 => Ok(Self::GPIO), + 1 => Ok(Self::UART), + _ => Err(Error::InvalidVariant(value)), + } + } +} +unsafe impl riscv::ExternalInterruptNumber for ExternalInterrupt {} #[entry] fn main() -> ! { @@ -12,8 +43,58 @@ fn main() -> ! { loop {} } -#[interrupt] -fn MachineSoft() { +/* EXAMPLES OF USING THE core_interrupt MACRO FOR CORE INTERRUPT HANDLERS. +IF v-trap ENABLED, THE MACRO ALSO DEFINES _start_COREINTERRUPT_trap routines */ + +/// Handler with the simplest signature. +#[core_interrupt(Interrupt::SupervisorSoft)] +fn supervisor_soft() { + // do something here + loop {} +} + +/// Handler with the most complete signature. +#[core_interrupt(Interrupt::SupervisorTimer)] +unsafe fn supervisor_timer() -> ! { + // do something here + loop {} +} + +/* EXAMPLES OF USING THE external_interrupt MACRO FOR EXTERNAL INTERRUPT HANDLERS. */ + +/// Handler with the simplest signature. +#[external_interrupt(ExternalInterrupt::GPIO)] +fn external_gpio() { + // do something here + loop {} +} + +/// Handler with the most complete signature. +#[external_interrupt(ExternalInterrupt::UART)] +unsafe fn external_uart() -> ! { + // do something here + loop {} +} + +/* EXAMPLES OF USING THE exception MACRO FOR EXCEPTION HANDLERS. */ + +/// Handler with the simplest signature. +#[exception(Exception::InstructionMisaligned)] +fn instruction_misaligned() { + // do something here + loop {} +} + +/// Handler with the most complete signature. +#[exception(Exception::IllegalInstruction)] +unsafe fn illegal_instruction(_trap: &riscv_rt::TrapFrame) -> ! { + // do something here + loop {} +} + +// The reference to TrapFrame can be mutable if the handler needs to modify it. +#[exception(Exception::Breakpoint)] +unsafe fn breakpoint(_trap: &mut riscv_rt::TrapFrame) -> ! { // do something here loop {} } diff --git a/riscv-rt/macros/src/lib.rs b/riscv-rt/macros/src/lib.rs index df7d5a01..0bc88b18 100644 --- a/riscv-rt/macros/src/lib.rs +++ b/riscv-rt/macros/src/lib.rs @@ -13,7 +13,7 @@ use syn::{ parse::{self, Parse}, punctuated::Punctuated, spanned::Spanned, - FnArg, ItemFn, LitInt, LitStr, PatType, ReturnType, Type, Visibility, + FnArg, ItemFn, LitInt, LitStr, PatType, Path, ReturnType, Type, Visibility, }; use proc_macro::TokenStream; @@ -357,12 +357,18 @@ pub fn loop_global_asm(input: TokenStream) -> TokenStream { res.parse().unwrap() } -#[derive(Clone, Copy)] +#[derive(Clone, Copy, Debug)] enum RiscvArch { Rv32, Rv64, } +#[derive(Clone, Copy, Debug)] +enum RiscvPacItem { + ExternalInterrupt, + CoreInterrupt, +} + /// Size of the trap frame (in number of registers) const TRAP_SIZE: usize = 16; @@ -549,26 +555,122 @@ _continue_interrupt_trap: } #[proc_macro_attribute] -/// Attribute to declare an interrupt handler. -/// -/// The function must have the signature `[unsafe] fn() [-> !]`. -/// If the `v-trap` feature is enabled, this macro generates the -/// interrupt trap handler in assembly for RISCV-32 targets. -pub fn interrupt_riscv32(args: TokenStream, input: TokenStream) -> TokenStream { - interrupt(args, input, RiscvArch::Rv32) +/// Attribute to declare an exception handler. The function must have the signature `[unsafe] fn(&[mut] riscv_rt::TrapFrame) [-> !]`. +pub fn exception(args: TokenStream, input: TokenStream) -> TokenStream { + let f = parse_macro_input!(input as ItemFn); + + // check the function arguments + if f.sig.inputs.len() > 1 { + return parse::Error::new( + f.sig.inputs.span(), + "`#[exception]` function must at have most one input argument", + ) + .to_compile_error() + .into(); + } + + if let Some(param) = f.sig.inputs.first() { + let first_param_type = match param { + FnArg::Typed(t) => *t.ty.clone(), + _ => { + return parse::Error::new(param.span(), "invalid argument") + .to_compile_error() + .into(); + } + }; + + let expected_types: Vec = vec![ + parse_quote!(&riscv_rt::TrapFrame), + parse_quote!(&mut riscv_rt::TrapFrame), + ]; + + if !expected_types.iter().any(|t| first_param_type == *t) { + return parse::Error::new( + first_param_type.span(), + "`#[exception]` function argument must be `&[mut] riscv_rt::TrapFrame`", + ) + .to_compile_error() + .into(); + } + } + + // check the function signature + let valid_signature = f.sig.constness.is_none() + && f.sig.asyncness.is_none() + && f.vis == Visibility::Inherited + && f.sig.abi.is_none() + && f.sig.generics.params.is_empty() + && f.sig.generics.where_clause.is_none() + && f.sig.variadic.is_none() + && match f.sig.output { + ReturnType::Default => true, + ReturnType::Type(_, ref ty) => matches!(**ty, Type::Never(_)), + }; + + if !valid_signature { + return parse::Error::new( + f.span(), + "`#[exception]` function must have signature `[unsafe] fn(&riscv_rt::TrapFrame) [-> !]`", + ) + .to_compile_error() + .into(); + } + + let int_path = parse_macro_input!(args as Path); + let int_ident = &int_path.segments.last().unwrap().ident; + let export_name = format!("{:#}", int_ident); + + quote!( + // Compile-time check to ensure the interrupt path implements the CoreInterruptNumber trait + const _: fn() = || { + fn assert_impl(_arg: T) {} + assert_impl(#int_path); + }; + + #[export_name = #export_name] + #f + ) + .into() } #[proc_macro_attribute] -/// Attribute to declare an interrupt handler. -/// -/// The function must have the signature `[unsafe] fn() [-> !]`. -/// If the `v-trap` feature is enabled, this macro generates the -/// interrupt trap handler in assembly for RISCV-64 targets. -pub fn interrupt_riscv64(args: TokenStream, input: TokenStream) -> TokenStream { - interrupt(args, input, RiscvArch::Rv64) +/// Attribute to declare an core interrupt handler. The function must have the signature `[unsafe] fn() [-> !]`. +/// If the `v-trap` feature is enabled, this macro generates the corresponding interrupt trap handler in assembly. +pub fn core_interrupt_riscv32(args: TokenStream, input: TokenStream) -> TokenStream { + let arch = match () { + #[cfg(feature = "v-trap")] + () => Some(RiscvArch::Rv32), + #[cfg(not(feature = "v-trap"))] + () => None, + }; + interrupt(args, input, RiscvPacItem::CoreInterrupt, arch) +} + +#[proc_macro_attribute] +/// Attribute to declare an interrupt handler. The function must have the signature `[unsafe] fn() [-> !]`. +/// If the `v-trap` feature is enabled, this macro generates the corresponding interrupt trap handler in assembly. +pub fn core_interrupt_riscv64(args: TokenStream, input: TokenStream) -> TokenStream { + let arch = match () { + #[cfg(feature = "v-trap")] + () => Some(RiscvArch::Rv64), + #[cfg(not(feature = "v-trap"))] + () => None, + }; + interrupt(args, input, RiscvPacItem::CoreInterrupt, arch) } -fn interrupt(args: TokenStream, input: TokenStream, _arch: RiscvArch) -> TokenStream { +#[proc_macro_attribute] +/// Attribute to declare an external interrupt handler. The function must have the signature `[unsafe] fn() [-> !]`. +pub fn external_interrupt(args: TokenStream, input: TokenStream) -> TokenStream { + interrupt(args, input, RiscvPacItem::ExternalInterrupt, None) +} + +fn interrupt( + args: TokenStream, + input: TokenStream, + pac_item: RiscvPacItem, + arch: Option, +) -> TokenStream { let f = parse_macro_input!(input as ItemFn); // check the function arguments @@ -603,22 +705,28 @@ fn interrupt(args: TokenStream, input: TokenStream, _arch: RiscvArch) -> TokenSt .into(); } - if !args.is_empty() { - return parse::Error::new(Span::call_site(), "This attribute accepts no arguments") - .to_compile_error() - .into(); - } + let int_path = parse_macro_input!(args as Path); + let int_ident = &int_path.segments.last().unwrap().ident; + let export_name = format!("{:#}", int_ident); - // XXX should we blacklist other attributes? - let ident = &f.sig.ident; - let export_name = format!("{:#}", ident); + let start_trap = match arch { + Some(RiscvArch::Rv32) => start_interrupt_trap(int_ident, RiscvArch::Rv32), + Some(RiscvArch::Rv64) => start_interrupt_trap(int_ident, RiscvArch::Rv64), + None => proc_macro2::TokenStream::new(), + }; - #[cfg(not(feature = "v-trap"))] - let start_trap = proc_macro2::TokenStream::new(); - #[cfg(feature = "v-trap")] - let start_trap = start_interrupt_trap(ident, _arch); + let pac_trait = match pac_item { + RiscvPacItem::ExternalInterrupt => quote!(riscv_rt::ExternalInterruptNumber), + RiscvPacItem::CoreInterrupt => quote!(riscv_rt::CoreInterruptNumber), + }; quote!( + // Compile-time check to ensure the interrupt path implements the CoreInterruptNumber trait + const _: fn() = || { + fn assert_impl(_arg: T) {} + assert_impl(#int_path); + }; + #start_trap #[export_name = #export_name] #f @@ -626,7 +734,6 @@ fn interrupt(args: TokenStream, input: TokenStream, _arch: RiscvArch) -> TokenSt .into() } -#[cfg(feature = "v-trap")] fn start_interrupt_trap(ident: &syn::Ident, arch: RiscvArch) -> proc_macro2::TokenStream { let interrupt = ident.to_string(); let width = match arch { diff --git a/riscv-rt/src/lib.rs b/riscv-rt/src/lib.rs index d147d74e..ea9a4ad0 100644 --- a/riscv-rt/src/lib.rs +++ b/riscv-rt/src/lib.rs @@ -485,13 +485,14 @@ use riscv::register::scause as xcause; #[cfg(not(feature = "s-mode"))] use riscv::register::mcause as xcause; -pub use riscv_rt_macros::{entry, pre_init}; +pub use riscv_rt_macros::{entry, exception, external_interrupt, pre_init}; -#[cfg(riscv32)] -pub use riscv_rt_macros::interrupt_riscv32 as interrupt; +pub use riscv_pac::*; +#[cfg(riscv32)] +pub use riscv_rt_macros::core_interrupt_riscv32 as core_interrupt; #[cfg(riscv64)] -pub use riscv_rt_macros::interrupt_riscv64 as interrupt; +pub use riscv_rt_macros::core_interrupt_riscv64 as core_interrupt; /// We export this static with an informative name so that if an application attempts to link /// two copies of riscv-rt together, linking will fail. We also declare a links key in From 08dcc856f0e64cebd1001285500509d5baf9e94d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rom=C3=A1n=20C=C3=A1rdenas=20Rodr=C3=ADguez?= Date: Mon, 29 Jul 2024 11:23:37 +0200 Subject: [PATCH 06/14] Apply suggestions from code review Co-authored-by: rmsyn <117854522+rmsyn@users.noreply.github.com> --- riscv-rt/src/exceptions.rs | 10 +++------- riscv-rt/src/interrupts.rs | 10 +++------- riscv/macros/src/lib.rs | 21 +++++++++------------ riscv/src/interrupt/machine.rs | 6 ++++-- 4 files changed, 19 insertions(+), 28 deletions(-) diff --git a/riscv-rt/src/exceptions.rs b/riscv-rt/src/exceptions.rs index 41d8595f..7d00ed2c 100644 --- a/riscv-rt/src/exceptions.rs +++ b/riscv-rt/src/exceptions.rs @@ -42,12 +42,8 @@ pub static __EXCEPTIONS: [Option; 16] = [ #[export_name = "_dispatch_exception"] #[inline] unsafe extern "C" fn dispatch_exception(trap_frame: &TrapFrame, code: usize) { - if code < __EXCEPTIONS.len() { - match &__EXCEPTIONS[code] { - Some(handler) => handler(trap_frame), - None => ExceptionHandler(trap_frame), - } - } else { - ExceptionHandler(trap_frame); + match __EXCEPTIONS.get(code) { + Some(Some(handler)) => handler(trap_frame), + _ => ExceptionHandler(trap_frame), } } diff --git a/riscv-rt/src/interrupts.rs b/riscv-rt/src/interrupts.rs index c2314fde..d2e1d646 100644 --- a/riscv-rt/src/interrupts.rs +++ b/riscv-rt/src/interrupts.rs @@ -28,13 +28,9 @@ pub static __CORE_INTERRUPTS: [Option; 12] = [ #[export_name = "_dispatch_core_interrupt"] #[inline] unsafe extern "C" fn dispatch_core_interrupt(code: usize) { - if code < __CORE_INTERRUPTS.len() { - match &__CORE_INTERRUPTS[code] { - Some(handler) => handler(), - None => DefaultHandler(), - } - } else { - DefaultHandler(); + match __CORE_INTERRUPTS.get(code) { + Some(Some(handler)) => handler(), + _ => DefaultHandler(), } } diff --git a/riscv/macros/src/lib.rs b/riscv/macros/src/lib.rs index 984979f7..40895809 100644 --- a/riscv/macros/src/lib.rs +++ b/riscv/macros/src/lib.rs @@ -121,20 +121,17 @@ impl PacEnumItem { }; for v in variants.iter() { let ident = v.ident.clone(); - let value = match &v.discriminant { - Some(d) => match &d.1 { - syn::Expr::Lit(expr_lit) => match &expr_lit.lit { - syn::Lit::Int(lit_int) => match lit_int.base10_parse::() { - Ok(num) => num, - Err(_) => { - panic!("All variant discriminants must be unsigned integers") - } - }, - _ => panic!("All variant discriminants must be unsigned integers"), - }, + let value = match v.discriminant.as_ref() { + Some((_, syn::Expr::Lit(expr_lit))) => match &expr_lit.lit { + syn::Lit::Int(lit_int) => { + lit_int.base10_parse::().unwrap_or_else(|_| { + panic!("All variant discriminants must be unsigned integers") + }) + } _ => panic!("All variant discriminants must be unsigned integers"), }, - _ => panic!("Variant must have a discriminant"), + None => panic!("Variant must have a discriminant"), + _ => panic!("All variant discriminants must be literal expressions"), }; if numbers.insert(value, ident).is_some() { diff --git a/riscv/src/interrupt/machine.rs b/riscv/src/interrupt/machine.rs index d88ceb46..92163a2e 100644 --- a/riscv/src/interrupt/machine.rs +++ b/riscv/src/interrupt/machine.rs @@ -192,10 +192,12 @@ where } // Restore MSTATUS.PIE, MSTATUS.MPP, and SEPC + let mut after_mstatus = mstatus::read(); if mstatus.mpie() { - mstatus::set_mpie(); + after_mstatus.set_mpie(mstatus.mpie()); } - mstatus::set_mpp(mstatus.mpp()); + after_mstatus.set_mpp(mstatus.mpp()); + mstatus::write(after_mstatus); mepc::write(mepc); r From 458e921cbf8f290ec3bfe47b777860e73a60d731 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rom=C3=A1n=20C=C3=A1rdenas=20Rodr=C3=ADguez?= Date: Mon, 29 Jul 2024 11:39:38 +0200 Subject: [PATCH 07/14] minor changes --- riscv/macros/src/lib.rs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/riscv/macros/src/lib.rs b/riscv/macros/src/lib.rs index 40895809..c68c7b26 100644 --- a/riscv/macros/src/lib.rs +++ b/riscv/macros/src/lib.rs @@ -121,7 +121,7 @@ impl PacEnumItem { }; for v in variants.iter() { let ident = v.ident.clone(); - let value = match v.discriminant.as_ref() { + let value = match v.discriminant.as_ref() { Some((_, syn::Expr::Lit(expr_lit))) => match &expr_lit.lit { syn::Lit::Int(lit_int) => { lit_int.base10_parse::().unwrap_or_else(|_| { @@ -291,13 +291,9 @@ core::arch::global_asm!(" fn DefaultHandler(); } - if code < #isr_array_name.len() { - match &#isr_array_name[code] { - Some(handler) => handler(), - None => DefaultHandler(), - } - } else { - DefaultHandler(); + match #isr_array_name.get(code) { + Some(Some(handler)) => handler(), + _ => DefaultHandler(), } } }); From decd79703542b61d61e8bd672726c515fda83e57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rom=C3=A1n=20C=C3=A1rdenas=20Rodr=C3=ADguez?= Date: Sat, 10 Aug 2024 14:43:41 +0200 Subject: [PATCH 08/14] works for custom enums --- riscv/Cargo.toml | 1 + riscv/macros/src/lib.rs | 141 +++++++++++++++++++++++++++++++++------- 2 files changed, 119 insertions(+), 23 deletions(-) diff --git a/riscv/Cargo.toml b/riscv/Cargo.toml index eb0b4cfd..366dd390 100644 --- a/riscv/Cargo.toml +++ b/riscv/Cargo.toml @@ -32,3 +32,4 @@ riscv-macros = { path = "macros", version = "0.1.0", optional = true } [dev-dependencies] trybuild = "1.0" +riscv-rt = { path = "../riscv-rt", version = "0.13.0" } diff --git a/riscv/macros/src/lib.rs b/riscv/macros/src/lib.rs index c68c7b26..63cff8d5 100644 --- a/riscv/macros/src/lib.rs +++ b/riscv/macros/src/lib.rs @@ -10,6 +10,65 @@ use syn::{ Data, DeriveInput, Ident, Token, }; +/// Struct to represent a function parameter. +struct FunctionParam { + /// Name of the parameter. + param_name: TokenStream2, + /// Data type of the parameter. + param_type: TokenStream2, +} + +/// Configuration parameters of a trap. It is useful to abstract the +/// differences between exception handlers and core interrupt handlers. +struct TrapConfig { + /// Name of the default handler (e.g., `DefaultHandler` for core interrupts). + default_handler: TokenStream2, + /// Vector describing all the function parameters of these kind of trap handlers. + handler_params: Vec, + /// Dispatch function name (e.g., `_dispatch_exception` or `_dispatch_core_interrupt`). + dispatch_fn_name: TokenStream2, + /// Name of the array that sorts all the trap handlers (e.g., `__CORE_INTERRUPTS`). + handlers_array_name: TokenStream2, +} + +impl TrapConfig { + /// Vector with all the input parameters expected when declaring extern handler functions + fn extern_signature(&self) -> Vec { + let mut res = Vec::new(); + for param in self.handler_params.iter() { + let param_name = ¶m.param_name; + let param_type = ¶m.param_type; + res.push(quote! { #param_name: #param_type }); + } + res + } + + /// Similar to [`Self::extern_signature`], but skipping the parameter names. + fn array_signature(&self) -> Vec { + let mut res = Vec::new(); + for param in self.handler_params.iter() { + res.push(param.param_type.clone()) + } + res + } + + /// Similar to [`Self::extern_signature`], but skipping the parameter data types. + fn handler_input(&self) -> Vec { + let mut res = Vec::new(); + for param in self.handler_params.iter() { + res.push(param.param_name.clone()) + } + res + } + + /// Similar to [`Self::extern_signature`], but pushing the trap `code` to the vector. + fn dispatch_fn_signature(&self) -> Vec { + let mut res = self.extern_signature(); + res.push(quote! {code: usize}); + res + } +} + /// Traits that can be implemented using the `pac_enum` macro enum PacTrait { Exception, @@ -29,6 +88,14 @@ impl PacTrait { } } + /// Returns a token stream representing an additional marker trait, if any. + fn marker_trait_name(&self) -> Option { + match self { + Self::Interrupt(interrupt_type) => Some(interrupt_type.marker_trait_name()), + _ => None, + } + } + /// Returns a token stream representing the data type used to represent the number fn num_type(&self) -> TokenStream2 { match self { @@ -48,6 +115,28 @@ impl PacTrait { Self::HartId => quote!(MAX_HART_ID_NUMBER), } } + + /// For Exception or an Interrupt enums, it returns the trap configuration details. + fn trap_config(&self) -> Option { + match self { + Self::Exception => Some(TrapConfig { + default_handler: quote! { ExceptionHandler }, + handler_params: vec![FunctionParam { + param_name: quote! { trap_frame }, + param_type: quote! { &riscv_rt::TrapFrame }, + }], + dispatch_fn_name: quote! { _dispatch_exception }, + handlers_array_name: quote! { __EXCEPTIONS }, + }), + Self::Interrupt(interrupt_type) => Some(TrapConfig { + default_handler: quote! { DefaultHandler }, + handler_params: Vec::new(), + dispatch_fn_name: interrupt_type.dispatch_fn_name(), + handlers_array_name: interrupt_type.isr_array_name(), + }), + _ => None, + } + } } impl Parse for PacTrait { @@ -165,11 +254,12 @@ impl PacEnumItem { } /// Returns a vector of token streams representing the interrupt handler functions - fn interrupt_handlers(&self) -> Vec { + fn handlers(&self, trap_config: &TrapConfig) -> Vec { + let signature = trap_config.extern_signature(); self.numbers .values() .map(|ident| { - quote! { fn #ident () } + quote! { fn #ident (#(#signature),*) } }) .collect() } @@ -177,7 +267,7 @@ impl PacEnumItem { /// Returns a sorted vector of token streams representing all the elements of the interrupt array. /// If an interrupt number is not present in the enum, the corresponding element is `None`. /// Otherwise, it is `Some()`. - fn interrupt_array(&self) -> Vec { + fn handlers_array(&self) -> Vec { let mut vectors = vec![]; for i in 0..=self.max_number { if let Some(ident) = self.numbers.get(&i) { @@ -261,46 +351,51 @@ core::arch::global_asm!(" } }); - // Interrupt traits require additional code - if let PacTrait::Interrupt(interrupt_type) = attr { - let marker_trait_name = interrupt_type.marker_trait_name(); - - let isr_array_name = interrupt_type.isr_array_name(); - let dispatch_fn_name = interrupt_type.dispatch_fn_name(); - - // Push the marker trait implementation + if let Some(marker_trait_name) = attr.marker_trait_name() { res.push(quote! { unsafe impl riscv::#marker_trait_name for #name {} }); + } - let interrupt_handlers = self.interrupt_handlers(); - let interrupt_array = self.interrupt_array(); + if let Some(trap_config) = attr.trap_config() { + let default_handler = &trap_config.default_handler; + let extern_signature = trap_config.extern_signature(); + let handler_input = trap_config.handler_input(); + let array_signature = trap_config.array_signature(); + let dispatch_fn_name = &trap_config.dispatch_fn_name; + let dispatch_fn_args = &trap_config.dispatch_fn_signature(); + let vector_table = &trap_config.handlers_array_name; + + let handlers = self.handlers(&trap_config); + let interrupt_array = self.handlers_array(); // Push the interrupt handler functions and the interrupt array res.push(quote! { extern "C" { - #(#interrupt_handlers;)* + #(#handlers;)* } + #[doc(hidden)] #[no_mangle] - pub static #isr_array_name: [Option; #max_discriminant + 1] = [ + pub static #vector_table: [Option; #max_discriminant + 1] = [ #(#interrupt_array),* ]; + #[inline] #[no_mangle] - unsafe extern "C" fn #dispatch_fn_name(code: usize) { + unsafe extern "C" fn #dispatch_fn_name(#(#dispatch_fn_args),*) { extern "C" { - fn DefaultHandler(); + fn #default_handler(#(#extern_signature),*); } - match #isr_array_name.get(code) { - Some(Some(handler)) => handler(), - _ => DefaultHandler(), + match #vector_table.get(code) { + Some(Some(handler)) => handler(#(#handler_input),*), + _ => #default_handler(#(#handler_input),*), } } }); + } - if let InterruptType::Core = interrupt_type { - res.push(self.vector_table()); - } + if let PacTrait::Interrupt(InterruptType::Core) = attr { + res.push(self.vector_table()); } res From 719382e562bfb704321728132c8aff055d407646 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rom=C3=A1n=20C=C3=A1rdenas=20Rodr=C3=ADguez?= Date: Tue, 13 Aug 2024 11:01:49 +0200 Subject: [PATCH 09/14] added trap macros with tests in riscv-rt --- riscv-pac/CHANGELOG.md | 2 +- riscv-pac/src/lib.rs | 38 +-- riscv-peripheral/CHANGELOG.md | 1 + riscv-peripheral/examples/e310x.rs | 19 +- riscv-peripheral/src/aclint.rs | 12 +- riscv-peripheral/src/aclint/mswi.rs | 2 +- riscv-peripheral/src/aclint/mtimer.rs | 2 +- riscv-peripheral/src/aclint/sswi.rs | 2 +- riscv-peripheral/src/macros.rs | 8 +- riscv-peripheral/src/plic.rs | 23 +- riscv-rt/CHANGELOG.md | 3 + riscv-rt/Cargo.toml | 8 + riscv-rt/macros/src/lib.rs | 321 ++++++++++-------- riscv-rt/src/exceptions.rs | 29 +- riscv-rt/src/interrupts.rs | 45 ++- riscv-rt/src/lib.rs | 38 ++- riscv-rt/tests/test.rs | 6 + .../ui/core_interrupt/fail_empty_macro.rs | 4 + .../ui/core_interrupt/fail_empty_macro.stderr | 7 + .../fail_impl_interrupt_number.rs | 4 + .../fail_impl_interrupt_number.stderr | 18 + .../ui/core_interrupt/fail_signatures.rs | 10 + .../ui/core_interrupt/fail_signatures.stderr | 17 + .../ui/core_interrupt/pass_core_interrupt.rs | 11 + .../tests/ui/exception/fail_empty_macro.rs | 4 + .../ui/exception/fail_empty_macro.stderr | 7 + .../exception/fail_impl_exception_number.rs | 4 + .../fail_impl_exception_number.stderr | 18 + .../tests/ui/exception/fail_signatures.rs | 10 + .../tests/ui/exception/fail_signatures.stderr | 17 + riscv-rt/tests/ui/exception/pass_exception.rs | 17 + .../ui/external_interrupt/fail_arguments.rs | 31 ++ .../external_interrupt/fail_arguments.stderr | 5 + .../ui/external_interrupt/fail_empty_macro.rs | 4 + .../fail_empty_macro.stderr | 7 + .../fail_impl_interrupt_number.rs | 4 + .../fail_impl_interrupt_number.stderr | 15 + .../ui/external_interrupt/fail_signatures.rs | 39 +++ .../external_interrupt/fail_signatures.stderr | 17 + .../pass_external_interrupt.rs | 36 ++ riscv/CHANGELOG.md | 2 +- riscv/macros/src/lib.rs | 26 +- 42 files changed, 657 insertions(+), 236 deletions(-) create mode 100644 riscv-rt/tests/test.rs create mode 100644 riscv-rt/tests/ui/core_interrupt/fail_empty_macro.rs create mode 100644 riscv-rt/tests/ui/core_interrupt/fail_empty_macro.stderr create mode 100644 riscv-rt/tests/ui/core_interrupt/fail_impl_interrupt_number.rs create mode 100644 riscv-rt/tests/ui/core_interrupt/fail_impl_interrupt_number.stderr create mode 100644 riscv-rt/tests/ui/core_interrupt/fail_signatures.rs create mode 100644 riscv-rt/tests/ui/core_interrupt/fail_signatures.stderr create mode 100644 riscv-rt/tests/ui/core_interrupt/pass_core_interrupt.rs create mode 100644 riscv-rt/tests/ui/exception/fail_empty_macro.rs create mode 100644 riscv-rt/tests/ui/exception/fail_empty_macro.stderr create mode 100644 riscv-rt/tests/ui/exception/fail_impl_exception_number.rs create mode 100644 riscv-rt/tests/ui/exception/fail_impl_exception_number.stderr create mode 100644 riscv-rt/tests/ui/exception/fail_signatures.rs create mode 100644 riscv-rt/tests/ui/exception/fail_signatures.stderr create mode 100644 riscv-rt/tests/ui/exception/pass_exception.rs create mode 100644 riscv-rt/tests/ui/external_interrupt/fail_arguments.rs create mode 100644 riscv-rt/tests/ui/external_interrupt/fail_arguments.stderr create mode 100644 riscv-rt/tests/ui/external_interrupt/fail_empty_macro.rs create mode 100644 riscv-rt/tests/ui/external_interrupt/fail_empty_macro.stderr create mode 100644 riscv-rt/tests/ui/external_interrupt/fail_impl_interrupt_number.rs create mode 100644 riscv-rt/tests/ui/external_interrupt/fail_impl_interrupt_number.stderr create mode 100644 riscv-rt/tests/ui/external_interrupt/fail_signatures.rs create mode 100644 riscv-rt/tests/ui/external_interrupt/fail_signatures.stderr create mode 100644 riscv-rt/tests/ui/external_interrupt/pass_external_interrupt.rs diff --git a/riscv-pac/CHANGELOG.md b/riscv-pac/CHANGELOG.md index 1d1813cc..969e9b32 100644 --- a/riscv-pac/CHANGELOG.md +++ b/riscv-pac/CHANGELOG.md @@ -16,7 +16,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Changed -- `InterruptNumber` trait now expects `usize` +- All traits now work with `usize` data type. ## [v0.1.1] - 2024-02-15 diff --git a/riscv-pac/src/lib.rs b/riscv-pac/src/lib.rs index 935d5200..f0929b3d 100644 --- a/riscv-pac/src/lib.rs +++ b/riscv-pac/src/lib.rs @@ -26,7 +26,6 @@ pub unsafe trait ExceptionNumber: Copy { fn number(self) -> usize; /// Tries to convert a number to a valid exception. - /// If the conversion fails, it returns an error with the number back. fn from_number(value: usize) -> Result; } @@ -51,8 +50,7 @@ pub unsafe trait InterruptNumber: Copy { /// Converts an interrupt source to its corresponding number. fn number(self) -> usize; - /// Tries to convert a number to a valid interrupt source. - /// If the conversion fails, it returns an error with the number back. + /// Tries to convert a number to a valid interrupt. fn from_number(value: usize) -> Result; } @@ -83,7 +81,7 @@ pub unsafe trait ExternalInterruptNumber: InterruptNumber {} /// 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. +/// priority numbers for a specific device. Each variant must convert to a `usize` of its priority level. /// /// # Safety /// @@ -95,20 +93,19 @@ pub unsafe trait ExternalInterruptNumber: InterruptNumber {} /// * `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; + const MAX_PRIORITY_NUMBER: usize; /// Converts a priority level to its corresponding number. - fn number(self) -> u8; + fn number(self) -> usize; /// 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; + fn from_number(value: usize) -> Result; } /// Trait for enums of HART identifiers. /// /// This trait should be implemented by a peripheral access crate (PAC) on its enum of available -/// HARTs for a specific device. Each variant must convert to a `u16` of its HART ID number. +/// HARTs for a specific device. Each variant must convert to a `usize` of its HART ID number. /// /// # Safety /// @@ -120,14 +117,13 @@ pub unsafe trait PriorityNumber: Copy { /// * `MAX_HART_ID_NUMBER` must coincide with the highest allowed HART ID number. pub unsafe trait HartIdNumber: Copy { /// Highest number assigned to a context. - const MAX_HART_ID_NUMBER: u16; + const MAX_HART_ID_NUMBER: usize; /// Converts a HART ID to its corresponding number. - fn number(self) -> u16; + fn number(self) -> usize; /// 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; + fn from_number(value: usize) -> Result; } #[cfg(test)] @@ -201,40 +197,40 @@ mod test { } unsafe impl PriorityNumber for Priority { - const MAX_PRIORITY_NUMBER: u8 = Self::P3 as u8; + const MAX_PRIORITY_NUMBER: usize = Self::P3 as usize; #[inline] - fn number(self) -> u8 { + fn number(self) -> usize { self as _ } #[inline] - fn from_number(number: u8) -> Result { + fn from_number(number: usize) -> Result { match number { 0 => Ok(Priority::P0), 1 => Ok(Priority::P1), 2 => Ok(Priority::P2), 3 => Ok(Priority::P3), - _ => Err(Error::InvalidVariant(number as _)), + _ => Err(Error::InvalidVariant(number)), } } } unsafe impl HartIdNumber for HartId { - const MAX_HART_ID_NUMBER: u16 = Self::H2 as u16; + const MAX_HART_ID_NUMBER: usize = Self::H2 as usize; #[inline] - fn number(self) -> u16 { + fn number(self) -> usize { self as _ } #[inline] - fn from_number(number: u16) -> Result { + fn from_number(number: usize) -> Result { match number { 0 => Ok(HartId::H0), 1 => Ok(HartId::H1), 2 => Ok(HartId::H2), - _ => Err(Error::InvalidVariant(number as _)), + _ => Err(Error::InvalidVariant(number)), } } } diff --git a/riscv-peripheral/CHANGELOG.md b/riscv-peripheral/CHANGELOG.md index bef9d5e3..e4703980 100644 --- a/riscv-peripheral/CHANGELOG.md +++ b/riscv-peripheral/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Changed +- Adapt to new version of `riscv-pac` traits. - `PLIC` now expects interrupt enums to implement the `riscv_pac::ExternalInterruptNumber` trait. ### Fixed diff --git a/riscv-peripheral/examples/e310x.rs b/riscv-peripheral/examples/e310x.rs index 54e69c28..60e28c61 100644 --- a/riscv-peripheral/examples/e310x.rs +++ b/riscv-peripheral/examples/e310x.rs @@ -13,18 +13,18 @@ pub enum HartId { } unsafe impl HartIdNumber for HartId { - const MAX_HART_ID_NUMBER: u16 = Self::H0 as u16; + const MAX_HART_ID_NUMBER: usize = Self::H0 as usize; #[inline] - fn number(self) -> u16 { + fn number(self) -> usize { self as _ } #[inline] - fn from_number(number: u16) -> Result { + fn from_number(number: usize) -> Result { match number { 0 => Ok(Self::H0), - _ => Err(Error::InvalidVariant(number as usize)), + _ => Err(Error::InvalidVariant(number)), } } } @@ -108,6 +108,7 @@ unsafe impl InterruptNumber for Interrupt { unsafe impl ExternalInterruptNumber for Interrupt {} #[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[repr(usize)] pub enum Priority { P0 = 0, P1 = 1, @@ -120,20 +121,20 @@ pub enum Priority { } unsafe impl PriorityNumber for Priority { - const MAX_PRIORITY_NUMBER: u8 = Self::P7 as u8; + const MAX_PRIORITY_NUMBER: usize = Self::P7 as usize; #[inline] - fn number(self) -> u8 { + fn number(self) -> usize { self as _ } #[inline] - fn from_number(number: u8) -> Result { + fn from_number(number: usize) -> Result { if number > Self::MAX_PRIORITY_NUMBER { - Err(Error::InvalidVariant(number as usize)) + Err(Error::InvalidVariant(number)) } else { // SAFETY: valid priority number - Ok(unsafe { core::mem::transmute::(number) }) + Ok(unsafe { core::mem::transmute::(number) }) } } } diff --git a/riscv-peripheral/src/aclint.rs b/riscv-peripheral/src/aclint.rs index 51d139d4..096aa4f3 100644 --- a/riscv-peripheral/src/aclint.rs +++ b/riscv-peripheral/src/aclint.rs @@ -66,7 +66,7 @@ pub(crate) mod test { use riscv_pac::result::{Error, Result}; #[derive(Clone, Copy, Debug, Eq, PartialEq)] - #[repr(u16)] + #[repr(usize)] pub(crate) enum HartId { H0 = 0, H1 = 1, @@ -74,20 +74,20 @@ pub(crate) mod test { } unsafe impl HartIdNumber for HartId { - const MAX_HART_ID_NUMBER: u16 = 2; + const MAX_HART_ID_NUMBER: usize = Self::H2 as usize; #[inline] - fn number(self) -> u16 { + fn number(self) -> usize { self as _ } #[inline] - fn from_number(number: u16) -> Result { + fn from_number(number: usize) -> Result { if number > Self::MAX_HART_ID_NUMBER { - Err(Error::InvalidVariant(number as usize)) + Err(Error::InvalidVariant(number)) } else { // SAFETY: valid context number - Ok(unsafe { core::mem::transmute::(number) }) + Ok(unsafe { core::mem::transmute::(number) }) } } } diff --git a/riscv-peripheral/src/aclint/mswi.rs b/riscv-peripheral/src/aclint/mswi.rs index 4e85121a..d24d7b07 100644 --- a/riscv-peripheral/src/aclint/mswi.rs +++ b/riscv-peripheral/src/aclint/mswi.rs @@ -33,7 +33,7 @@ impl MSWI { #[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 { MSIP::new(self.msip0.get_ptr().add(hart_id.number()) as _) } } /// Returns the `MSIP` register for the current HART. diff --git a/riscv-peripheral/src/aclint/mtimer.rs b/riscv-peripheral/src/aclint/mtimer.rs index 5e5e1c1c..707c2de9 100644 --- a/riscv-peripheral/src/aclint/mtimer.rs +++ b/riscv-peripheral/src/aclint/mtimer.rs @@ -35,7 +35,7 @@ impl MTIMER { #[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 _) } + unsafe { MTIMECMP::new(self.mtimecmp0.get_ptr().add(hart_id.number()) as _) } } /// Returns the `MTIMECMP` register for the current HART. diff --git a/riscv-peripheral/src/aclint/sswi.rs b/riscv-peripheral/src/aclint/sswi.rs index 51072d66..048bd300 100644 --- a/riscv-peripheral/src/aclint/sswi.rs +++ b/riscv-peripheral/src/aclint/sswi.rs @@ -64,7 +64,7 @@ impl SSWI { #[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 { SETSSIP::new(self.setssip0.get_ptr().add(hart_id.number()) as _) } } } diff --git a/riscv-peripheral/src/macros.rs b/riscv-peripheral/src/macros.rs index faf093f1..5ba21314 100644 --- a/riscv-peripheral/src/macros.rs +++ b/riscv-peripheral/src/macros.rs @@ -37,14 +37,14 @@ /// /// // 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 { +/// const MAX_HART_ID_NUMBER: usize = Self::H2 as usize; +/// fn number(self) -> usize { self as _ } +/// fn from_number(number: usize) -> Result { /// match number { /// 0 => Ok(HartId::H0), /// 1 => Ok(HartId::H1), /// 2 => Ok(HartId::H2), -/// _ => Err(Error::InvalidVariant(number as _)), +/// _ => Err(Error::InvalidVariant(number)), /// } /// } /// } diff --git a/riscv-peripheral/src/plic.rs b/riscv-peripheral/src/plic.rs index 6f5ee98f..596a8795 100644 --- a/riscv-peripheral/src/plic.rs +++ b/riscv-peripheral/src/plic.rs @@ -64,7 +64,7 @@ impl PLIC

{ #[inline] pub fn ctx(hart_id: H) -> CTX

{ // SAFETY: valid context number - unsafe { CTX::new(hart_id.number()) } + unsafe { CTX::new(hart_id.number() as _) } } /// Returns the PLIC HART context for the current HART. @@ -149,7 +149,6 @@ pub(crate) mod test { use riscv_pac::{ExternalInterruptNumber, HartIdNumber, InterruptNumber, PriorityNumber}; #[derive(Clone, Copy, Debug, Eq, PartialEq)] - #[repr(usize)] pub(crate) enum Interrupt { I1 = 1, I2 = 2, @@ -158,7 +157,6 @@ pub(crate) mod test { } #[derive(Clone, Copy, Debug, Eq, PartialEq)] - #[repr(u8)] pub(crate) enum Priority { P0 = 0, P1 = 1, @@ -167,7 +165,6 @@ pub(crate) mod test { } #[derive(Clone, Copy, Debug, Eq, PartialEq)] - #[repr(u16)] pub(crate) enum Context { C0 = 0, C1 = 1, @@ -175,7 +172,7 @@ pub(crate) mod test { } unsafe impl InterruptNumber for Interrupt { - const MAX_INTERRUPT_NUMBER: usize = 4; + const MAX_INTERRUPT_NUMBER: usize = Self::I4 as usize; #[inline] fn number(self) -> usize { @@ -197,40 +194,40 @@ pub(crate) mod test { unsafe impl ExternalInterruptNumber for Interrupt {} unsafe impl PriorityNumber for Priority { - const MAX_PRIORITY_NUMBER: u8 = 3; + const MAX_PRIORITY_NUMBER: usize = Self::P3 as usize; #[inline] - fn number(self) -> u8 { + fn number(self) -> usize { self as _ } #[inline] - fn from_number(number: u8) -> Result { + fn from_number(number: usize) -> Result { match number { 0 => Ok(Priority::P0), 1 => Ok(Priority::P1), 2 => Ok(Priority::P2), 3 => Ok(Priority::P3), - _ => Err(Error::InvalidVariant(number as usize)), + _ => Err(Error::InvalidVariant(number)), } } } unsafe impl HartIdNumber for Context { - const MAX_HART_ID_NUMBER: u16 = 2; + const MAX_HART_ID_NUMBER: usize = Self::C2 as usize; #[inline] - fn number(self) -> u16 { + fn number(self) -> usize { self as _ } #[inline] - fn from_number(number: u16) -> Result { + fn from_number(number: usize) -> Result { match number { 0 => Ok(Context::C0), 1 => Ok(Context::C1), 2 => Ok(Context::C2), - _ => Err(Error::InvalidVariant(number as usize)), + _ => Err(Error::InvalidVariant(number)), } } } diff --git a/riscv-rt/CHANGELOG.md b/riscv-rt/CHANGELOG.md index a843ec59..659ded45 100644 --- a/riscv-rt/CHANGELOG.md +++ b/riscv-rt/CHANGELOG.md @@ -9,11 +9,13 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Added +- Add integration tests to check that macros work as expected. - Add `no-exceptions` feature to opt-out the default implementation of `_dispatch_exception` - Add `no-interrupts` feature to opt-out the default implementation of `_dispatch_core_interrupt` - Add `pre_init_trap` to detect early errors during the boot process. - Add `v-trap` feature to enable interrupt handling in vectored mode. - Add `core_interrupt` proc macro to help defining core interrupt handlers. + If `v-trap` feature is enabled, this macro also generates its corresponding trap. - Add `external_interrupt` proc macro to help defining external interrupt handlers. - Add `exception` proc macro to help defining exception handlers. If `v-trap` feature is enabled, this macro also generates its corresponding trap. @@ -22,6 +24,7 @@ work with passed arguments. ### Changed +- Use `cfg_attr` in `start_trap_rust` to allow compilation in non-riscv targets. - Moved all the assembly code to `asm.rs` - Use `weak` symbols for functions such as `_mp_hook` or `_start_trap` - `abort` is now `weak`, so it is possible to link third-party libraries including this symbol. diff --git a/riscv-rt/Cargo.toml b/riscv-rt/Cargo.toml index f6000653..b861372e 100644 --- a/riscv-rt/Cargo.toml +++ b/riscv-rt/Cargo.toml @@ -12,12 +12,20 @@ license = "ISC" edition = "2021" links = "riscv-rt" # Prevent multiple versions of riscv-rt being linked +[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", +] + [dependencies] riscv = { path = "../riscv", version = "0.12.0" } riscv-pac = { path = "../riscv-pac", version = "0.2.0" } riscv-rt-macros = { path = "macros", version = "0.2.1" } [dev-dependencies] +trybuild = "1.0" panic-halt = "0.2.0" [features] diff --git a/riscv-rt/macros/src/lib.rs b/riscv-rt/macros/src/lib.rs index 0bc88b18..3eb482ed 100644 --- a/riscv-rt/macros/src/lib.rs +++ b/riscv-rt/macros/src/lib.rs @@ -1,19 +1,13 @@ #![deny(warnings)] -extern crate proc_macro; -#[macro_use] -extern crate quote; -extern crate core; -extern crate proc_macro2; -#[macro_use] -extern crate syn; - -use proc_macro2::Span; +use proc_macro2::{Span, TokenStream as TokenStream2}; +use quote::quote; use syn::{ parse::{self, Parse}, + parse_macro_input, parse_quote, punctuated::Punctuated, spanned::Spanned, - FnArg, ItemFn, LitInt, LitStr, PatType, Path, ReturnType, Type, Visibility, + FnArg, ItemFn, LitInt, LitStr, PatType, Path, ReturnType, Token, Type, Visibility, }; use proc_macro::TokenStream; @@ -363,10 +357,27 @@ enum RiscvArch { Rv64, } -#[derive(Clone, Copy, Debug)] -enum RiscvPacItem { - ExternalInterrupt, - CoreInterrupt, +impl RiscvArch { + fn width(&self) -> usize { + match self { + Self::Rv32 => 4, + Self::Rv64 => 8, + } + } + + fn store(&self) -> &str { + match self { + Self::Rv32 => "sw", + Self::Rv64 => "sd", + } + } + + fn load(&self) -> &str { + match self { + Self::Rv32 => "lw", + Self::Rv64 => "ld", + } + } } /// Size of the trap frame (in number of registers) @@ -402,11 +413,8 @@ const TRAP_FRAME: [&str; TRAP_SIZE] = [ /// frame storage in two parts: the first part saves space in the stack and stores only the `a0` register, /// while the second part stores the remaining registers. fn store_trap bool>(arch: RiscvArch, mut filter: T) -> String { - let (width, store) = match arch { - RiscvArch::Rv32 => (4, "sw"), - RiscvArch::Rv64 => (8, "sd"), - }; - + let width = arch.width(); + let store = arch.store(); TRAP_FRAME .iter() .enumerate() @@ -419,10 +427,8 @@ fn store_trap bool>(arch: RiscvArch, mut filter: T) -> String /// Generate the assembly instructions to load the trap frame. /// The `arch` parameter is used to determine the width of the registers. fn load_trap(arch: RiscvArch) -> String { - let (width, load) = match arch { - RiscvArch::Rv32 => (4, "lw"), - RiscvArch::Rv64 => (8, "ld"), - }; + let width = arch.width(); + let load = arch.load(); TRAP_FRAME .iter() .enumerate() @@ -457,10 +463,7 @@ pub fn weak_start_trap_riscv64(_input: TokenStream) -> TokenStream { /// The `arch` parameter is used to determine the width of the registers. /// The macro also ensures that the trap frame size is 16-byte aligned. fn weak_start_trap(arch: RiscvArch) -> TokenStream { - let width = match arch { - RiscvArch::Rv32 => 4, - RiscvArch::Rv64 => 8, - }; + let width = arch.width(); // ensure we do not break that sp is 16-byte aligned if (TRAP_SIZE * width) % 16 != 0 { return parse::Error::new(Span::call_site(), "Trap frame size must be 16-byte aligned") @@ -515,10 +518,7 @@ pub fn vectored_interrupt_trap_riscv64(_input: TokenStream) -> TokenStream { /// jumps to the interrupt handler. The '_continue_interrupt_trap' function stores the trap frame /// partially (all registers except a0), jumps to the interrupt handler, and restores the trap frame. fn vectored_interrupt_trap(arch: RiscvArch) -> TokenStream { - let width = match arch { - RiscvArch::Rv32 => 4, - RiscvArch::Rv64 => 8, - }; + let width = arch.width(); let store_start = store_trap(arch, |reg| reg == "a0"); let store_continue = store_trap(arch, |reg| reg != "a0"); let load = load_trap(arch); @@ -554,88 +554,110 @@ _continue_interrupt_trap: instructions.parse().unwrap() } -#[proc_macro_attribute] -/// Attribute to declare an exception handler. The function must have the signature `[unsafe] fn(&[mut] riscv_rt::TrapFrame) [-> !]`. -pub fn exception(args: TokenStream, input: TokenStream) -> TokenStream { - let f = parse_macro_input!(input as ItemFn); +#[derive(Clone, Copy, Debug)] +enum RiscvPacItem { + Exception, + ExternalInterrupt, + CoreInterrupt, +} - // check the function arguments - if f.sig.inputs.len() > 1 { - return parse::Error::new( - f.sig.inputs.span(), - "`#[exception]` function must at have most one input argument", - ) - .to_compile_error() - .into(); +impl RiscvPacItem { + fn macro_id(&self) -> &str { + match self { + Self::Exception => "exception", + Self::ExternalInterrupt => "external_interrupt", + Self::CoreInterrupt => "core_interrupt", + } } - if let Some(param) = f.sig.inputs.first() { - let first_param_type = match param { - FnArg::Typed(t) => *t.ty.clone(), - _ => { - return parse::Error::new(param.span(), "invalid argument") - .to_compile_error() - .into(); - } - }; - - let expected_types: Vec = vec![ - parse_quote!(&riscv_rt::TrapFrame), - parse_quote!(&mut riscv_rt::TrapFrame), - ]; - - if !expected_types.iter().any(|t| first_param_type == *t) { - return parse::Error::new( - first_param_type.span(), - "`#[exception]` function argument must be `&[mut] riscv_rt::TrapFrame`", - ) - .to_compile_error() - .into(); + fn valid_signature(&self) -> &str { + match self { + Self::Exception => "`[unsafe] fn([&[mut] riscv_rt::TrapFrame]) [-> !]`", + _ => "`[unsafe] fn() [-> !]`", } } - // check the function signature - let valid_signature = f.sig.constness.is_none() - && f.sig.asyncness.is_none() - && f.vis == Visibility::Inherited - && f.sig.abi.is_none() - && f.sig.generics.params.is_empty() - && f.sig.generics.where_clause.is_none() - && f.sig.variadic.is_none() - && match f.sig.output { - ReturnType::Default => true, - ReturnType::Type(_, ref ty) => matches!(**ty, Type::Never(_)), + fn check_signature(&self, f: &ItemFn) -> bool { + let valid_args = match self { + Self::Exception => { + if f.sig.inputs.len() > 1 { + return false; + } + match f.sig.inputs.first() { + Some(FnArg::Typed(t)) => { + let first_param_type = *t.ty.clone(); + let expected_types: Vec = vec![ + parse_quote!(&riscv_rt::TrapFrame), + parse_quote!(&mut riscv_rt::TrapFrame), + ]; + expected_types.iter().any(|t| first_param_type == *t) + } + Some(_) => false, + None => true, + } + } + _ => f.sig.inputs.is_empty(), }; - if !valid_signature { - return parse::Error::new( - f.span(), - "`#[exception]` function must have signature `[unsafe] fn(&riscv_rt::TrapFrame) [-> !]`", - ) - .to_compile_error() - .into(); + valid_args + && f.sig.constness.is_none() + && f.sig.asyncness.is_none() + && f.vis == Visibility::Inherited + && f.sig.abi.is_none() + && f.sig.generics.params.is_empty() + && f.sig.generics.where_clause.is_none() + && f.sig.variadic.is_none() + && match f.sig.output { + ReturnType::Default => true, + ReturnType::Type(_, ref ty) => matches!(**ty, Type::Never(_)), + } } - let int_path = parse_macro_input!(args as Path); - let int_ident = &int_path.segments.last().unwrap().ident; - let export_name = format!("{:#}", int_ident); - - quote!( - // Compile-time check to ensure the interrupt path implements the CoreInterruptNumber trait - const _: fn() = || { - fn assert_impl(_arg: T) {} - assert_impl(#int_path); - }; + fn impl_trait(&self) -> TokenStream2 { + match self { + Self::Exception => quote! { riscv_rt::ExceptionNumber }, + Self::ExternalInterrupt => quote! { riscv_rt::ExternalInterruptNumber }, + Self::CoreInterrupt => quote! { riscv_rt::CoreInterruptNumber }, + } + } +} - #[export_name = #export_name] - #f - ) - .into() +#[proc_macro_attribute] +/// Attribute to declare an exception handler. +/// +/// The function must have the signature `[unsafe] fn([&[mut] riscv_rt::TrapFrame]) [-> !]`. +/// +/// The argument of the macro must be a path to a variant of an enum that implements the `riscv_rt::ExceptionNumber` trait. +/// +/// # Example +/// +/// ``` ignore,no_run +/// #[riscv_rt::exception(riscv::interrupt::Exception::LoadMisaligned)] +/// fn load_misaligned(trap_frame: &mut riscv_rt::TrapFrame) -> ! { +/// loop{}; +/// } +/// ``` +pub fn exception(args: TokenStream, input: TokenStream) -> TokenStream { + trap(args, input, RiscvPacItem::Exception, None) } #[proc_macro_attribute] -/// Attribute to declare an core interrupt handler. The function must have the signature `[unsafe] fn() [-> !]`. +/// Attribute to declare a core interrupt handler. +/// +/// The function must have the signature `[unsafe] fn() [-> !]`. +/// +/// The argument of the macro must be a path to a variant of an enum that implements the `riscv_rt::CoreInterruptNumber` trait. +/// /// If the `v-trap` feature is enabled, this macro generates the corresponding interrupt trap handler in assembly. +/// +/// # Example +/// +/// ``` ignore,no_run +/// #[riscv_rt::core_interrupt(riscv::interrupt::Interrupt::SupervisorSoft)] +/// fn supervisor_soft() -> ! { +/// loop{}; +/// } +/// ``` pub fn core_interrupt_riscv32(args: TokenStream, input: TokenStream) -> TokenStream { let arch = match () { #[cfg(feature = "v-trap")] @@ -643,12 +665,26 @@ pub fn core_interrupt_riscv32(args: TokenStream, input: TokenStream) -> TokenStr #[cfg(not(feature = "v-trap"))] () => None, }; - interrupt(args, input, RiscvPacItem::CoreInterrupt, arch) + trap(args, input, RiscvPacItem::CoreInterrupt, arch) } #[proc_macro_attribute] -/// Attribute to declare an interrupt handler. The function must have the signature `[unsafe] fn() [-> !]`. +/// Attribute to declare a core interrupt handler. +/// +/// The function must have the signature `[unsafe] fn() [-> !]`. +/// +/// The argument of the macro must be a path to a variant of an enum that implements the `riscv_rt::CoreInterruptNumber` trait. +/// /// If the `v-trap` feature is enabled, this macro generates the corresponding interrupt trap handler in assembly. +/// +/// # Example +/// +/// ``` ignore,no_run +/// #[riscv_rt::core_interrupt(riscv::interrupt::Interrupt::SupervisorSoft)] +/// fn supervisor_soft() -> ! { +/// loop{}; +/// } +/// ``` pub fn core_interrupt_riscv64(args: TokenStream, input: TokenStream) -> TokenStream { let arch = match () { #[cfg(feature = "v-trap")] @@ -656,16 +692,29 @@ pub fn core_interrupt_riscv64(args: TokenStream, input: TokenStream) -> TokenStr #[cfg(not(feature = "v-trap"))] () => None, }; - interrupt(args, input, RiscvPacItem::CoreInterrupt, arch) + trap(args, input, RiscvPacItem::CoreInterrupt, arch) } #[proc_macro_attribute] -/// Attribute to declare an external interrupt handler. The function must have the signature `[unsafe] fn() [-> !]`. +/// Attribute to declare an external interrupt handler. +/// +/// The function must have the signature `[unsafe] fn() [-> !]`. +/// +/// The argument of the macro must be a path to a variant of an enum that implements the `riscv_rt::ExternalInterruptNumber` trait. +/// +/// # Example +/// +/// ``` ignore,no_run +/// #[riscv_rt::external_interrupt(e310x::interrupt::Interrupt::GPIO0)] +/// fn gpio0() -> ! { +/// loop{}; +/// } +/// ``` pub fn external_interrupt(args: TokenStream, input: TokenStream) -> TokenStream { - interrupt(args, input, RiscvPacItem::ExternalInterrupt, None) + trap(args, input, RiscvPacItem::ExternalInterrupt, None) } -fn interrupt( +fn trap( args: TokenStream, input: TokenStream, pac_item: RiscvPacItem, @@ -673,36 +722,25 @@ fn interrupt( ) -> TokenStream { let f = parse_macro_input!(input as ItemFn); - // check the function arguments - if !f.sig.inputs.is_empty() { - return parse::Error::new( - f.sig.inputs.first().unwrap().span(), - "`#[interrupt]` function should not have arguments", - ) - .to_compile_error() - .into(); + if !pac_item.check_signature(&f) { + let msg = format!( + "`#[{}]` function must have signature {}", + pac_item.macro_id(), + pac_item.valid_signature() + ); + return parse::Error::new(f.sig.span(), msg) + .to_compile_error() + .into(); } - - // check the function signature - let valid_signature = f.sig.constness.is_none() - && f.sig.asyncness.is_none() - && f.vis == Visibility::Inherited - && f.sig.abi.is_none() - && f.sig.generics.params.is_empty() - && f.sig.generics.where_clause.is_none() - && f.sig.variadic.is_none() - && match f.sig.output { - ReturnType::Default => true, - ReturnType::Type(_, ref ty) => matches!(**ty, Type::Never(_)), - }; - - if !valid_signature { - return parse::Error::new( - f.span(), - "`#[interrupt]` function must have signature `[unsafe] fn() [-> !]`", - ) - .to_compile_error() - .into(); + if args.is_empty() { + let msg = format!( + "`#[{}]` attribute expects a path to a variant of an enum that implements the {} trait.", + pac_item.macro_id(), + pac_item.impl_trait() + ); + return parse::Error::new(Span::call_site(), msg) + .to_compile_error() + .into(); } let int_path = parse_macro_input!(args as Path); @@ -710,24 +748,22 @@ fn interrupt( let export_name = format!("{:#}", int_ident); let start_trap = match arch { - Some(RiscvArch::Rv32) => start_interrupt_trap(int_ident, RiscvArch::Rv32), - Some(RiscvArch::Rv64) => start_interrupt_trap(int_ident, RiscvArch::Rv64), + Some(arch) => start_interrupt_trap(int_ident, arch), None => proc_macro2::TokenStream::new(), }; - let pac_trait = match pac_item { - RiscvPacItem::ExternalInterrupt => quote!(riscv_rt::ExternalInterruptNumber), - RiscvPacItem::CoreInterrupt => quote!(riscv_rt::CoreInterruptNumber), - }; + let pac_trait = pac_item.impl_trait(); quote!( - // Compile-time check to ensure the interrupt path implements the CoreInterruptNumber trait + // Compile-time check to ensure the trap path implements the trap trait const _: fn() = || { fn assert_impl(_arg: T) {} assert_impl(#int_path); }; + #[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))] #start_trap + #[export_name = #export_name] #f ) @@ -736,10 +772,7 @@ fn interrupt( fn start_interrupt_trap(ident: &syn::Ident, arch: RiscvArch) -> proc_macro2::TokenStream { let interrupt = ident.to_string(); - let width = match arch { - RiscvArch::Rv32 => 4, - RiscvArch::Rv64 => 8, - }; + let width = arch.width(); let store = store_trap(arch, |r| r == "a0"); let instructions = format!( diff --git a/riscv-rt/src/exceptions.rs b/riscv-rt/src/exceptions.rs index 7d00ed2c..2fd80dcb 100644 --- a/riscv-rt/src/exceptions.rs +++ b/riscv-rt/src/exceptions.rs @@ -1,3 +1,16 @@ +//! Exception handling for targets that comply with the RISC-V exception handling standard. +//! +//! Exception dispatching is performed by the [`_dispatch_exception`] function. +//! This function is called by the [crate::start_trap_rust] whenever an exception is triggered. +//! This approach relies on the [`__EXCEPTIONS`] array, which sorts all the exception handlers +//! depending on their corresponding exception source code. +//! +//! # Note +//! +//! If your target has custom exception sources, the target PAC might provide equivalent +//! code to adapt for the target needs. In this case, you may need to opt out this module. +//! To do so, activate the `no-exceptions` feature of the `riscv-rt` crate. + use crate::TrapFrame; extern "C" { @@ -15,10 +28,9 @@ extern "C" { fn InstructionPageFault(trap_frame: &TrapFrame); fn LoadPageFault(trap_frame: &TrapFrame); fn StorePageFault(trap_frame: &TrapFrame); - fn ExceptionHandler(trap_frame: &TrapFrame); } -#[doc(hidden)] +/// Array with all the exception handlers sorted according to their exception source code. #[no_mangle] pub static __EXCEPTIONS: [Option; 16] = [ Some(InstructionMisaligned), @@ -39,9 +51,18 @@ pub static __EXCEPTIONS: [Option; 16] = [ Some(StorePageFault), ]; -#[export_name = "_dispatch_exception"] +/// It calls the corresponding exception handler depending on the exception source code. +/// +/// # Safety +/// +/// This function must be called only from the [`crate::start_trap_rust`] function. +/// Do **NOT** call this function directly. #[inline] -unsafe extern "C" fn dispatch_exception(trap_frame: &TrapFrame, code: usize) { +#[no_mangle] +pub unsafe extern "C" fn _dispatch_exception(trap_frame: &TrapFrame, code: usize) { + extern "C" { + fn ExceptionHandler(trap_frame: &TrapFrame); + } match __EXCEPTIONS.get(code) { Some(Some(handler)) => handler(trap_frame), _ => ExceptionHandler(trap_frame), diff --git a/riscv-rt/src/interrupts.rs b/riscv-rt/src/interrupts.rs index d2e1d646..6fe900e5 100644 --- a/riscv-rt/src/interrupts.rs +++ b/riscv-rt/src/interrupts.rs @@ -1,3 +1,20 @@ +//! Interrupt handling for targets that comply with the RISC-V interrupt handling standard. +//! +//! In direct mode (i.e., `v-trap` feature disabled), interrupt dispatching is performed by the +//! [`_dispatch_core_interrupt`] function. This function is called by the [crate::start_trap_rust] +//! whenever an interrupt is triggered. This approach relies on the [`__CORE_INTERRUPTS`] array, +//! which sorts all the interrupt handlers depending on their corresponding interrupt source code. +//! +//! In vectored mode (i.e., `v-trap` feature enabled), interrupt dispatching is handled by hardware. +//! To support this mode, we provide inline assembly code that defines the interrupt vector table. +//! +//! # Note +//! +//! If your target has custom core interrupt sources, the target PAC might provide equivalent +//! code to adapt for the target needs. In this case, you may need to opt out this module. +//! To do so, activate the `no-interrupts` feature of the `riscv-rt` crate. + +#[cfg(not(feature = "v-trap"))] extern "C" { fn SupervisorSoft(); fn MachineSoft(); @@ -5,10 +22,14 @@ extern "C" { fn MachineTimer(); fn SupervisorExternal(); fn MachineExternal(); - fn DefaultHandler(); } -#[doc(hidden)] +/// Array with all the core interrupt handlers sorted according to their interrupt source code. +/// +/// # Note +/// +/// This array is necessary only in direct mode (i.e., `v-trap` feature disabled). +#[cfg(not(feature = "v-trap"))] #[no_mangle] pub static __CORE_INTERRUPTS: [Option; 12] = [ None, @@ -25,15 +46,31 @@ pub static __CORE_INTERRUPTS: [Option; 12] = [ Some(MachineExternal), ]; -#[export_name = "_dispatch_core_interrupt"] +/// It calls the corresponding interrupt handler depending on the interrupt source code. +/// +/// # Note +/// +/// This function is only required in direct mode (i.e., `v-trap` feature disabled). +/// In vectored mode, interrupt handler dispatching is performed directly by hardware. +/// +/// # Safety +/// +/// This function must be called only from the [`crate::start_trap_rust`] function. +/// Do **NOT** call this function directly. +#[cfg(not(feature = "v-trap"))] #[inline] -unsafe extern "C" fn dispatch_core_interrupt(code: usize) { +#[no_mangle] +pub unsafe extern "C" fn _dispatch_core_interrupt(code: usize) { + extern "C" { + fn DefaultHandler(); + } match __CORE_INTERRUPTS.get(code) { Some(Some(handler)) => handler(), _ => DefaultHandler(), } } +// In vectored mode, we also must provide a vector table #[cfg(all(riscv, feature = "v-trap"))] core::arch::global_asm!( r#" .section .trap, "ax" diff --git a/riscv-rt/src/lib.rs b/riscv-rt/src/lib.rs index ea9a4ad0..7b74b456 100644 --- a/riscv-rt/src/lib.rs +++ b/riscv-rt/src/lib.rs @@ -474,10 +474,10 @@ mod asm; #[cfg(not(feature = "no-exceptions"))] -mod exceptions; +pub mod exceptions; #[cfg(not(feature = "no-interrupts"))] -mod interrupts; +pub mod interrupts; #[cfg(feature = "s-mode")] use riscv::register::scause as xcause; @@ -493,6 +493,8 @@ pub use riscv_pac::*; pub use riscv_rt_macros::core_interrupt_riscv32 as core_interrupt; #[cfg(riscv64)] pub use riscv_rt_macros::core_interrupt_riscv64 as core_interrupt; +#[cfg(not(riscv))] +pub use riscv_rt_macros::core_interrupt_riscv64 as core_interrupt; // just for docs, tests, etc. /// We export this static with an informative name so that if an application attempts to link /// two copies of riscv-rt together, linking will fail. We also declare a links key in @@ -528,23 +530,49 @@ pub struct TrapFrame { /// Trap entry point rust (_start_trap_rust) /// /// `scause`/`mcause` is read to determine the cause of the trap. XLEN-1 bit indicates -/// if it's an interrupt or an exception. The result is examined and ExceptionHandler -/// or one of the core interrupt handlers is called. +/// if it's an interrupt or an exception. The result is examined and one of the +/// exception handlers or one of the core interrupt handlers is called. +/// +/// # Note +/// +/// Exception dispatching is performed by an extern `_dispatch_exception` function. +/// Targets that comply with the RISC-V standard can use the implementation provided +/// by this crate in the [`exceptions`] module. Targets with special exception sources +/// may provide their custom implementation of the `_dispatch_exception` function. You may +/// also need to enable the `no-exceptions` feature to op-out the default implementation. +/// +/// In direct mode (i.e., `v-trap` feature disabled), interrupt dispatching is performed +/// by an extern `_dispatch_core_interrupt` function. Targets that comply with the RISC-V +/// standard can use the implementation provided by this crate in the [`interrupts`] module. +/// Targets with special interrupt sources may provide their custom implementation of the +/// `_dispatch_core_interrupt` function. You may also need to enable the `no-interrupts` +/// feature to op-out the default implementation. +/// +/// In vectored mode (i.e., `v-trap` feature enabled), interrupt dispatching is performed +/// directly by hardware, and thus this function should **not** be triggered due to an +/// interrupt. If this abnormal situation happens, this function will directly call the +/// `DefaultHandler` function. /// /// # Safety /// /// This function must be called only from assembly `_start_trap` function. /// Do **NOT** call this function directly. -#[link_section = ".trap.rust"] +#[cfg_attr(riscv, link_section = ".trap.rust")] #[export_name = "_start_trap_rust"] pub unsafe extern "C" fn start_trap_rust(trap_frame: *const TrapFrame) { extern "C" { + #[cfg(not(feature = "v-trap"))] fn _dispatch_core_interrupt(code: usize); + #[cfg(feature = "v-trap")] + fn DefaultHandler(); fn _dispatch_exception(trap_frame: &TrapFrame, code: usize); } match xcause::read().cause() { + #[cfg(not(feature = "v-trap"))] xcause::Trap::Interrupt(code) => _dispatch_core_interrupt(code), + #[cfg(feature = "v-trap")] + xcause::Trap::Interrupt(_) => DefaultHandler(), xcause::Trap::Exception(code) => _dispatch_exception(&*trap_frame, code), } } diff --git a/riscv-rt/tests/test.rs b/riscv-rt/tests/test.rs new file mode 100644 index 00000000..7c57e60a --- /dev/null +++ b/riscv-rt/tests/test.rs @@ -0,0 +1,6 @@ +#[test] +fn ui() { + let t = trybuild::TestCases::new(); + t.compile_fail("tests/ui/*/fail_*.rs"); + t.pass("tests/ui/*/pass_*.rs"); +} diff --git a/riscv-rt/tests/ui/core_interrupt/fail_empty_macro.rs b/riscv-rt/tests/ui/core_interrupt/fail_empty_macro.rs new file mode 100644 index 00000000..0cec9d6c --- /dev/null +++ b/riscv-rt/tests/ui/core_interrupt/fail_empty_macro.rs @@ -0,0 +1,4 @@ +#[riscv_rt::core_interrupt] +fn my_interrupt() {} + +fn main() {} diff --git a/riscv-rt/tests/ui/core_interrupt/fail_empty_macro.stderr b/riscv-rt/tests/ui/core_interrupt/fail_empty_macro.stderr new file mode 100644 index 00000000..1f3d12f4 --- /dev/null +++ b/riscv-rt/tests/ui/core_interrupt/fail_empty_macro.stderr @@ -0,0 +1,7 @@ +error: `#[core_interrupt]` attribute expects a path to a variant of an enum that implements the riscv_rt :: CoreInterruptNumber trait. + --> tests/ui/core_interrupt/fail_empty_macro.rs:1:1 + | +1 | #[riscv_rt::core_interrupt] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in the attribute macro `riscv_rt::core_interrupt` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/riscv-rt/tests/ui/core_interrupt/fail_impl_interrupt_number.rs b/riscv-rt/tests/ui/core_interrupt/fail_impl_interrupt_number.rs new file mode 100644 index 00000000..752f62d7 --- /dev/null +++ b/riscv-rt/tests/ui/core_interrupt/fail_impl_interrupt_number.rs @@ -0,0 +1,4 @@ +#[riscv_rt::core_interrupt(riscv::interrupt::Exception::LoadMisaligned)] +fn my_interrupt() {} + +fn main() {} diff --git a/riscv-rt/tests/ui/core_interrupt/fail_impl_interrupt_number.stderr b/riscv-rt/tests/ui/core_interrupt/fail_impl_interrupt_number.stderr new file mode 100644 index 00000000..0febf6cc --- /dev/null +++ b/riscv-rt/tests/ui/core_interrupt/fail_impl_interrupt_number.stderr @@ -0,0 +1,18 @@ +error[E0277]: the trait bound `riscv::interrupt::Exception: CoreInterruptNumber` is not satisfied + --> tests/ui/core_interrupt/fail_impl_interrupt_number.rs:1:28 + | +1 | #[riscv_rt::core_interrupt(riscv::interrupt::Exception::LoadMisaligned)] + | ---------------------------^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^-- + | | | + | | the trait `CoreInterruptNumber` is not implemented for `riscv::interrupt::Exception` + | required by a bound introduced by this call + | + = help: the following other types implement trait `CoreInterruptNumber`: + riscv::interrupt::Interrupt + riscv::interrupt::supervisor::Interrupt +note: required by a bound in `assert_impl` + --> tests/ui/core_interrupt/fail_impl_interrupt_number.rs:1:1 + | +1 | #[riscv_rt::core_interrupt(riscv::interrupt::Exception::LoadMisaligned)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `assert_impl` + = note: this error originates in the attribute macro `riscv_rt::core_interrupt` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/riscv-rt/tests/ui/core_interrupt/fail_signatures.rs b/riscv-rt/tests/ui/core_interrupt/fail_signatures.rs new file mode 100644 index 00000000..0beab5b9 --- /dev/null +++ b/riscv-rt/tests/ui/core_interrupt/fail_signatures.rs @@ -0,0 +1,10 @@ +#[riscv_rt::core_interrupt(riscv::interrupt::Interrupt::SupervisorSoft)] +fn my_interrupt(code: usize) {} + +#[riscv_rt::core_interrupt(riscv::interrupt::Interrupt::SupervisorTimer)] +fn my_other_interrupt() -> usize {} + +#[riscv_rt::core_interrupt(riscv::interrupt::Interrupt::SupervisorExternal)] +async fn my_async_interrupt() {} + +fn main() {} diff --git a/riscv-rt/tests/ui/core_interrupt/fail_signatures.stderr b/riscv-rt/tests/ui/core_interrupt/fail_signatures.stderr new file mode 100644 index 00000000..0b3c882f --- /dev/null +++ b/riscv-rt/tests/ui/core_interrupt/fail_signatures.stderr @@ -0,0 +1,17 @@ +error: `#[core_interrupt]` function must have signature `[unsafe] fn() [-> !]` + --> tests/ui/core_interrupt/fail_signatures.rs:2:1 + | +2 | fn my_interrupt(code: usize) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: `#[core_interrupt]` function must have signature `[unsafe] fn() [-> !]` + --> tests/ui/core_interrupt/fail_signatures.rs:5:1 + | +5 | fn my_other_interrupt() -> usize {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: `#[core_interrupt]` function must have signature `[unsafe] fn() [-> !]` + --> tests/ui/core_interrupt/fail_signatures.rs:8:1 + | +8 | async fn my_async_interrupt() {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/riscv-rt/tests/ui/core_interrupt/pass_core_interrupt.rs b/riscv-rt/tests/ui/core_interrupt/pass_core_interrupt.rs new file mode 100644 index 00000000..1ed3a404 --- /dev/null +++ b/riscv-rt/tests/ui/core_interrupt/pass_core_interrupt.rs @@ -0,0 +1,11 @@ +use riscv::interrupt::Interrupt::*; + +#[riscv_rt::core_interrupt(SupervisorSoft)] +fn simple_interrupt() {} + +#[riscv_rt::core_interrupt(SupervisorTimer)] +unsafe fn no_return_interrupt() -> ! { + loop {} +} + +fn main() {} diff --git a/riscv-rt/tests/ui/exception/fail_empty_macro.rs b/riscv-rt/tests/ui/exception/fail_empty_macro.rs new file mode 100644 index 00000000..48b8af93 --- /dev/null +++ b/riscv-rt/tests/ui/exception/fail_empty_macro.rs @@ -0,0 +1,4 @@ +#[riscv_rt::exception] +fn my_exception() {} + +fn main() {} diff --git a/riscv-rt/tests/ui/exception/fail_empty_macro.stderr b/riscv-rt/tests/ui/exception/fail_empty_macro.stderr new file mode 100644 index 00000000..7bf44695 --- /dev/null +++ b/riscv-rt/tests/ui/exception/fail_empty_macro.stderr @@ -0,0 +1,7 @@ +error: `#[exception]` attribute expects a path to a variant of an enum that implements the riscv_rt :: ExceptionNumber trait. + --> tests/ui/exception/fail_empty_macro.rs:1:1 + | +1 | #[riscv_rt::exception] + | ^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in the attribute macro `riscv_rt::exception` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/riscv-rt/tests/ui/exception/fail_impl_exception_number.rs b/riscv-rt/tests/ui/exception/fail_impl_exception_number.rs new file mode 100644 index 00000000..42e44659 --- /dev/null +++ b/riscv-rt/tests/ui/exception/fail_impl_exception_number.rs @@ -0,0 +1,4 @@ +#[riscv_rt::exception(riscv::interrupt::Interrupt::SupervisorSoft)] +fn my_exception() {} + +fn main() {} diff --git a/riscv-rt/tests/ui/exception/fail_impl_exception_number.stderr b/riscv-rt/tests/ui/exception/fail_impl_exception_number.stderr new file mode 100644 index 00000000..13d929ac --- /dev/null +++ b/riscv-rt/tests/ui/exception/fail_impl_exception_number.stderr @@ -0,0 +1,18 @@ +error[E0277]: the trait bound `riscv::interrupt::Interrupt: ExceptionNumber` is not satisfied + --> tests/ui/exception/fail_impl_exception_number.rs:1:23 + | +1 | #[riscv_rt::exception(riscv::interrupt::Interrupt::SupervisorSoft)] + | ----------------------^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^-- + | | | + | | the trait `ExceptionNumber` is not implemented for `riscv::interrupt::Interrupt` + | required by a bound introduced by this call + | + = help: the following other types implement trait `ExceptionNumber`: + riscv::interrupt::Exception + riscv::interrupt::supervisor::Exception +note: required by a bound in `assert_impl` + --> tests/ui/exception/fail_impl_exception_number.rs:1:1 + | +1 | #[riscv_rt::exception(riscv::interrupt::Interrupt::SupervisorSoft)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `assert_impl` + = note: this error originates in the attribute macro `riscv_rt::exception` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/riscv-rt/tests/ui/exception/fail_signatures.rs b/riscv-rt/tests/ui/exception/fail_signatures.rs new file mode 100644 index 00000000..6970d9be --- /dev/null +++ b/riscv-rt/tests/ui/exception/fail_signatures.rs @@ -0,0 +1,10 @@ +#[riscv_rt::exception(riscv::interrupt::Exception::LoadMisaligned)] +fn my_exception(code: usize) {} + +#[riscv_rt::exception(riscv::interrupt::Exception::StoreMisaligned)] +fn my_other_exception(trap_frame: &riscv_rt::TrapFrame, code: usize) {} + +#[riscv_rt::exception(riscv::interrupt::Exception::LoadFault)] +async fn my_async_exception(trap_frame: &riscv_rt::TrapFrame, code: usize) {} + +fn main() {} diff --git a/riscv-rt/tests/ui/exception/fail_signatures.stderr b/riscv-rt/tests/ui/exception/fail_signatures.stderr new file mode 100644 index 00000000..aa26287a --- /dev/null +++ b/riscv-rt/tests/ui/exception/fail_signatures.stderr @@ -0,0 +1,17 @@ +error: `#[exception]` function must have signature `[unsafe] fn([&[mut] riscv_rt::TrapFrame]) [-> !]` + --> tests/ui/exception/fail_signatures.rs:2:1 + | +2 | fn my_exception(code: usize) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: `#[exception]` function must have signature `[unsafe] fn([&[mut] riscv_rt::TrapFrame]) [-> !]` + --> tests/ui/exception/fail_signatures.rs:5:1 + | +5 | fn my_other_exception(trap_frame: &riscv_rt::TrapFrame, code: usize) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: `#[exception]` function must have signature `[unsafe] fn([&[mut] riscv_rt::TrapFrame]) [-> !]` + --> tests/ui/exception/fail_signatures.rs:8:1 + | +8 | async fn my_async_exception(trap_frame: &riscv_rt::TrapFrame, code: usize) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/riscv-rt/tests/ui/exception/pass_exception.rs b/riscv-rt/tests/ui/exception/pass_exception.rs new file mode 100644 index 00000000..e0cdab5b --- /dev/null +++ b/riscv-rt/tests/ui/exception/pass_exception.rs @@ -0,0 +1,17 @@ +use riscv::interrupt::Exception::*; + +#[riscv_rt::exception(LoadMisaligned)] +fn simple_exception() {} + +#[riscv_rt::exception(LoadFault)] +fn unmutable_exception(_trap_frame: &riscv_rt::TrapFrame) {} + +#[riscv_rt::exception(StoreMisaligned)] +fn mutable_exception(_trap_frame: &mut riscv_rt::TrapFrame) {} + +#[riscv_rt::exception(StoreFault)] +fn no_return_exception(_trap_frame: &mut riscv_rt::TrapFrame) -> ! { + loop {} +} + +fn main() {} diff --git a/riscv-rt/tests/ui/external_interrupt/fail_arguments.rs b/riscv-rt/tests/ui/external_interrupt/fail_arguments.rs new file mode 100644 index 00000000..309d1d8c --- /dev/null +++ b/riscv-rt/tests/ui/external_interrupt/fail_arguments.rs @@ -0,0 +1,31 @@ +use riscv_rt::result::{Error, Result}; + +/// Just a dummy type to test the `ExternalInterrupt` trait. +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum ExternalInterrupt { + GPIO, + UART, +} +unsafe impl riscv_rt::InterruptNumber for ExternalInterrupt { + const MAX_INTERRUPT_NUMBER: usize = 1; + + #[inline] + fn number(self) -> usize { + self as usize + } + + #[inline] + fn from_number(value: usize) -> Result { + match value { + 0 => Ok(Self::GPIO), + 1 => Ok(Self::UART), + _ => Err(Error::InvalidVariant(value)), + } + } +} +unsafe impl riscv::ExternalInterruptNumber for ExternalInterrupt {} + +#[riscv_rt::external_interrupt(ExternalInterrupt::GPIO)] +fn my_interrupt(code: usize) {} + +fn main() {} diff --git a/riscv-rt/tests/ui/external_interrupt/fail_arguments.stderr b/riscv-rt/tests/ui/external_interrupt/fail_arguments.stderr new file mode 100644 index 00000000..8bd94554 --- /dev/null +++ b/riscv-rt/tests/ui/external_interrupt/fail_arguments.stderr @@ -0,0 +1,5 @@ +error: `#[external_interrupt]` function must have signature `[unsafe] fn() [-> !]` + --> tests/ui/external_interrupt/fail_arguments.rs:29:1 + | +29 | fn my_interrupt(code: usize) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/riscv-rt/tests/ui/external_interrupt/fail_empty_macro.rs b/riscv-rt/tests/ui/external_interrupt/fail_empty_macro.rs new file mode 100644 index 00000000..7767ed24 --- /dev/null +++ b/riscv-rt/tests/ui/external_interrupt/fail_empty_macro.rs @@ -0,0 +1,4 @@ +#[riscv_rt::external_interrupt] +fn my_interrupt() {} + +fn main() {} diff --git a/riscv-rt/tests/ui/external_interrupt/fail_empty_macro.stderr b/riscv-rt/tests/ui/external_interrupt/fail_empty_macro.stderr new file mode 100644 index 00000000..079b4e4f --- /dev/null +++ b/riscv-rt/tests/ui/external_interrupt/fail_empty_macro.stderr @@ -0,0 +1,7 @@ +error: `#[external_interrupt]` attribute expects a path to a variant of an enum that implements the riscv_rt :: ExternalInterruptNumber trait. + --> tests/ui/external_interrupt/fail_empty_macro.rs:1:1 + | +1 | #[riscv_rt::external_interrupt] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in the attribute macro `riscv_rt::external_interrupt` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/riscv-rt/tests/ui/external_interrupt/fail_impl_interrupt_number.rs b/riscv-rt/tests/ui/external_interrupt/fail_impl_interrupt_number.rs new file mode 100644 index 00000000..48ab92b6 --- /dev/null +++ b/riscv-rt/tests/ui/external_interrupt/fail_impl_interrupt_number.rs @@ -0,0 +1,4 @@ +#[riscv_rt::external_interrupt(riscv::interrupt::Interrupt::SupervisorSoft)] +fn my_interrupt() {} + +fn main() {} diff --git a/riscv-rt/tests/ui/external_interrupt/fail_impl_interrupt_number.stderr b/riscv-rt/tests/ui/external_interrupt/fail_impl_interrupt_number.stderr new file mode 100644 index 00000000..6f1d18ab --- /dev/null +++ b/riscv-rt/tests/ui/external_interrupt/fail_impl_interrupt_number.stderr @@ -0,0 +1,15 @@ +error[E0277]: the trait bound `riscv::interrupt::Interrupt: ExternalInterruptNumber` is not satisfied + --> tests/ui/external_interrupt/fail_impl_interrupt_number.rs:1:32 + | +1 | #[riscv_rt::external_interrupt(riscv::interrupt::Interrupt::SupervisorSoft)] + | -------------------------------^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^-- + | | | + | | the trait `ExternalInterruptNumber` is not implemented for `riscv::interrupt::Interrupt` + | required by a bound introduced by this call + | +note: required by a bound in `assert_impl` + --> tests/ui/external_interrupt/fail_impl_interrupt_number.rs:1:1 + | +1 | #[riscv_rt::external_interrupt(riscv::interrupt::Interrupt::SupervisorSoft)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `assert_impl` + = note: this error originates in the attribute macro `riscv_rt::external_interrupt` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/riscv-rt/tests/ui/external_interrupt/fail_signatures.rs b/riscv-rt/tests/ui/external_interrupt/fail_signatures.rs new file mode 100644 index 00000000..29757bae --- /dev/null +++ b/riscv-rt/tests/ui/external_interrupt/fail_signatures.rs @@ -0,0 +1,39 @@ +use riscv_rt::result::{Error, Result}; + +/// Just a dummy type to test the `ExternalInterrupt` trait. +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum ExternalInterrupt { + GPIO, + UART, + PWM, +} +unsafe impl riscv_rt::InterruptNumber for ExternalInterrupt { + const MAX_INTERRUPT_NUMBER: usize = 2; + + #[inline] + fn number(self) -> usize { + self as usize + } + + #[inline] + fn from_number(value: usize) -> Result { + match value { + 0 => Ok(Self::GPIO), + 1 => Ok(Self::UART), + 2 => Ok(Self::PWM), + _ => Err(Error::InvalidVariant(value)), + } + } +} +unsafe impl riscv::ExternalInterruptNumber for ExternalInterrupt {} + +#[riscv_rt::external_interrupt(ExternalInterrupt::GPIO)] +fn my_interrupt() -> usize {} + +#[riscv_rt::external_interrupt(ExternalInterrupt::UART)] +fn my_other_interrupt(code: usize) -> usize {} + +#[riscv_rt::external_interrupt(ExternalInterrupt::PWM)] +async fn my_async_interrupt(code: usize) -> usize {} + +fn main() {} diff --git a/riscv-rt/tests/ui/external_interrupt/fail_signatures.stderr b/riscv-rt/tests/ui/external_interrupt/fail_signatures.stderr new file mode 100644 index 00000000..39d6618f --- /dev/null +++ b/riscv-rt/tests/ui/external_interrupt/fail_signatures.stderr @@ -0,0 +1,17 @@ +error: `#[external_interrupt]` function must have signature `[unsafe] fn() [-> !]` + --> tests/ui/external_interrupt/fail_signatures.rs:31:1 + | +31 | fn my_interrupt() -> usize {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: `#[external_interrupt]` function must have signature `[unsafe] fn() [-> !]` + --> tests/ui/external_interrupt/fail_signatures.rs:34:1 + | +34 | fn my_other_interrupt(code: usize) -> usize {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: `#[external_interrupt]` function must have signature `[unsafe] fn() [-> !]` + --> tests/ui/external_interrupt/fail_signatures.rs:37:1 + | +37 | async fn my_async_interrupt(code: usize) -> usize {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/riscv-rt/tests/ui/external_interrupt/pass_external_interrupt.rs b/riscv-rt/tests/ui/external_interrupt/pass_external_interrupt.rs new file mode 100644 index 00000000..57aff904 --- /dev/null +++ b/riscv-rt/tests/ui/external_interrupt/pass_external_interrupt.rs @@ -0,0 +1,36 @@ +use riscv_rt::result::{Error, Result}; + +/// Just a dummy type to test the `ExternalInterrupt` trait. +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum ExternalInterrupt { + GPIO, + UART, +} +unsafe impl riscv_rt::InterruptNumber for ExternalInterrupt { + const MAX_INTERRUPT_NUMBER: usize = 1; + + #[inline] + fn number(self) -> usize { + self as usize + } + + #[inline] + fn from_number(value: usize) -> Result { + match value { + 0 => Ok(Self::GPIO), + 1 => Ok(Self::UART), + _ => Err(Error::InvalidVariant(value)), + } + } +} +unsafe impl riscv::ExternalInterruptNumber for ExternalInterrupt {} + +#[riscv_rt::external_interrupt(ExternalInterrupt::GPIO)] +fn simple_interrupt() {} + +#[riscv_rt::external_interrupt(ExternalInterrupt::UART)] +unsafe fn no_return_interrupt() -> ! { + loop {} +} + +fn main() {} diff --git a/riscv/CHANGELOG.md b/riscv/CHANGELOG.md index 7d1ede5f..98d1b7b4 100644 --- a/riscv/CHANGELOG.md +++ b/riscv/CHANGELOG.md @@ -9,7 +9,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Added -- riscv-macros crate for non-standard interrupt enums. +- `riscv-macros` crate for `riscv-pac` enums. - Bump MSRV to 1.61. - Implementation of `riscv-pac` traits for `Interrupt` and `Exception` enums. - Tests for the `riscv-pac` trait implementations of `Interrupt` and `Exception` enums. diff --git a/riscv/macros/src/lib.rs b/riscv/macros/src/lib.rs index 63cff8d5..fae9b4be 100644 --- a/riscv/macros/src/lib.rs +++ b/riscv/macros/src/lib.rs @@ -96,16 +96,6 @@ impl PacTrait { } } - /// Returns a token stream representing the data type used to represent the number - fn num_type(&self) -> TokenStream2 { - match self { - Self::Exception => quote!(usize), - Self::Interrupt(_) => quote!(usize), - Self::Priority => quote!(u8), - Self::HartId => quote!(u16), - } - } - /// Returns a token stream representing the name of the constant that holds the maximum number fn const_name(&self) -> TokenStream2 { match self { @@ -238,11 +228,6 @@ impl PacEnumItem { } } - /// Returns a token stream representing the maximum discriminant value of the enum - fn max_discriminant(&self) -> TokenStream2 { - TokenStream2::from_str(&format!("{}", self.max_number)).unwrap() - } - /// Returns a vector of token streams representing the valid matches in the `pac::from_number` function fn valid_matches(&self) -> Vec { self.numbers @@ -325,27 +310,26 @@ core::arch::global_asm!(" let name = &self.name; let trait_name = attr.trait_name(); - let num_type = attr.num_type(); let const_name = attr.const_name(); - let max_discriminant = self.max_discriminant(); + let max_discriminant = self.max_number; let valid_matches = self.valid_matches(); // Push the trait implementation res.push(quote! { unsafe impl riscv::#trait_name for #name { - const #const_name: #num_type = #max_discriminant; + const #const_name: usize = #max_discriminant; #[inline] - fn number(self) -> #num_type { + fn number(self) -> usize { self as _ } #[inline] - fn from_number(number: #num_type) -> riscv::result::Result { + fn from_number(number: usize) -> riscv::result::Result { match number { #(#valid_matches,)* - _ => Err(riscv::result::Error::InvalidVariant(number as _)), + _ => Err(riscv::result::Error::InvalidVariant(number)), } } } From 7b3c186cfedf25084ecb62938e2f996712d45eb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rom=C3=A1n=20C=C3=A1rdenas=20Rodr=C3=ADguez?= Date: Tue, 13 Aug 2024 11:34:38 +0200 Subject: [PATCH 10/14] Isolate try-build tests from riscv-rt --- .github/workflows/riscv-rt.yaml | 22 +++++++++++-- .github/workflows/tests.yaml | 28 +++++++++++++++++ Cargo.toml | 1 + riscv-rt/Cargo.toml | 1 - riscv-rt/examples/empty.rs | 1 - riscv-rt/examples/multi_core.rs | 2 -- riscv-rt/macros/src/lib.rs | 6 ++++ riscv-rt/tests/test.rs | 6 ---- .../ui/external_interrupt/fail_arguments.rs | 31 ------------------- .../external_interrupt/fail_arguments.stderr | 5 --- tests/Cargo.toml | 9 ++++++ .../core_interrupt/fail_empty_macro.rs | 0 .../core_interrupt/fail_empty_macro.stderr | 2 +- .../fail_impl_interrupt_number.rs | 0 .../fail_impl_interrupt_number.stderr | 4 +-- .../core_interrupt/fail_signatures.rs | 0 .../core_interrupt/fail_signatures.stderr | 6 ++-- .../core_interrupt/pass_core_interrupt.rs | 0 .../riscv-rt}/exception/fail_empty_macro.rs | 0 .../exception/fail_empty_macro.stderr | 2 +- .../exception/fail_impl_exception_number.rs | 0 .../fail_impl_exception_number.stderr | 4 +-- .../riscv-rt}/exception/fail_signatures.rs | 0 .../exception/fail_signatures.stderr | 6 ++-- .../riscv-rt}/exception/pass_exception.rs | 0 .../external_interrupt/fail_empty_macro.rs | 0 .../fail_empty_macro.stderr | 2 +- .../fail_impl_interrupt_number.rs | 0 .../fail_impl_interrupt_number.stderr | 4 +-- .../external_interrupt/fail_signatures.rs | 0 .../external_interrupt/fail_signatures.stderr | 6 ++-- .../pass_external_interrupt.rs | 0 tests/tests/test.rs | 6 ++++ 33 files changed, 87 insertions(+), 67 deletions(-) create mode 100644 .github/workflows/tests.yaml delete mode 100644 riscv-rt/tests/test.rs delete mode 100644 riscv-rt/tests/ui/external_interrupt/fail_arguments.rs delete mode 100644 riscv-rt/tests/ui/external_interrupt/fail_arguments.stderr create mode 100644 tests/Cargo.toml rename {riscv-rt/tests/ui => tests/tests/riscv-rt}/core_interrupt/fail_empty_macro.rs (100%) rename {riscv-rt/tests/ui => tests/tests/riscv-rt}/core_interrupt/fail_empty_macro.stderr (85%) rename {riscv-rt/tests/ui => tests/tests/riscv-rt}/core_interrupt/fail_impl_interrupt_number.rs (100%) rename {riscv-rt/tests/ui => tests/tests/riscv-rt}/core_interrupt/fail_impl_interrupt_number.stderr (87%) rename {riscv-rt/tests/ui => tests/tests/riscv-rt}/core_interrupt/fail_signatures.rs (100%) rename {riscv-rt/tests/ui => tests/tests/riscv-rt}/core_interrupt/fail_signatures.stderr (72%) rename {riscv-rt/tests/ui => tests/tests/riscv-rt}/core_interrupt/pass_core_interrupt.rs (100%) rename {riscv-rt/tests/ui => tests/tests/riscv-rt}/exception/fail_empty_macro.rs (100%) rename {riscv-rt/tests/ui => tests/tests/riscv-rt}/exception/fail_empty_macro.stderr (85%) rename {riscv-rt/tests/ui => tests/tests/riscv-rt}/exception/fail_impl_exception_number.rs (100%) rename {riscv-rt/tests/ui => tests/tests/riscv-rt}/exception/fail_impl_exception_number.stderr (88%) rename {riscv-rt/tests/ui => tests/tests/riscv-rt}/exception/fail_signatures.rs (100%) rename {riscv-rt/tests/ui => tests/tests/riscv-rt}/exception/fail_signatures.stderr (81%) rename {riscv-rt/tests/ui => tests/tests/riscv-rt}/exception/pass_exception.rs (100%) rename {riscv-rt/tests/ui => tests/tests/riscv-rt}/external_interrupt/fail_empty_macro.rs (100%) rename {riscv-rt/tests/ui => tests/tests/riscv-rt}/external_interrupt/fail_empty_macro.stderr (85%) rename {riscv-rt/tests/ui => tests/tests/riscv-rt}/external_interrupt/fail_impl_interrupt_number.rs (100%) rename {riscv-rt/tests/ui => tests/tests/riscv-rt}/external_interrupt/fail_impl_interrupt_number.stderr (85%) rename {riscv-rt/tests/ui => tests/tests/riscv-rt}/external_interrupt/fail_signatures.rs (100%) rename {riscv-rt/tests/ui => tests/tests/riscv-rt}/external_interrupt/fail_signatures.stderr (74%) rename {riscv-rt/tests/ui => tests/tests/riscv-rt}/external_interrupt/pass_external_interrupt.rs (100%) create mode 100644 tests/tests/test.rs diff --git a/.github/workflows/riscv-rt.yaml b/.github/workflows/riscv-rt.yaml index be49bbce..a1727162 100644 --- a/.github/workflows/riscv-rt.yaml +++ b/.github/workflows/riscv-rt.yaml @@ -7,7 +7,7 @@ on: name: Build check (riscv-rt) jobs: - build: + build-riscv: strategy: matrix: # All generated code should be running on stable now, MRSV is 1.61.0 @@ -45,11 +45,27 @@ jobs: run: RUSTFLAGS="-C link-arg=-Triscv-rt/examples/device.x" cargo build --package riscv-rt --target ${{ matrix.target }} --example ${{ matrix.example }} --features=s-mode,single-hart,v-trap - name : Build (u-boot) run: RUSTFLAGS="-C link-arg=-Triscv-rt/examples/device.x" cargo build --package riscv-rt --target ${{ matrix.target }} --example empty --features=u-boot - + + build-others: + strategy: + matrix: + os: [ macos-latest, ubuntu-latest, windows-latest ] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + - name: Build (no features) + run: cargo build --package riscv-rt + - name: Build (all features but u-boot) + run: cargo build --package riscv-rt --features=s-mode,single-hart,v-trap + - name: Build (u-boot) + run: cargo build --package riscv-rt --features=u-boot + # Job to check that all the builds succeeded build-check: needs: - - build + - build-riscv + - build-others runs-on: ubuntu-latest if: always() steps: diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml new file mode 100644 index 00000000..a7ab62c7 --- /dev/null +++ b/.github/workflows/tests.yaml @@ -0,0 +1,28 @@ +on: + push: + branches: [ master ] + pull_request: + merge_group: + +name: Run macro tests (tests) + +jobs: + run-tests: + strategy: + matrix: + os: [ macos-latest, ubuntu-latest ] # windows shows weird linking errors + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@nightly + - name: Run tests + run: cargo test --package tests + + # Job to check that all the builds succeeded + tests-check: + needs: + - run-tests + 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 5e73fed7..ba28eae6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,4 +6,5 @@ members = [ "riscv-peripheral", "riscv-rt", "riscv-semihosting", + "tests", ] diff --git a/riscv-rt/Cargo.toml b/riscv-rt/Cargo.toml index b861372e..245aa565 100644 --- a/riscv-rt/Cargo.toml +++ b/riscv-rt/Cargo.toml @@ -25,7 +25,6 @@ riscv-pac = { path = "../riscv-pac", version = "0.2.0" } riscv-rt-macros = { path = "macros", version = "0.2.1" } [dev-dependencies] -trybuild = "1.0" panic-halt = "0.2.0" [features] diff --git a/riscv-rt/examples/empty.rs b/riscv-rt/examples/empty.rs index 837b6aea..cac3488b 100644 --- a/riscv-rt/examples/empty.rs +++ b/riscv-rt/examples/empty.rs @@ -2,7 +2,6 @@ #![no_main] extern crate panic_halt; -extern crate riscv_rt; use riscv_rt::{core_interrupt, entry, exception, external_interrupt}; diff --git a/riscv-rt/examples/multi_core.rs b/riscv-rt/examples/multi_core.rs index a3cbab79..0ccfc97a 100644 --- a/riscv-rt/examples/multi_core.rs +++ b/riscv-rt/examples/multi_core.rs @@ -2,8 +2,6 @@ #![no_main] extern crate panic_halt; -extern crate riscv; -extern crate riscv_rt; use riscv::asm::wfi; use riscv::register::{mie, mip}; diff --git a/riscv-rt/macros/src/lib.rs b/riscv-rt/macros/src/lib.rs index 3eb482ed..62923e57 100644 --- a/riscv-rt/macros/src/lib.rs +++ b/riscv-rt/macros/src/lib.rs @@ -1,5 +1,11 @@ #![deny(warnings)] +extern crate core; +extern crate proc_macro; +extern crate proc_macro2; +extern crate quote; +extern crate syn; + use proc_macro2::{Span, TokenStream as TokenStream2}; use quote::quote; use syn::{ diff --git a/riscv-rt/tests/test.rs b/riscv-rt/tests/test.rs deleted file mode 100644 index 7c57e60a..00000000 --- a/riscv-rt/tests/test.rs +++ /dev/null @@ -1,6 +0,0 @@ -#[test] -fn ui() { - let t = trybuild::TestCases::new(); - t.compile_fail("tests/ui/*/fail_*.rs"); - t.pass("tests/ui/*/pass_*.rs"); -} diff --git a/riscv-rt/tests/ui/external_interrupt/fail_arguments.rs b/riscv-rt/tests/ui/external_interrupt/fail_arguments.rs deleted file mode 100644 index 309d1d8c..00000000 --- a/riscv-rt/tests/ui/external_interrupt/fail_arguments.rs +++ /dev/null @@ -1,31 +0,0 @@ -use riscv_rt::result::{Error, Result}; - -/// Just a dummy type to test the `ExternalInterrupt` trait. -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub enum ExternalInterrupt { - GPIO, - UART, -} -unsafe impl riscv_rt::InterruptNumber for ExternalInterrupt { - const MAX_INTERRUPT_NUMBER: usize = 1; - - #[inline] - fn number(self) -> usize { - self as usize - } - - #[inline] - fn from_number(value: usize) -> Result { - match value { - 0 => Ok(Self::GPIO), - 1 => Ok(Self::UART), - _ => Err(Error::InvalidVariant(value)), - } - } -} -unsafe impl riscv::ExternalInterruptNumber for ExternalInterrupt {} - -#[riscv_rt::external_interrupt(ExternalInterrupt::GPIO)] -fn my_interrupt(code: usize) {} - -fn main() {} diff --git a/riscv-rt/tests/ui/external_interrupt/fail_arguments.stderr b/riscv-rt/tests/ui/external_interrupt/fail_arguments.stderr deleted file mode 100644 index 8bd94554..00000000 --- a/riscv-rt/tests/ui/external_interrupt/fail_arguments.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: `#[external_interrupt]` function must have signature `[unsafe] fn() [-> !]` - --> tests/ui/external_interrupt/fail_arguments.rs:29:1 - | -29 | fn my_interrupt(code: usize) {} - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/Cargo.toml b/tests/Cargo.toml new file mode 100644 index 00000000..1dafa88a --- /dev/null +++ b/tests/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "tests" +version = "0.1.0" +edition = "2021" + +[dependencies] +riscv = { path = "../riscv", version = "0.12.0" } +riscv-rt = { path = "../riscv-rt", version = "0.13.0" } +trybuild = "1.0" diff --git a/riscv-rt/tests/ui/core_interrupt/fail_empty_macro.rs b/tests/tests/riscv-rt/core_interrupt/fail_empty_macro.rs similarity index 100% rename from riscv-rt/tests/ui/core_interrupt/fail_empty_macro.rs rename to tests/tests/riscv-rt/core_interrupt/fail_empty_macro.rs diff --git a/riscv-rt/tests/ui/core_interrupt/fail_empty_macro.stderr b/tests/tests/riscv-rt/core_interrupt/fail_empty_macro.stderr similarity index 85% rename from riscv-rt/tests/ui/core_interrupt/fail_empty_macro.stderr rename to tests/tests/riscv-rt/core_interrupt/fail_empty_macro.stderr index 1f3d12f4..99d53458 100644 --- a/riscv-rt/tests/ui/core_interrupt/fail_empty_macro.stderr +++ b/tests/tests/riscv-rt/core_interrupt/fail_empty_macro.stderr @@ -1,5 +1,5 @@ error: `#[core_interrupt]` attribute expects a path to a variant of an enum that implements the riscv_rt :: CoreInterruptNumber trait. - --> tests/ui/core_interrupt/fail_empty_macro.rs:1:1 + --> tests/riscv-rt/core_interrupt/fail_empty_macro.rs:1:1 | 1 | #[riscv_rt::core_interrupt] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/riscv-rt/tests/ui/core_interrupt/fail_impl_interrupt_number.rs b/tests/tests/riscv-rt/core_interrupt/fail_impl_interrupt_number.rs similarity index 100% rename from riscv-rt/tests/ui/core_interrupt/fail_impl_interrupt_number.rs rename to tests/tests/riscv-rt/core_interrupt/fail_impl_interrupt_number.rs diff --git a/riscv-rt/tests/ui/core_interrupt/fail_impl_interrupt_number.stderr b/tests/tests/riscv-rt/core_interrupt/fail_impl_interrupt_number.stderr similarity index 87% rename from riscv-rt/tests/ui/core_interrupt/fail_impl_interrupt_number.stderr rename to tests/tests/riscv-rt/core_interrupt/fail_impl_interrupt_number.stderr index 0febf6cc..96e6fba5 100644 --- a/riscv-rt/tests/ui/core_interrupt/fail_impl_interrupt_number.stderr +++ b/tests/tests/riscv-rt/core_interrupt/fail_impl_interrupt_number.stderr @@ -1,5 +1,5 @@ error[E0277]: the trait bound `riscv::interrupt::Exception: CoreInterruptNumber` is not satisfied - --> tests/ui/core_interrupt/fail_impl_interrupt_number.rs:1:28 + --> tests/riscv-rt/core_interrupt/fail_impl_interrupt_number.rs:1:28 | 1 | #[riscv_rt::core_interrupt(riscv::interrupt::Exception::LoadMisaligned)] | ---------------------------^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^-- @@ -11,7 +11,7 @@ error[E0277]: the trait bound `riscv::interrupt::Exception: CoreInterruptNumber` riscv::interrupt::Interrupt riscv::interrupt::supervisor::Interrupt note: required by a bound in `assert_impl` - --> tests/ui/core_interrupt/fail_impl_interrupt_number.rs:1:1 + --> tests/riscv-rt/core_interrupt/fail_impl_interrupt_number.rs:1:1 | 1 | #[riscv_rt::core_interrupt(riscv::interrupt::Exception::LoadMisaligned)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `assert_impl` diff --git a/riscv-rt/tests/ui/core_interrupt/fail_signatures.rs b/tests/tests/riscv-rt/core_interrupt/fail_signatures.rs similarity index 100% rename from riscv-rt/tests/ui/core_interrupt/fail_signatures.rs rename to tests/tests/riscv-rt/core_interrupt/fail_signatures.rs diff --git a/riscv-rt/tests/ui/core_interrupt/fail_signatures.stderr b/tests/tests/riscv-rt/core_interrupt/fail_signatures.stderr similarity index 72% rename from riscv-rt/tests/ui/core_interrupt/fail_signatures.stderr rename to tests/tests/riscv-rt/core_interrupt/fail_signatures.stderr index 0b3c882f..03c1eec7 100644 --- a/riscv-rt/tests/ui/core_interrupt/fail_signatures.stderr +++ b/tests/tests/riscv-rt/core_interrupt/fail_signatures.stderr @@ -1,17 +1,17 @@ error: `#[core_interrupt]` function must have signature `[unsafe] fn() [-> !]` - --> tests/ui/core_interrupt/fail_signatures.rs:2:1 + --> tests/riscv-rt/core_interrupt/fail_signatures.rs:2:1 | 2 | fn my_interrupt(code: usize) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: `#[core_interrupt]` function must have signature `[unsafe] fn() [-> !]` - --> tests/ui/core_interrupt/fail_signatures.rs:5:1 + --> tests/riscv-rt/core_interrupt/fail_signatures.rs:5:1 | 5 | fn my_other_interrupt() -> usize {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: `#[core_interrupt]` function must have signature `[unsafe] fn() [-> !]` - --> tests/ui/core_interrupt/fail_signatures.rs:8:1 + --> tests/riscv-rt/core_interrupt/fail_signatures.rs:8:1 | 8 | async fn my_async_interrupt() {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/riscv-rt/tests/ui/core_interrupt/pass_core_interrupt.rs b/tests/tests/riscv-rt/core_interrupt/pass_core_interrupt.rs similarity index 100% rename from riscv-rt/tests/ui/core_interrupt/pass_core_interrupt.rs rename to tests/tests/riscv-rt/core_interrupt/pass_core_interrupt.rs diff --git a/riscv-rt/tests/ui/exception/fail_empty_macro.rs b/tests/tests/riscv-rt/exception/fail_empty_macro.rs similarity index 100% rename from riscv-rt/tests/ui/exception/fail_empty_macro.rs rename to tests/tests/riscv-rt/exception/fail_empty_macro.rs diff --git a/riscv-rt/tests/ui/exception/fail_empty_macro.stderr b/tests/tests/riscv-rt/exception/fail_empty_macro.stderr similarity index 85% rename from riscv-rt/tests/ui/exception/fail_empty_macro.stderr rename to tests/tests/riscv-rt/exception/fail_empty_macro.stderr index 7bf44695..7489bd94 100644 --- a/riscv-rt/tests/ui/exception/fail_empty_macro.stderr +++ b/tests/tests/riscv-rt/exception/fail_empty_macro.stderr @@ -1,5 +1,5 @@ error: `#[exception]` attribute expects a path to a variant of an enum that implements the riscv_rt :: ExceptionNumber trait. - --> tests/ui/exception/fail_empty_macro.rs:1:1 + --> tests/riscv-rt/exception/fail_empty_macro.rs:1:1 | 1 | #[riscv_rt::exception] | ^^^^^^^^^^^^^^^^^^^^^^ diff --git a/riscv-rt/tests/ui/exception/fail_impl_exception_number.rs b/tests/tests/riscv-rt/exception/fail_impl_exception_number.rs similarity index 100% rename from riscv-rt/tests/ui/exception/fail_impl_exception_number.rs rename to tests/tests/riscv-rt/exception/fail_impl_exception_number.rs diff --git a/riscv-rt/tests/ui/exception/fail_impl_exception_number.stderr b/tests/tests/riscv-rt/exception/fail_impl_exception_number.stderr similarity index 88% rename from riscv-rt/tests/ui/exception/fail_impl_exception_number.stderr rename to tests/tests/riscv-rt/exception/fail_impl_exception_number.stderr index 13d929ac..ce4c506b 100644 --- a/riscv-rt/tests/ui/exception/fail_impl_exception_number.stderr +++ b/tests/tests/riscv-rt/exception/fail_impl_exception_number.stderr @@ -1,5 +1,5 @@ error[E0277]: the trait bound `riscv::interrupt::Interrupt: ExceptionNumber` is not satisfied - --> tests/ui/exception/fail_impl_exception_number.rs:1:23 + --> tests/riscv-rt/exception/fail_impl_exception_number.rs:1:23 | 1 | #[riscv_rt::exception(riscv::interrupt::Interrupt::SupervisorSoft)] | ----------------------^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^-- @@ -11,7 +11,7 @@ error[E0277]: the trait bound `riscv::interrupt::Interrupt: ExceptionNumber` is riscv::interrupt::Exception riscv::interrupt::supervisor::Exception note: required by a bound in `assert_impl` - --> tests/ui/exception/fail_impl_exception_number.rs:1:1 + --> tests/riscv-rt/exception/fail_impl_exception_number.rs:1:1 | 1 | #[riscv_rt::exception(riscv::interrupt::Interrupt::SupervisorSoft)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `assert_impl` diff --git a/riscv-rt/tests/ui/exception/fail_signatures.rs b/tests/tests/riscv-rt/exception/fail_signatures.rs similarity index 100% rename from riscv-rt/tests/ui/exception/fail_signatures.rs rename to tests/tests/riscv-rt/exception/fail_signatures.rs diff --git a/riscv-rt/tests/ui/exception/fail_signatures.stderr b/tests/tests/riscv-rt/exception/fail_signatures.stderr similarity index 81% rename from riscv-rt/tests/ui/exception/fail_signatures.stderr rename to tests/tests/riscv-rt/exception/fail_signatures.stderr index aa26287a..6f0c9107 100644 --- a/riscv-rt/tests/ui/exception/fail_signatures.stderr +++ b/tests/tests/riscv-rt/exception/fail_signatures.stderr @@ -1,17 +1,17 @@ error: `#[exception]` function must have signature `[unsafe] fn([&[mut] riscv_rt::TrapFrame]) [-> !]` - --> tests/ui/exception/fail_signatures.rs:2:1 + --> tests/riscv-rt/exception/fail_signatures.rs:2:1 | 2 | fn my_exception(code: usize) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: `#[exception]` function must have signature `[unsafe] fn([&[mut] riscv_rt::TrapFrame]) [-> !]` - --> tests/ui/exception/fail_signatures.rs:5:1 + --> tests/riscv-rt/exception/fail_signatures.rs:5:1 | 5 | fn my_other_exception(trap_frame: &riscv_rt::TrapFrame, code: usize) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: `#[exception]` function must have signature `[unsafe] fn([&[mut] riscv_rt::TrapFrame]) [-> !]` - --> tests/ui/exception/fail_signatures.rs:8:1 + --> tests/riscv-rt/exception/fail_signatures.rs:8:1 | 8 | async fn my_async_exception(trap_frame: &riscv_rt::TrapFrame, code: usize) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/riscv-rt/tests/ui/exception/pass_exception.rs b/tests/tests/riscv-rt/exception/pass_exception.rs similarity index 100% rename from riscv-rt/tests/ui/exception/pass_exception.rs rename to tests/tests/riscv-rt/exception/pass_exception.rs diff --git a/riscv-rt/tests/ui/external_interrupt/fail_empty_macro.rs b/tests/tests/riscv-rt/external_interrupt/fail_empty_macro.rs similarity index 100% rename from riscv-rt/tests/ui/external_interrupt/fail_empty_macro.rs rename to tests/tests/riscv-rt/external_interrupt/fail_empty_macro.rs diff --git a/riscv-rt/tests/ui/external_interrupt/fail_empty_macro.stderr b/tests/tests/riscv-rt/external_interrupt/fail_empty_macro.stderr similarity index 85% rename from riscv-rt/tests/ui/external_interrupt/fail_empty_macro.stderr rename to tests/tests/riscv-rt/external_interrupt/fail_empty_macro.stderr index 079b4e4f..5ef53507 100644 --- a/riscv-rt/tests/ui/external_interrupt/fail_empty_macro.stderr +++ b/tests/tests/riscv-rt/external_interrupt/fail_empty_macro.stderr @@ -1,5 +1,5 @@ error: `#[external_interrupt]` attribute expects a path to a variant of an enum that implements the riscv_rt :: ExternalInterruptNumber trait. - --> tests/ui/external_interrupt/fail_empty_macro.rs:1:1 + --> tests/riscv-rt/external_interrupt/fail_empty_macro.rs:1:1 | 1 | #[riscv_rt::external_interrupt] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/riscv-rt/tests/ui/external_interrupt/fail_impl_interrupt_number.rs b/tests/tests/riscv-rt/external_interrupt/fail_impl_interrupt_number.rs similarity index 100% rename from riscv-rt/tests/ui/external_interrupt/fail_impl_interrupt_number.rs rename to tests/tests/riscv-rt/external_interrupt/fail_impl_interrupt_number.rs diff --git a/riscv-rt/tests/ui/external_interrupt/fail_impl_interrupt_number.stderr b/tests/tests/riscv-rt/external_interrupt/fail_impl_interrupt_number.stderr similarity index 85% rename from riscv-rt/tests/ui/external_interrupt/fail_impl_interrupt_number.stderr rename to tests/tests/riscv-rt/external_interrupt/fail_impl_interrupt_number.stderr index 6f1d18ab..03dace2c 100644 --- a/riscv-rt/tests/ui/external_interrupt/fail_impl_interrupt_number.stderr +++ b/tests/tests/riscv-rt/external_interrupt/fail_impl_interrupt_number.stderr @@ -1,5 +1,5 @@ error[E0277]: the trait bound `riscv::interrupt::Interrupt: ExternalInterruptNumber` is not satisfied - --> tests/ui/external_interrupt/fail_impl_interrupt_number.rs:1:32 + --> tests/riscv-rt/external_interrupt/fail_impl_interrupt_number.rs:1:32 | 1 | #[riscv_rt::external_interrupt(riscv::interrupt::Interrupt::SupervisorSoft)] | -------------------------------^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^-- @@ -8,7 +8,7 @@ error[E0277]: the trait bound `riscv::interrupt::Interrupt: ExternalInterruptNum | required by a bound introduced by this call | note: required by a bound in `assert_impl` - --> tests/ui/external_interrupt/fail_impl_interrupt_number.rs:1:1 + --> tests/riscv-rt/external_interrupt/fail_impl_interrupt_number.rs:1:1 | 1 | #[riscv_rt::external_interrupt(riscv::interrupt::Interrupt::SupervisorSoft)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `assert_impl` diff --git a/riscv-rt/tests/ui/external_interrupt/fail_signatures.rs b/tests/tests/riscv-rt/external_interrupt/fail_signatures.rs similarity index 100% rename from riscv-rt/tests/ui/external_interrupt/fail_signatures.rs rename to tests/tests/riscv-rt/external_interrupt/fail_signatures.rs diff --git a/riscv-rt/tests/ui/external_interrupt/fail_signatures.stderr b/tests/tests/riscv-rt/external_interrupt/fail_signatures.stderr similarity index 74% rename from riscv-rt/tests/ui/external_interrupt/fail_signatures.stderr rename to tests/tests/riscv-rt/external_interrupt/fail_signatures.stderr index 39d6618f..3ea34689 100644 --- a/riscv-rt/tests/ui/external_interrupt/fail_signatures.stderr +++ b/tests/tests/riscv-rt/external_interrupt/fail_signatures.stderr @@ -1,17 +1,17 @@ error: `#[external_interrupt]` function must have signature `[unsafe] fn() [-> !]` - --> tests/ui/external_interrupt/fail_signatures.rs:31:1 + --> tests/riscv-rt/external_interrupt/fail_signatures.rs:31:1 | 31 | fn my_interrupt() -> usize {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^ error: `#[external_interrupt]` function must have signature `[unsafe] fn() [-> !]` - --> tests/ui/external_interrupt/fail_signatures.rs:34:1 + --> tests/riscv-rt/external_interrupt/fail_signatures.rs:34:1 | 34 | fn my_other_interrupt(code: usize) -> usize {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: `#[external_interrupt]` function must have signature `[unsafe] fn() [-> !]` - --> tests/ui/external_interrupt/fail_signatures.rs:37:1 + --> tests/riscv-rt/external_interrupt/fail_signatures.rs:37:1 | 37 | async fn my_async_interrupt(code: usize) -> usize {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/riscv-rt/tests/ui/external_interrupt/pass_external_interrupt.rs b/tests/tests/riscv-rt/external_interrupt/pass_external_interrupt.rs similarity index 100% rename from riscv-rt/tests/ui/external_interrupt/pass_external_interrupt.rs rename to tests/tests/riscv-rt/external_interrupt/pass_external_interrupt.rs diff --git a/tests/tests/test.rs b/tests/tests/test.rs new file mode 100644 index 00000000..7506ddfc --- /dev/null +++ b/tests/tests/test.rs @@ -0,0 +1,6 @@ +#[test] +fn riscv_rt() { + let t = trybuild::TestCases::new(); + t.compile_fail("tests/riscv-rt/*/fail_*.rs"); + t.pass("tests/riscv-rt/*/pass_*.rs"); +} From a84b43df16af330d1382dd368833fb32e15ebcfd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rom=C3=A1n=20C=C3=A1rdenas=20Rodr=C3=ADguez?= Date: Tue, 20 Aug 2024 18:21:13 +0200 Subject: [PATCH 11/14] fix error in riscv_peripheral::plic_codegen! --- riscv-peripheral/src/macros.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/riscv-peripheral/src/macros.rs b/riscv-peripheral/src/macros.rs index 5ba21314..a6272fe8 100644 --- a/riscv-peripheral/src/macros.rs +++ b/riscv-peripheral/src/macros.rs @@ -354,7 +354,7 @@ macro_rules! plic_codegen { /// 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 { + pub fn ctx_mhartid() -> $crate::plic::CTX { $crate::plic::PLIC::::ctx_mhartid() } } From ce9a296ddf18e8129e8e37d3bff56c860bedaebe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rom=C3=A1n=20C=C3=A1rdenas=20Rodr=C3=ADguez?= Date: Wed, 21 Aug 2024 11:10:59 +0200 Subject: [PATCH 12/14] Update docs --- riscv-rt/src/lib.rs | 116 +++++++----------- riscv/Cargo.toml | 4 - riscv/src/interrupt.rs | 32 ++++- riscv/src/interrupt/supervisor.rs | 1 + riscv/tests/test.rs | 6 - tests/Cargo.toml | 2 +- .../tests/riscv}/fail_empty_macro.rs | 0 .../tests/riscv}/fail_empty_macro.stderr | 2 +- .../tests/riscv}/fail_no_unsafe.rs | 0 .../tests/riscv}/fail_no_unsafe.stderr | 2 +- .../tests/riscv}/fail_unknown_trait.rs | 0 .../tests/riscv}/fail_unknown_trait.stderr | 2 +- .../ui => tests/tests/riscv}/pass_test.rs | 0 tests/tests/test.rs | 9 ++ 14 files changed, 91 insertions(+), 85 deletions(-) delete mode 100644 riscv/tests/test.rs rename {riscv/tests/ui => tests/tests/riscv}/fail_empty_macro.rs (100%) rename {riscv/tests/ui => tests/tests/riscv}/fail_empty_macro.stderr (85%) rename {riscv/tests/ui => tests/tests/riscv}/fail_no_unsafe.rs (100%) rename {riscv/tests/ui => tests/tests/riscv}/fail_no_unsafe.stderr (72%) rename {riscv/tests/ui => tests/tests/riscv}/fail_unknown_trait.rs (100%) rename {riscv/tests/ui => tests/tests/riscv}/fail_unknown_trait.stderr (84%) rename {riscv/tests/ui => tests/tests/riscv}/pass_test.rs (100%) diff --git a/riscv-rt/src/lib.rs b/riscv-rt/src/lib.rs index 7b74b456..9705ad28 100644 --- a/riscv-rt/src/lib.rs +++ b/riscv-rt/src/lib.rs @@ -270,34 +270,24 @@ //! ### Core exception handlers //! //! This functions are called when corresponding exception occurs. -//! You can define an exception handler with one of the following names: -//! * `InstructionMisaligned` -//! * `InstructionFault` -//! * `IllegalInstruction` -//! * `Breakpoint` -//! * `LoadMisaligned` -//! * `LoadFault` -//! * `StoreMisaligned` -//! * `StoreFault` -//! * `UserEnvCall` -//! * `SupervisorEnvCall` -//! * `MachineEnvCall` -//! * `InstructionPageFault` -//! * `LoadPageFault` -//! * `StorePageFault` +//! You can define an exception handler with the [`exception`] attribute. +//! The attribute expects the path to the exception source as an argument. +//! +//! The [`exception`] attribute ensures at compile time that there is a valid +//! exception source for the given handler. //! //! For example: //! ``` no_run -//! #[export_name = "MachineEnvCall"] -//! fn custom_menv_call_handler(trap_frame: &riscv_rt::TrapFrame) { -//! // ... +//! use riscv::interrupt::Exception; // or a target-specific exception enum +//! +//! #[riscv_rt::exception(Exception::MachineEnvCall)] +//! fn custom_menv_call_handler(trap_frame: &mut riscv_rt::TrapFrame) { +//! todo!() //! } -//! ``` -//! or -//! ``` no_run -//! #[no_mangle] -//! fn MachineEnvCall(trap_frame: &riscv_rt::TrapFrame) -> ! { -//! // ... +//! +//! #[riscv_rt::exception(Exception::LoadFault)] +//! fn custom_load_fault_handler() -> ! { +//! loop {} //! } //! ``` //! @@ -320,51 +310,50 @@ //! or //! ``` no_run //! #[no_mangle] -//! fn ExceptionHandler(trap_frame: &riscv_rt::TrapFrame) -> ! { +//! fn ExceptionHandler(trap_frame: &mut riscv_rt::TrapFrame) { //! // ... //! } //! ``` //! //! Default implementation of this function stucks in a busy-loop. //! -//! //! ### Core interrupt handlers //! //! This functions are called when corresponding interrupt is occured. -//! You can define an interrupt handler with one of the following names: -//! * `SupervisorSoft` -//! * `MachineSoft` -//! * `SupervisorTimer` -//! * `MachineTimer` -//! * `SupervisorExternal` -//! * `MachineExternal` +//! You can define a core interrupt handler with the [`core_interrupt`] attribute. +//! The attribute expects the path to the interrupt source as an argument. +//! +//! The [`core_interrupt`] attribute ensures at compile time that there is a valid +//! core interrupt source for the given handler. //! //! For example: //! ``` no_run -//! #[export_name = "MachineTimer"] -//! fn custom_timer_handler() { -//! // ... +//! use riscv::interrupt::Interrupt; // or a target-specific core interrupt enum +//! +//! #[riscv_rt::core_interrupt(Interrupt::MachineSoft)] +//! unsafe fn custom_machine_soft_handler() { +//! todo!() //! } -//! ``` -//! or -//! ``` no_run -//! #[no_mangle] -//! fn MachineTimer() { -//! // ... +//! +//! #[riscv_rt::core_interrupt(Interrupt::MachineTimer)] +//! fn custom_machine_timer_handler() -> ! { +//! loop {} //! } //! ``` //! -//! You can also use the `#[interrupt]` macro to define interrupt handlers: +//! In vectored mode, this macro will also generate a proper trap handler for the interrupt. //! -//! ``` no_run -//! #[riscv_rt::interrupt] -//! fn MachineTimer() { -//! // ... -//! } -//! ``` +//! If interrupt handler is not explicitly defined, `DefaultHandler` is called. +//! +//! ### External interrupt handlers +//! +//! This functions are called when corresponding interrupt is occured. +//! You can define an external interrupt handler with the [`external_interrupt`] attribute. +//! The attribute expects the path to the interrupt source as an argument. //! -//! In direct mode, this macro is equivalent to defining a function with the same name. -//! However, in vectored mode, this macro will generate a proper trap handler for the interrupt. +//! The [`external_interrupt`] attribute ensures at compile time that there is a valid +//! external interrupt source for the given handler. +//! Note that external interrupts are target-specific and may not be available on all platforms. //! //! If interrupt handler is not explicitly defined, `DefaultHandler` is called. //! @@ -372,20 +361,22 @@ //! //! This function is called when interrupt without defined interrupt handler is occured. //! The interrupt reason can be decoded from the `mcause`/`scause` register. +//! If it is an external interrupt, the interrupt reason can be decoded from a +//! target-specific peripheral interrupt controller. //! //! This function can be redefined in the following way: //! //! ``` no_run //! #[export_name = "DefaultHandler"] -//! fn custom_interrupt_handler() { +//! unsafe fn custom_interrupt_handler() { //! // ... //! } //! ``` //! or //! ``` no_run //! #[no_mangle] -//! fn DefaultHandler() { -//! // ... +//! fn DefaultHandler() -> ! { +//! loop {} //! } //! ``` //! @@ -436,22 +427,7 @@ //! riscv-rt = {features=["v-trap"]} //! ``` //! When the vectored trap feature is enabled, the trap vector is set to `_vector_table` in vectored mode. -//! This table is a list of `j _start_INTERRUPT_trap` instructions, where `INTERRUPT` is the name of the interrupt. -//! -//! ### Defining interrupt handlers in vectored mode -//! -//! In vectored mode, each interrupt must also have a corresponding trap handler. -//! Therefore, using `export_name` or `no_mangle` is not enough to define an interrupt handler. -//! The [`interrupt`] macro will generate the trap handler for the interrupt: -//! -//! ``` no_run -//! #[riscv_rt::interrupt] -//! fn MachineTimer() { -//! // ... -//! } -//! ``` -//! -//! This will generate a function named `_start_MachineTimer_trap` that calls the interrupt handler `MachineTimer`. +//! This table is a list of `j _start_INTERRUPT_trap` instructions, where `INTERRUPT` is the name of the core interrupt. //! //! ## `u-boot` //! diff --git a/riscv/Cargo.toml b/riscv/Cargo.toml index 366dd390..35bc7fd8 100644 --- a/riscv/Cargo.toml +++ b/riscv/Cargo.toml @@ -29,7 +29,3 @@ critical-section = "1.1.2" embedded-hal = "1.0.0" riscv-pac = { path = "../riscv-pac", version = "0.2.0" } riscv-macros = { path = "macros", version = "0.1.0", optional = true } - -[dev-dependencies] -trybuild = "1.0" -riscv-rt = { path = "../riscv-rt", version = "0.13.0" } diff --git a/riscv/src/interrupt.rs b/riscv/src/interrupt.rs index 697d0345..93fda11c 100644 --- a/riscv/src/interrupt.rs +++ b/riscv/src/interrupt.rs @@ -15,7 +15,37 @@ pub use machine::*; #[cfg(feature = "s-mode")] pub use supervisor::*; -/// Trap Cause +/// Trap Cause. +/// +/// This enum represents the cause of a trap. It can be either an interrupt or an exception. +/// The [`mcause`](crate::register::mcause::Mcause::cause) and +/// [`scause`](crate::register::scause::Scause::cause) registers return a value of this type. +/// However, the trap cause is represented as raw numbers. To get a target-specific trap cause, +/// use [`Trap::try_into`] with your target-specific M-Mode or S-Mode trap cause types. +/// +/// # Example +/// +/// In targets that comply with the RISC-V standard, you can use the standard +/// [`Interrupt`] and [`Exception`] enums to represent the trap cause: +/// +/// ```no_run +/// use riscv::interrupt::{Trap, Interrupt, Exception}; +/// use riscv::register::mcause; +/// +/// let raw_trap: Trap = mcause::read().cause(); +/// let standard_trap: Trap = raw_trap.try_into().unwrap(); +/// ``` +/// +/// Targets that do not comply with the RISC-V standard usually have their own interrupt and exceptions. +/// You can find these types in the target-specific PAC. If it has been generated with `svd2rust`, +/// you can use the `pac::interrupt::CoreInterrupt` and `pac::interrupt::Exception` enums: +/// +/// ```ignore,no_run +/// use riscv::interrupt::Trap; +/// use pac::interrupt::{CoreInterrupt, Exception}; // pac is the target-specific PAC +/// +/// let standard_trap: Trap = pac::interrupt::cause(); +/// ``` #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum Trap { Interrupt(I), diff --git a/riscv/src/interrupt/supervisor.rs b/riscv/src/interrupt/supervisor.rs index ed25d236..ddb913c2 100644 --- a/riscv/src/interrupt/supervisor.rs +++ b/riscv/src/interrupt/supervisor.rs @@ -153,6 +153,7 @@ where } /// Execute closure `f` with interrupts enabled in the current hart (supervisor mode). +/// /// This method is assumed to be called within an interrupt handler, and allows /// nested interrupts to occur. After the closure `f` is executed, the [`sstatus`] /// and [`sepc`] registers are properly restored to their previous values. diff --git a/riscv/tests/test.rs b/riscv/tests/test.rs deleted file mode 100644 index c74b861b..00000000 --- a/riscv/tests/test.rs +++ /dev/null @@ -1,6 +0,0 @@ -#[test] -fn ui() { - let t = trybuild::TestCases::new(); - t.compile_fail("tests/ui/fail_*.rs"); - t.pass("tests/ui/pass_*.rs"); -} diff --git a/tests/Cargo.toml b/tests/Cargo.toml index 1dafa88a..d787b18b 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -5,5 +5,5 @@ edition = "2021" [dependencies] riscv = { path = "../riscv", version = "0.12.0" } -riscv-rt = { path = "../riscv-rt", version = "0.13.0" } +riscv-rt = { path = "../riscv-rt", version = "0.13.0", features = ["no-exceptions", "no-interrupts"]} trybuild = "1.0" diff --git a/riscv/tests/ui/fail_empty_macro.rs b/tests/tests/riscv/fail_empty_macro.rs similarity index 100% rename from riscv/tests/ui/fail_empty_macro.rs rename to tests/tests/riscv/fail_empty_macro.rs diff --git a/riscv/tests/ui/fail_empty_macro.stderr b/tests/tests/riscv/fail_empty_macro.stderr similarity index 85% rename from riscv/tests/ui/fail_empty_macro.stderr rename to tests/tests/riscv/fail_empty_macro.stderr index 28c594b7..d163ec43 100644 --- a/riscv/tests/ui/fail_empty_macro.stderr +++ b/tests/tests/riscv/fail_empty_macro.stderr @@ -1,5 +1,5 @@ error: unexpected end of input, expected `unsafe` - --> tests/ui/fail_empty_macro.rs:1:1 + --> tests/riscv/fail_empty_macro.rs:1:1 | 1 | #[riscv::pac_enum] | ^^^^^^^^^^^^^^^^^^ diff --git a/riscv/tests/ui/fail_no_unsafe.rs b/tests/tests/riscv/fail_no_unsafe.rs similarity index 100% rename from riscv/tests/ui/fail_no_unsafe.rs rename to tests/tests/riscv/fail_no_unsafe.rs diff --git a/riscv/tests/ui/fail_no_unsafe.stderr b/tests/tests/riscv/fail_no_unsafe.stderr similarity index 72% rename from riscv/tests/ui/fail_no_unsafe.stderr rename to tests/tests/riscv/fail_no_unsafe.stderr index 68fec6f8..cdb3789e 100644 --- a/riscv/tests/ui/fail_no_unsafe.stderr +++ b/tests/tests/riscv/fail_no_unsafe.stderr @@ -1,5 +1,5 @@ error: expected `unsafe` - --> tests/ui/fail_no_unsafe.rs:1:19 + --> tests/riscv/fail_no_unsafe.rs:1:19 | 1 | #[riscv::pac_enum(InterruptNumber)] | ^^^^^^^^^^^^^^^ diff --git a/riscv/tests/ui/fail_unknown_trait.rs b/tests/tests/riscv/fail_unknown_trait.rs similarity index 100% rename from riscv/tests/ui/fail_unknown_trait.rs rename to tests/tests/riscv/fail_unknown_trait.rs diff --git a/riscv/tests/ui/fail_unknown_trait.stderr b/tests/tests/riscv/fail_unknown_trait.stderr similarity index 84% rename from riscv/tests/ui/fail_unknown_trait.stderr rename to tests/tests/riscv/fail_unknown_trait.stderr index 337f8548..03fcad8b 100644 --- a/riscv/tests/ui/fail_unknown_trait.stderr +++ b/tests/tests/riscv/fail_unknown_trait.stderr @@ -1,5 +1,5 @@ error: Unknown trait name. Expected: 'ExceptionNumber', 'CoreInterruptNumber', 'ExternalInterruptNumber', 'PriorityNumber', or 'HartIdNumber' - --> tests/ui/fail_unknown_trait.rs:1:26 + --> tests/riscv/fail_unknown_trait.rs:1:26 | 1 | #[riscv::pac_enum(unsafe InterruptNumber)] | ^^^^^^^^^^^^^^^ diff --git a/riscv/tests/ui/pass_test.rs b/tests/tests/riscv/pass_test.rs similarity index 100% rename from riscv/tests/ui/pass_test.rs rename to tests/tests/riscv/pass_test.rs diff --git a/tests/tests/test.rs b/tests/tests/test.rs index 7506ddfc..b89b72cc 100644 --- a/tests/tests/test.rs +++ b/tests/tests/test.rs @@ -1,6 +1,15 @@ +#[test] +fn riscv() { + let t = trybuild::TestCases::new(); + + t.compile_fail("tests/riscv/fail_*.rs"); + t.pass("tests/riscv/pass_*.rs"); +} + #[test] fn riscv_rt() { let t = trybuild::TestCases::new(); + t.compile_fail("tests/riscv-rt/*/fail_*.rs"); t.pass("tests/riscv-rt/*/pass_*.rs"); } From 483a0129ac827c8f34f0f2d3c2ef831e42ce3a93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rom=C3=A1n=20C=C3=A1rdenas=20Rodr=C3=ADguez?= Date: Wed, 11 Sep 2024 09:35:10 +0200 Subject: [PATCH 13/14] fix feature gate bug on interrupt macros --- riscv-rt/macros/src/lib.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/riscv-rt/macros/src/lib.rs b/riscv-rt/macros/src/lib.rs index 62923e57..3cd75e1a 100644 --- a/riscv-rt/macros/src/lib.rs +++ b/riscv-rt/macros/src/lib.rs @@ -754,7 +754,13 @@ fn trap( let export_name = format!("{:#}", int_ident); let start_trap = match arch { - Some(arch) => start_interrupt_trap(int_ident, arch), + Some(arch) => { + let trap = start_interrupt_trap(int_ident, arch); + quote! { + #[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))] + #trap + } + } None => proc_macro2::TokenStream::new(), }; @@ -767,7 +773,6 @@ fn trap( assert_impl(#int_path); }; - #[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))] #start_trap #[export_name = #export_name] From fd77cf96fc09b7b2f482ebb2e45f93dc7cfda3bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rom=C3=A1n=20C=C3=A1rdenas=20Rodr=C3=ADguez?= Date: Wed, 16 Oct 2024 08:28:00 +0200 Subject: [PATCH 14/14] Apply clippy changes in riscv-semihosting --- riscv-semihosting/CHANGELOG.md | 1 + riscv-semihosting/README.md | 2 +- riscv-semihosting/src/export.rs | 70 +++++++++++++++++++-------------- 3 files changed, 42 insertions(+), 31 deletions(-) diff --git a/riscv-semihosting/CHANGELOG.md b/riscv-semihosting/CHANGELOG.md index c9d61fe6..80e008a3 100644 --- a/riscv-semihosting/CHANGELOG.md +++ b/riscv-semihosting/CHANGELOG.md @@ -7,6 +7,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Changed +- Apply clippy changes - Bump riscv dependency version - Made `cfg` variable selection more robust for custom targets - Fixed debug::exit() on riscv64 QEMU simulation diff --git a/riscv-semihosting/README.md b/riscv-semihosting/README.md index 0768aeba..56e89eb6 100644 --- a/riscv-semihosting/README.md +++ b/riscv-semihosting/README.md @@ -17,7 +17,7 @@ Given this, the generally sufficient for using this library. A major difference between this library and `cortex-m-semihosting` is that there -are mandatory features to choose the privilege level at which the semihosting +are features to choose the privilege level at which the semihosting calls are executed. The *machine-mode (M-mode)* feature will cause the macros in `export` to execute the semihosting operation in an interrupt-free context, while *user-mode (U-mode)* causes them to just execute the operation. diff --git a/riscv-semihosting/src/export.rs b/riscv-semihosting/src/export.rs index c56d6d48..3aefa178 100644 --- a/riscv-semihosting/src/export.rs +++ b/riscv-semihosting/src/export.rs @@ -1,7 +1,9 @@ //! IMPLEMENTATION DETAILS USED BY MACROS - use crate::hio::{self, HostStream}; -use core::fmt::{self, Write}; +use core::{ + fmt::{self, Write}, + ptr::addr_of_mut, +}; static mut HSTDOUT: Option = None; static mut HSTDERR: Option = None; @@ -11,42 +13,46 @@ mod machine { use super::*; pub fn hstdout_str(s: &str) { - let _result = critical_section::with(|_| unsafe { - if HSTDOUT.is_none() { - HSTDOUT = Some(hio::hstdout()?); + let _result = critical_section::with(|_| { + let hstdout = unsafe { &mut *addr_of_mut!(HSTDOUT) }; + if hstdout.is_none() { + *hstdout = Some(hio::hstdout()?); } - HSTDOUT.as_mut().unwrap().write_str(s).map_err(drop) + hstdout.as_mut().unwrap().write_str(s).map_err(drop) }); } pub fn hstdout_fmt(args: fmt::Arguments) { - let _result = critical_section::with(|_| unsafe { - if HSTDOUT.is_none() { - HSTDOUT = Some(hio::hstdout()?); + let _result = critical_section::with(|_| { + let hstdout = unsafe { &mut *addr_of_mut!(HSTDOUT) }; + if hstdout.is_none() { + *hstdout = Some(hio::hstdout()?); } - HSTDOUT.as_mut().unwrap().write_fmt(args).map_err(drop) + hstdout.as_mut().unwrap().write_fmt(args).map_err(drop) }); } pub fn hstderr_str(s: &str) { - let _result = critical_section::with(|_| unsafe { - if HSTDERR.is_none() { - HSTDERR = Some(hio::hstderr()?); + let _result = critical_section::with(|_| { + let hstderr = unsafe { &mut *addr_of_mut!(HSTDERR) }; + if hstderr.is_none() { + *hstderr = Some(hio::hstderr()?); } - HSTDERR.as_mut().unwrap().write_str(s).map_err(drop) + hstderr.as_mut().unwrap().write_str(s).map_err(drop) }); } pub fn hstderr_fmt(args: fmt::Arguments) { - let _result = critical_section::with(|_| unsafe { - if HSTDERR.is_none() { - HSTDERR = Some(hio::hstderr()?); + let _result = critical_section::with(|_| { + let hstderr = unsafe { &mut *addr_of_mut!(HSTDERR) }; + if hstderr.is_none() { + *hstderr = Some(hio::hstderr()?); } - HSTDERR.as_mut().unwrap().write_fmt(args).map_err(drop) + hstderr.as_mut().unwrap().write_fmt(args).map_err(drop) }); } } @@ -59,41 +65,45 @@ mod user { pub fn hstdout_str(s: &str) { let _result = unsafe { - if HSTDOUT.is_none() { - HSTDOUT = Some(hio::hstdout().unwrap()); + let hstdout = &mut *addr_of_mut!(HSTDOUT); + if hstdout.is_none() { + *hstdout = Some(hio::hstdout().unwrap()); } - HSTDOUT.as_mut().unwrap().write_str(s).map_err(drop) + hstdout.as_mut().unwrap().write_str(s).map_err(drop) }; } pub fn hstdout_fmt(args: fmt::Arguments) { let _result = unsafe { - if HSTDOUT.is_none() { - HSTDOUT = Some(hio::hstdout().unwrap()); + let hstdout = &mut *addr_of_mut!(HSTDOUT); + if hstdout.is_none() { + *hstdout = Some(hio::hstdout().unwrap()); } - HSTDOUT.as_mut().unwrap().write_fmt(args).map_err(drop) + hstdout.as_mut().unwrap().write_fmt(args).map_err(drop) }; } pub fn hstderr_str(s: &str) { let _result = unsafe { - if HSTDERR.is_none() { - HSTDERR = Some(hio::hstderr().unwrap()); + let hstderr = &mut *addr_of_mut!(HSTDERR); + if hstderr.is_none() { + *hstderr = Some(hio::hstderr().unwrap()); } - HSTDERR.as_mut().unwrap().write_str(s).map_err(drop) + hstderr.as_mut().unwrap().write_str(s).map_err(drop) }; } pub fn hstderr_fmt(args: fmt::Arguments) { let _result = unsafe { - if HSTDERR.is_none() { - HSTDERR = Some(hio::hstderr().unwrap()); + let hstderr = &mut *addr_of_mut!(HSTDERR); + if hstderr.is_none() { + *hstderr = Some(hio::hstderr().unwrap()); } - HSTDERR.as_mut().unwrap().write_fmt(args).map_err(drop) + hstderr.as_mut().unwrap().write_fmt(args).map_err(drop) }; } }