From dd7e35a2f6aefb0c8eb9ffc948bee5f02510915c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rom=C3=A1n=20C=C3=A1rdenas=20Rodr=C3=ADguez?= Date: Wed, 21 Feb 2024 14:18:00 +0100 Subject: [PATCH 01/17] new traits --- .github/workflows/riscv-pac.yaml | 60 ++++++ .github/workflows/riscv-peripheral.yaml | 14 +- .github/workflows/riscv.yaml | 16 +- riscv-pac/CHANGELOG.md | 3 + riscv-pac/Cargo.toml | 2 +- riscv-pac/src/lib.rs | 236 +++++++++++++++++++++++- riscv-peripheral/CHANGELOG.md | 4 + riscv-peripheral/Cargo.toml | 6 +- riscv-peripheral/examples/e310x.rs | 10 +- riscv-peripheral/src/plic.rs | 4 +- riscv-peripheral/src/plic/claim.rs | 8 +- riscv-peripheral/src/plic/enables.rs | 20 +- riscv-peripheral/src/plic/pendings.rs | 8 +- riscv-peripheral/src/plic/priorities.rs | 13 +- riscv-peripheral/src/plic/threshold.rs | 3 +- riscv/CHANGELOG.md | 6 + riscv/Cargo.toml | 29 --- riscv/src/register/mcause.rs | 146 ++++++++++++--- riscv/src/register/scause.rs | 128 +++++++++++-- 19 files changed, 591 insertions(+), 125 deletions(-) create mode 100644 .github/workflows/riscv-pac.yaml diff --git a/.github/workflows/riscv-pac.yaml b/.github/workflows/riscv-pac.yaml new file mode 100644 index 00000000..9ab4c5db --- /dev/null +++ b/.github/workflows/riscv-pac.yaml @@ -0,0 +1,60 @@ +on: + push: + branches: [ master, riscv-pac ] + pull_request: + merge_group: + +name: Build check (riscv-pac) + +jobs: + # We check that the crate builds and links for all the toolchains and targets. + build-riscv: + strategy: + matrix: + # All generated code should be running on stable now, MRSV is 1.60.0 + toolchain: [ stable, nightly, 1.60.0 ] + target: + - riscv32i-unknown-none-elf + - riscv32imc-unknown-none-elf + - riscv32imac-unknown-none-elf + - riscv64imac-unknown-none-elf + - riscv64gc-unknown-none-elf + include: + # Nightly is only for reference and allowed to fail + - toolchain: nightly + experimental: true + runs-on: ubuntu-latest + continue-on-error: ${{ matrix.experimental || false }} + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ matrix.toolchain }} + targets: ${{ matrix.target }} + - name: Build + run: cargo build --package riscv-pac --target ${{ matrix.target }} + + # On MacOS, Ubuntu, and Windows, we run the tests. + test-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: Run tests + run: cargo test --package riscv-pac + + # Job to check that all the builds succeeded + build-check: + needs: + - build-riscv + - test-others + runs-on: ubuntu-latest + if: always() + steps: + - run: jq --exit-status 'all(.result == "success")' <<< '${{ toJson(needs) }}' diff --git a/.github/workflows/riscv-peripheral.yaml b/.github/workflows/riscv-peripheral.yaml index 8797a8a4..ce2b45e6 100644 --- a/.github/workflows/riscv-peripheral.yaml +++ b/.github/workflows/riscv-peripheral.yaml @@ -1,6 +1,6 @@ on: push: - branches: [ master ] + branches: [ master, riscv-pac ] pull_request: merge_group: @@ -37,27 +37,27 @@ jobs: run: cargo build --package riscv-peripheral --target ${{ matrix.target }} --all-features # On MacOS, Ubuntu, and Windows, we run the tests. - build-others: + test-others: strategy: matrix: os: - macos-latest - ubuntu-latest - # - windows-latest issues when testing and external symbols are not found + # - windows-latest # issues when testing and external symbols are not found runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - - name: Build (no features) + - name: Run tests (no features) run: cargo test --package riscv-peripheral - - name: Build (all features) + - name: Run tests (all features) run: cargo test --package riscv-peripheral --all-features # Job to check that all the builds succeeded build-check: needs: - build-riscv - - build-others + - test-others runs-on: ubuntu-latest if: always() steps: diff --git a/.github/workflows/riscv.yaml b/.github/workflows/riscv.yaml index cd03b872..b52145a5 100644 --- a/.github/workflows/riscv.yaml +++ b/.github/workflows/riscv.yaml @@ -1,6 +1,6 @@ on: push: - branches: [ master ] + branches: [ master, riscv-pac ] pull_request: merge_group: @@ -41,24 +41,24 @@ jobs: run: cargo build --package riscv --target ${{ matrix.target }} --all-features # On MacOS, Ubuntu, and Windows, we at least make sure that the crate builds and links. - build-others: + test-others: strategy: matrix: os: [ macos-latest, ubuntu-latest, windows-latest ] runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - - name: Build (no features) - run: cargo build --package riscv - - name: Build (all features) - run: cargo build --package riscv --all-features + - name: Run tests (no features) + run: cargo test --package riscv + - name: Run tests (all features) + run: cargo test --package riscv --all-features # Job to check that all the builds succeeded build-check: needs: - build-riscv - - build-others + - test-others runs-on: ubuntu-latest if: always() steps: diff --git a/riscv-pac/CHANGELOG.md b/riscv-pac/CHANGELOG.md index 0303047f..4269f8a5 100644 --- a/riscv-pac/CHANGELOG.md +++ b/riscv-pac/CHANGELOG.md @@ -10,6 +10,9 @@ 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. ## [v0.1.1] - 2024-02-15 diff --git a/riscv-pac/Cargo.toml b/riscv-pac/Cargo.toml index b1cb2ae4..6eacfeaf 100644 --- a/riscv-pac/Cargo.toml +++ b/riscv-pac/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "riscv-pac" -version = "0.1.1" +version = "0.1.2" edition = "2021" rust-version = "1.60" repository = "https://github.com/rust-embedded/riscv" diff --git a/riscv-pac/src/lib.rs b/riscv-pac/src/lib.rs index 4da1170a..5fc0c6b2 100644 --- a/riscv-pac/src/lib.rs +++ b/riscv-pac/src/lib.rs @@ -4,16 +4,42 @@ 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 `u16` 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: u16; + + /// Converts an exception to its corresponding number. + fn number(self) -> u16; + + /// 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: u16) -> 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 `u16` 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`. @@ -30,6 +56,31 @@ pub unsafe trait InterruptNumber: Copy { fn from_number(value: u16) -> Result; } +/// 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 {} + +/// 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) @@ -81,3 +132,176 @@ 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::*; + + #[derive(Clone, Copy, Debug, Eq, PartialEq)] + #[repr(u16)] + enum Exception { + E1 = 1, + E3 = 3, + } + + #[derive(Clone, Copy, Debug, Eq, PartialEq)] + #[repr(u16)] + enum Interrupt { + I1 = 1, + I2 = 2, + I3 = 3, + I4 = 4, + } + + #[derive(Clone, Copy, Debug, Eq, PartialEq)] + #[repr(u8)] + enum Priority { + P0 = 0, + P1 = 1, + P2 = 2, + P3 = 3, + } + + #[derive(Clone, Copy, Debug, Eq, PartialEq)] + #[repr(u16)] + enum Context { + C0 = 0, + C1 = 1, + C2 = 2, + } + + unsafe impl ExceptionNumber for Exception { + const MAX_EXCEPTION_NUMBER: u16 = Self::E3 as u16; + + #[inline] + fn number(self) -> u16 { + self as _ + } + + #[inline] + fn from_number(number: u16) -> Result { + if number > Self::MAX_EXCEPTION_NUMBER || number == 0 { + Err(number) + } else if number == 1 || number == 3 { + // SAFETY: valid exception number + Ok(unsafe { core::mem::transmute(number) }) + } else { + Err(number) + } + } + } + + unsafe impl InterruptNumber for Interrupt { + const MAX_INTERRUPT_NUMBER: u16 = Self::I4 as u16; + + #[inline] + fn number(self) -> u16 { + self as _ + } + + #[inline] + fn from_number(number: u16) -> Result { + if number > Self::MAX_INTERRUPT_NUMBER || number == 0 { + Err(number) + } else { + // SAFETY: valid interrupt number + Ok(unsafe { core::mem::transmute(number) }) + } + } + } + + unsafe impl PriorityNumber for Priority { + const MAX_PRIORITY_NUMBER: u8 = Self::P3 as u8; + + #[inline] + fn number(self) -> u8 { + self as _ + } + + #[inline] + fn from_number(number: u8) -> Result { + if number > Self::MAX_PRIORITY_NUMBER { + Err(number) + } else { + // SAFETY: valid priority number + Ok(unsafe { core::mem::transmute(number) }) + } + } + } + + unsafe impl HartIdNumber for Context { + const MAX_HART_ID_NUMBER: u16 = Self::C2 as u16; + + #[inline] + fn number(self) -> u16 { + self as _ + } + + #[inline] + fn from_number(number: u16) -> Result { + if number > Self::MAX_HART_ID_NUMBER { + Err(number) + } else { + // SAFETY: valid context number + Ok(unsafe { core::mem::transmute(number) }) + } + } + } + + #[test] + fn check_exception_enum() { + 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(3), Ok(Exception::E3)); + + assert_eq!(Exception::from_number(0), Err(0)); + assert_eq!(Exception::from_number(2), Err(2)); + assert_eq!(Exception::from_number(4), Err(4)); + } + + #[test] + fn check_interrupt_enum() { + assert_eq!(Interrupt::I1.number(), 1); + assert_eq!(Interrupt::I2.number(), 2); + assert_eq!(Interrupt::I3.number(), 3); + assert_eq!(Interrupt::I4.number(), 4); + + assert_eq!(Interrupt::from_number(1), Ok(Interrupt::I1)); + assert_eq!(Interrupt::from_number(2), Ok(Interrupt::I2)); + assert_eq!(Interrupt::from_number(3), Ok(Interrupt::I3)); + assert_eq!(Interrupt::from_number(4), Ok(Interrupt::I4)); + + assert_eq!(Interrupt::from_number(0), Err(0)); + assert_eq!(Interrupt::from_number(5), Err(5)); + } + + #[test] + fn check_priority_enum() { + assert_eq!(Priority::P0.number(), 0); + assert_eq!(Priority::P1.number(), 1); + assert_eq!(Priority::P2.number(), 2); + assert_eq!(Priority::P3.number(), 3); + + assert_eq!(Priority::from_number(0), Ok(Priority::P0)); + assert_eq!(Priority::from_number(1), Ok(Priority::P1)); + assert_eq!(Priority::from_number(2), Ok(Priority::P2)); + assert_eq!(Priority::from_number(3), Ok(Priority::P3)); + + assert_eq!(Priority::from_number(4), Err(4)); + } + + #[test] + fn check_context_enum() { + assert_eq!(Context::C0.number(), 0); + assert_eq!(Context::C1.number(), 1); + assert_eq!(Context::C2.number(), 2); + + assert_eq!(Context::from_number(0), Ok(Context::C0)); + assert_eq!(Context::from_number(1), Ok(Context::C1)); + assert_eq!(Context::from_number(2), Ok(Context::C2)); + + assert_eq!(Context::from_number(3), Err(3)); + } +} diff --git a/riscv-peripheral/CHANGELOG.md b/riscv-peripheral/CHANGELOG.md index 3feb9601..8f43411f 100644 --- a/riscv-peripheral/CHANGELOG.md +++ b/riscv-peripheral/CHANGELOG.md @@ -15,6 +15,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - `clippy` fixes +### Changed + +- `PLIC` now expects interrupt enums to implement the `riscv_pac::ExternalInterruptNumber` trait. + ## [v0.1.0] - 2024-02-15 ### Added diff --git a/riscv-peripheral/Cargo.toml b/riscv-peripheral/Cargo.toml index e54efa9f..4655d9fa 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" @@ -16,8 +16,8 @@ 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-pac = { path = "../riscv-pac", version = "0.1.1" } +riscv = { path = "../riscv", version = "0.11.2" } +riscv-pac = { path = "../riscv-pac", version = "0.1.2" } [dev-dependencies] heapless = "0.8.0" diff --git a/riscv-peripheral/examples/e310x.rs b/riscv-peripheral/examples/e310x.rs index 6f608b6c..b092861b 100644 --- a/riscv-peripheral/examples/e310x.rs +++ b/riscv-peripheral/examples/e310x.rs @@ -3,7 +3,7 @@ //! peripheral definitions for a target. use riscv_pac::result::{Error, Result}; -use riscv_pac::{HartIdNumber, InterruptNumber, PriorityNumber}; +use riscv_pac::{ExternalInterruptNumber, HartIdNumber, InterruptNumber, PriorityNumber}; #[repr(u16)] #[derive(Clone, Copy, Debug, Eq, PartialEq)] @@ -12,7 +12,7 @@ pub enum HartId { } 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 { @@ -88,7 +88,7 @@ pub enum Interrupt { } unsafe impl InterruptNumber for Interrupt { - const MAX_INTERRUPT_NUMBER: u16 = 52; + const MAX_INTERRUPT_NUMBER: u16 = Self::I2C0 as u16; #[inline] fn number(self) -> u16 { @@ -106,6 +106,8 @@ unsafe impl InterruptNumber for Interrupt { } } +unsafe impl ExternalInterruptNumber for Interrupt {} + #[derive(Copy, Clone, Debug, PartialEq, Eq)] #[repr(u8)] pub enum Priority { @@ -120,7 +122,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/plic.rs b/riscv-peripheral/src/plic.rs index 116689a6..16952222 100644 --- a/riscv-peripheral/src/plic.rs +++ b/riscv-peripheral/src/plic.rs @@ -145,8 +145,8 @@ 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)] @@ -193,6 +193,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..7dfe6140 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,7 +29,7 @@ impl ENABLES { /// Checks if an interrupt source is enabled for the PLIC context. #[inline] - pub fn is_enabled(self, source: I) -> bool { + pub fn is_enabled(self, source: I) -> bool { let source = source.number() as usize; let offset = (source / u32::BITS as usize) as _; // SAFETY: valid interrupt number @@ -49,7 +47,7 @@ impl ENABLES { /// /// * Enabling an interrupt source can break mask-based critical sections. #[inline] - pub unsafe fn enable(self, source: I) { + pub unsafe fn enable(self, source: I) { let source = source.number() as usize; let offset = (source / u32::BITS as usize) as _; // SAFETY: valid interrupt number @@ -70,7 +68,7 @@ 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, @@ -88,7 +86,7 @@ impl ENABLES { /// /// It performs non-atomic read-modify-write operations, which may lead to **wrong** behavior. #[inline] - pub fn disable(self, source: I) { + pub fn disable(self, source: I) { let source = source.number() as usize; let offset = (source / u32::BITS as usize) as _; // SAFETY: valid interrupt number @@ -108,7 +106,7 @@ 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, @@ -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..c166ec86 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,7 +29,7 @@ impl PENDINGS { /// Checks if an interrupt triggered by a given source is pending. #[inline] - pub fn is_pending(self, source: I) -> bool { + pub fn is_pending(self, source: I) -> bool { let source = source.number() as usize; let offset = (source / u32::BITS as usize) as _; // SAFETY: valid interrupt number diff --git a/riscv-peripheral/src/plic/priorities.rs b/riscv-peripheral/src/plic/priorities.rs index a9726838..7662831f 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,7 +29,7 @@ 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 _)) }; P::from_number(reg.read() as _).unwrap() @@ -43,7 +41,7 @@ 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, @@ -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() { diff --git a/riscv-peripheral/src/plic/threshold.rs b/riscv-peripheral/src/plic/threshold.rs index 936a13b1..992667cd 100644 --- a/riscv-peripheral/src/plic/threshold.rs +++ b/riscv-peripheral/src/plic/threshold.rs @@ -1,6 +1,7 @@ //! Priority threshold register. -use crate::{common::unsafe_peripheral, plic::PriorityNumber}; +use crate::common::unsafe_peripheral; +use riscv_pac::PriorityNumber; unsafe_peripheral!(THRESHOLD, u32, RW); diff --git a/riscv/CHANGELOG.md b/riscv/CHANGELOG.md index 7b2aa2fb..b47ff4ad 100644 --- a/riscv/CHANGELOG.md +++ b/riscv/CHANGELOG.md @@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Added +- 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()` @@ -21,6 +23,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Add `Mstatus` vector extension support - Add fallible counterparts to all functions that `panic` +### Changed + +- More efficient implementation of `From` traits for `Interrupt` and `Exception` enums. + ### Fixed - Fixed `sip::set_ssoft` and `sip::clear_ssoft` using wrong address diff --git a/riscv/Cargo.toml b/riscv/Cargo.toml index a783bb1c..e69de29b 100644 --- a/riscv/Cargo.toml +++ b/riscv/Cargo.toml @@ -1,29 +0,0 @@ -[package] -name = "riscv" -version = "0.11.1" -edition = "2021" -rust-version = "1.60" -repository = "https://github.com/rust-embedded/riscv" -authors = ["The RISC-V Team "] -categories = ["embedded", "hardware-support", "no-std"] -description = "Low level access to RISC-V processors" -documentation = "https://docs.rs/riscv" -keywords = ["riscv", "register", "peripheral"] -license = "ISC" - -[package.metadata.docs.rs] -all-features = true -default-target = "riscv64imac-unknown-none-elf" -targets = [ - "riscv32i-unknown-none-elf", "riscv32imc-unknown-none-elf", "riscv32imac-unknown-none-elf", - "riscv64imac-unknown-none-elf", "riscv64gc-unknown-none-elf", -] - -[features] -s-mode = [] -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 } diff --git a/riscv/src/register/mcause.rs b/riscv/src/register/mcause.rs index ff7f730b..fa26608b 100644 --- a/riscv/src/register/mcause.rs +++ b/riscv/src/register/mcause.rs @@ -1,5 +1,8 @@ //! mcause register +use riscv_pac::CoreInterruptNumber; +pub use riscv_pac::{ExceptionNumber, InterruptNumber}; // re-export useful riscv-pac traits + /// mcause register #[derive(Clone, Copy, Debug)] pub struct Mcause { @@ -57,14 +60,11 @@ pub enum Exception { 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, + if nr > 11 || nr % 2 == 0 { + Self::Unknown + } else { + // SAFETY: valid interrupt number + unsafe { core::mem::transmute(nr) } } } } @@ -81,25 +81,35 @@ impl TryFrom for usize { } } +/// SAFETY: `Interrupt` represents the standard RISC-V interrupts +unsafe impl InterruptNumber for Interrupt { + const MAX_INTERRUPT_NUMBER: u16 = Self::MachineExternal as u16; + + #[inline] + fn number(self) -> u16 { + self as u16 + } + + #[inline] + fn from_number(value: u16) -> Result { + match (value as usize).into() { + Self::Unknown => Err(value), + value => Ok(value), + } + } +} + +/// SAFETY: `Interrupt` represents the standard RISC-V core interrupts +unsafe impl CoreInterruptNumber for Interrupt {} + 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, + if nr == 10 || nr == 14 || nr > 15 { + Self::Unknown + } else { + // SAFETY: valid exception number + unsafe { core::mem::transmute(nr) } } } } @@ -116,6 +126,24 @@ impl TryFrom for usize { } } +/// SAFETY: `Exception` represents the standard RISC-V exceptions +unsafe impl ExceptionNumber for Exception { + const MAX_EXCEPTION_NUMBER: u16 = Self::StorePageFault as u16; + + #[inline] + fn number(self) -> u16 { + self as u16 + } + + #[inline] + fn from_number(value: u16) -> Result { + match (value as usize).into() { + Self::Unknown => Err(value), + value => Ok(value), + } + } +} + impl Mcause { /// Returns the contents of the register as raw bits #[inline] @@ -153,3 +181,73 @@ impl Mcause { } read_csr_as!(Mcause, 0x342); + +#[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(2)); + assert_eq!(Interrupt::from_number(3), Ok(MachineSoft)); + assert_eq!(Interrupt::from_number(4), Err(4)); + assert_eq!(Interrupt::from_number(5), Ok(SupervisorTimer)); + assert_eq!(Interrupt::from_number(6), Err(6)); + assert_eq!(Interrupt::from_number(7), Ok(MachineTimer)); + assert_eq!(Interrupt::from_number(8), Err(8)); + assert_eq!(Interrupt::from_number(9), Ok(SupervisorExternal)); + assert_eq!(Interrupt::from_number(10), Err(10)); + assert_eq!(Interrupt::from_number(11), Ok(MachineExternal)); + assert_eq!(Interrupt::from_number(12), Err(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(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(14)); + assert_eq!(Exception::from_number(15), Ok(StorePageFault)); + assert_eq!(Exception::from_number(16), Err(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/register/scause.rs b/riscv/src/register/scause.rs index 79fa1b9e..250437be 100644 --- a/riscv/src/register/scause.rs +++ b/riscv/src/register/scause.rs @@ -1,5 +1,8 @@ //! scause register +use riscv_pac::CoreInterruptNumber; +pub use riscv_pac::{ExceptionNumber, InterruptNumber}; // re-export useful riscv-pac traits + /// scause register #[derive(Clone, Copy)] pub struct Scause { @@ -67,24 +70,35 @@ impl TryFrom for usize { } } +/// SAFETY: `Interrupt` represents the standard RISC-V interrupts +unsafe impl InterruptNumber for Interrupt { + const MAX_INTERRUPT_NUMBER: u16 = Self::SupervisorExternal as u16; + + #[inline] + fn number(self) -> u16 { + self as u16 + } + + #[inline] + fn from_number(value: u16) -> Result { + match (value as usize).into() { + Self::Unknown => Err(value), + value => Ok(value), + } + } +} + +/// SAFETY: `Interrupt` represents the standard RISC-V core interrupts +unsafe impl CoreInterruptNumber for Interrupt {} + 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, + if nr == 10 || nr == 11 || nr == 14 || nr > 15 { + Self::Unknown + } else { + // SAFETY: valid exception number + unsafe { core::mem::transmute(nr) } } } } @@ -101,6 +115,24 @@ impl TryFrom for usize { } } +/// SAFETY: `Exception` represents the standard RISC-V exceptions +unsafe impl ExceptionNumber for Exception { + const MAX_EXCEPTION_NUMBER: u16 = Self::StorePageFault as u16; + + #[inline] + fn number(self) -> u16 { + self as u16 + } + + #[inline] + fn from_number(value: u16) -> Result { + match (value as usize).into() { + Self::Unknown => Err(value), + value => Ok(value), + } + } +} + impl Scause { /// Returns the contents of the register as raw bits #[inline] @@ -158,3 +190,69 @@ pub unsafe fn set(cause: Trap) { }; _write(bits); } + +#[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(2)); + assert_eq!(Interrupt::from_number(3), Err(3)); + assert_eq!(Interrupt::from_number(4), Err(4)); + assert_eq!(Interrupt::from_number(5), Ok(SupervisorTimer)); + assert_eq!(Interrupt::from_number(6), Err(6)); + assert_eq!(Interrupt::from_number(7), Err(7)); + assert_eq!(Interrupt::from_number(8), Err(8)); + assert_eq!(Interrupt::from_number(9), Ok(SupervisorExternal)); + assert_eq!(Interrupt::from_number(10), Err(10)); + assert_eq!(Interrupt::from_number(11), Err(11)); + assert_eq!(Interrupt::from_number(12), Err(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(10)); + assert_eq!(Exception::from_number(11), Err(11)); + assert_eq!(Exception::from_number(12), Ok(InstructionPageFault)); + assert_eq!(Exception::from_number(13), Ok(LoadPageFault)); + assert_eq!(Exception::from_number(14), Err(14)); + assert_eq!(Exception::from_number(15), Ok(StorePageFault)); + assert_eq!(Exception::from_number(16), Err(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) + } +} From 4056e2fe89e6352825a589f25f248e9547c22f67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rom=C3=A1n=20C=C3=A1rdenas=20Rodr=C3=ADguez?= Date: Thu, 22 Feb 2024 12:20:36 +0100 Subject: [PATCH 02/17] Macros work --- riscv-pac/Cargo.toml | 3 + riscv-pac/macros/Cargo.toml | 20 +++++ riscv-pac/macros/src/lib.rs | 159 ++++++++++++++++++++++++++++++++++++ riscv-pac/src/lib.rs | 42 +++++----- 4 files changed, 201 insertions(+), 23 deletions(-) create mode 100644 riscv-pac/macros/Cargo.toml create mode 100644 riscv-pac/macros/src/lib.rs diff --git a/riscv-pac/Cargo.toml b/riscv-pac/Cargo.toml index 6eacfeaf..44fdeb37 100644 --- a/riscv-pac/Cargo.toml +++ b/riscv-pac/Cargo.toml @@ -17,3 +17,6 @@ targets = [ "riscv32i-unknown-none-elf", "riscv32imc-unknown-none-elf", "riscv32imac-unknown-none-elf", "riscv64imac-unknown-none-elf", "riscv64gc-unknown-none-elf", ] + +[dependencies] +riscv-pac-macros = { path = "macros", version = "0.1.0" } diff --git a/riscv-pac/macros/Cargo.toml b/riscv-pac/macros/Cargo.toml new file mode 100644 index 00000000..f3228fb6 --- /dev/null +++ b/riscv-pac/macros/Cargo.toml @@ -0,0 +1,20 @@ +[package] +authors = [ + "The RISC-V Team ", +] +categories = ["embedded", "no-std"] +description = "Derive macros re-exported in `riscv-pac`" +documentation = "https://docs.rs/riscv-rt" +keywords = ["riscv", "register", "peripheral"] +license = "MIT OR Apache-2.0" +name = "riscv-pac-macros" +repository = "https://github.com/rust-embedded/riscv" +version = "0.1.0" + +[lib] +proc-macro = true + +[dependencies] +proc-macro2 = "1.0" +quote = "1.0" +syn = { version = "2.0", default-features = false, features = ["derive", "parsing", "proc-macro"]} diff --git a/riscv-pac/macros/src/lib.rs b/riscv-pac/macros/src/lib.rs new file mode 100644 index 00000000..524e6ad9 --- /dev/null +++ b/riscv-pac/macros/src/lib.rs @@ -0,0 +1,159 @@ +extern crate proc_macro; +extern crate proc_macro2; +extern crate quote; +extern crate syn; + +use proc_macro::TokenStream; +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; +use std::{collections::HashMap, convert::TryFrom, ops::Range, str::FromStr}; +use syn::{parse_macro_input, Data, DeriveInput, Error, Ident}; + +struct PacNumberEnum { + name: Ident, + valid_ranges: Vec>, +} + +impl PacNumberEnum { + fn valid_condition(&self) -> TokenStream2 { + let mut arms = Vec::new(); + for range in &self.valid_ranges { + let (start, end) = (range.start, range.end); + if end - start == 1 { + arms.push(TokenStream2::from_str(&format!("number == {start}")).unwrap()); + } else { + arms.push( + TokenStream2::from_str(&format!("({start}..{end}).contains(&number)")).unwrap(), + ); + } + } + quote! { #(#arms) || * } + } + + fn max_discriminant(&self) -> TokenStream2 { + let max_discriminant = self.valid_ranges.last().expect("invalid range").end - 1; + TokenStream2::from_str(&format!("{max_discriminant}")).unwrap() + } + + fn quote(&self, trait_name: &str, num_type: &str, const_name: &str) -> TokenStream2 { + let name = &self.name; + let max_discriminant = self.max_discriminant(); + let valid_condition = self.valid_condition(); + + let trait_name = TokenStream2::from_str(trait_name).unwrap(); + let num_type = TokenStream2::from_str(num_type).unwrap(); + let const_name = TokenStream2::from_str(const_name).unwrap(); + + quote! { + unsafe impl #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) -> Result { + if #valid_condition { + // SAFETY: The number is valid for this enum + Ok(unsafe { core::mem::transmute(number) }) + } else { + Err(number) + } + } + } + } + } +} + +impl TryFrom for PacNumberEnum { + type Error = Error; + + fn try_from(input: DeriveInput) -> Result { + let variants = match &input.data { + Data::Enum(data) => &data.variants, + _ => panic!("Input is not an enum"), + }; + + // Collect the variants and their associated number discriminants + let mut var_map = HashMap::new(); + let mut numbers = Vec::new(); + for variant in variants { + let ident = &variant.ident; + let value = match &variant.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"), + }; + // check for duplicate discriminant values + var_map.insert(value, ident); + numbers.push(value); + } + + // sort the number discriminants and generate a list of valid ranges + numbers.sort_unstable(); + let mut valid_ranges = Vec::new(); + let mut start = numbers[0]; + let mut end = start; + for &number in &numbers[1..] { + if number == end + 1 { + end = number; + } else { + valid_ranges.push(start..end + 1); + start = number; + end = start; + } + } + valid_ranges.push(start..end + 1); + + Ok(PacNumberEnum { + name: input.ident.clone(), + valid_ranges, + }) + } +} + +#[proc_macro_derive(ExceptionNumber)] +pub fn exception_number_derive(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + let pac_enum = PacNumberEnum::try_from(input).unwrap(); + pac_enum + .quote("ExceptionNumber", "u16", "MAX_EXCEPTION_NUMBER") + .into() +} + +#[proc_macro_derive(InterruptNumber)] +pub fn interrupt_number_derive(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + let pac_enum = PacNumberEnum::try_from(input).unwrap(); + pac_enum + .quote("InterruptNumber", "u16", "MAX_INTERRUPT_NUMBER") + .into() +} + +#[proc_macro_derive(PriorityNumber)] +pub fn priority_number_derive(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + let pac_enum = PacNumberEnum::try_from(input).unwrap(); + pac_enum + .quote("PriorityNumber", "u8", "MAX_PRIORITY_NUMBER") + .into() +} + +#[proc_macro_derive(HartIdNumber)] +pub fn hart_id_number_derive(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + let pac_enum = PacNumberEnum::try_from(input).unwrap(); + pac_enum + .quote("HartIdNumber", "u16", "MAX_HART_ID_NUMBER") + .into() +} diff --git a/riscv-pac/src/lib.rs b/riscv-pac/src/lib.rs index 5fc0c6b2..1a91ffc2 100644 --- a/riscv-pac/src/lib.rs +++ b/riscv-pac/src/lib.rs @@ -4,6 +4,8 @@ pub mod result; use result::Result; +pub use riscv_pac_macros::*; + /// Trait for enums of target-specific exception numbers. /// /// This trait should be implemented by a peripheral access crate (PAC) on its enum of available @@ -137,23 +139,22 @@ pub unsafe trait HartIdNumber: Copy { mod test { use super::*; - #[derive(Clone, Copy, Debug, Eq, PartialEq)] + #[derive(Clone, Copy, Debug, Eq, PartialEq, ExceptionNumber)] #[repr(u16)] enum Exception { E1 = 1, E3 = 3, } - #[derive(Clone, Copy, Debug, Eq, PartialEq)] + #[derive(Clone, Copy, Debug, Eq, PartialEq, InterruptNumber)] #[repr(u16)] enum Interrupt { I1 = 1, I2 = 2, - I3 = 3, I4 = 4, } - #[derive(Clone, Copy, Debug, Eq, PartialEq)] + #[derive(Clone, Copy, Debug, Eq, PartialEq, PriorityNumber)] #[repr(u8)] enum Priority { P0 = 0, @@ -162,7 +163,7 @@ mod test { P3 = 3, } - #[derive(Clone, Copy, Debug, Eq, PartialEq)] + #[derive(Clone, Copy, Debug, Eq, PartialEq, HartIdNumber)] #[repr(u16)] enum Context { C0 = 0, @@ -253,11 +254,10 @@ mod test { 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(3), Ok(Exception::E3)); - assert_eq!(Exception::from_number(0), Err(0)); + 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::from_number(4), Err(4)); } @@ -265,15 +265,13 @@ mod test { fn check_interrupt_enum() { assert_eq!(Interrupt::I1.number(), 1); assert_eq!(Interrupt::I2.number(), 2); - assert_eq!(Interrupt::I3.number(), 3); assert_eq!(Interrupt::I4.number(), 4); + assert_eq!(Interrupt::from_number(0), Err(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), Ok(Interrupt::I3)); + assert_eq!(Interrupt::from_number(3), Err(3)); assert_eq!(Interrupt::from_number(4), Ok(Interrupt::I4)); - - assert_eq!(Interrupt::from_number(0), Err(0)); assert_eq!(Interrupt::from_number(5), Err(5)); } @@ -288,20 +286,18 @@ mod test { assert_eq!(Priority::from_number(1), Ok(Priority::P1)); assert_eq!(Priority::from_number(2), Ok(Priority::P2)); assert_eq!(Priority::from_number(3), Ok(Priority::P3)); - assert_eq!(Priority::from_number(4), Err(4)); } #[test] - fn check_context_enum() { - assert_eq!(Context::C0.number(), 0); - assert_eq!(Context::C1.number(), 1); - assert_eq!(Context::C2.number(), 2); - - assert_eq!(Context::from_number(0), Ok(Context::C0)); - assert_eq!(Context::from_number(1), Ok(Context::C1)); - assert_eq!(Context::from_number(2), Ok(Context::C2)); - - assert_eq!(Context::from_number(3), Err(3)); + fn check_hart_id_enum() { + assert_eq!(HartId::H0.number(), 0); + assert_eq!(HartId::H1.number(), 1); + assert_eq!(HartId::H2.number(), 2); + + assert_eq!(HartId::from_number(0), Ok(HartId::H0)); + assert_eq!(HartId::from_number(1), Ok(HartId::H1)); + assert_eq!(HartId::from_number(2), Ok(HartId::H2)); + assert_eq!(HartId::from_number(3), Err(3)); } } From 43dc47fc822b4627208bc4da5db317c8726da296 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rom=C3=A1n=20C=C3=A1rdenas=20Rodr=C3=ADguez?= Date: Thu, 22 Feb 2024 16:56:32 +0100 Subject: [PATCH 03/17] Macro to implement riscv-pac traits --- riscv-pac/Cargo.toml | 8 +- riscv-pac/macros/Cargo.toml | 2 +- riscv-pac/macros/src/lib.rs | 226 +++++++++++------- riscv-pac/src/lib.rs | 26 +- riscv-pac/tests/test.rs | 6 + riscv-pac/tests/ui/fail_empty_macro.rs | 9 + riscv-pac/tests/ui/fail_empty_macro.stderr | 7 + .../tests/ui/fail_more_than_one_trait.rs | 9 + .../tests/ui/fail_more_than_one_trait.stderr | 7 + riscv-pac/tests/ui/fail_no_unsafe.rs | 9 + riscv-pac/tests/ui/fail_no_unsafe.stderr | 7 + riscv-pac/tests/ui/fail_unknown_trait.rs | 9 + riscv-pac/tests/ui/fail_unknown_trait.stderr | 7 + riscv-pac/tests/ui/fail_wrong_repr.rs | 9 + riscv-pac/tests/ui/fail_wrong_repr.stderr | 9 + riscv-pac/tests/ui/pass_test.rs | 83 +++++++ riscv-peripheral/Cargo.toml | 3 +- riscv-peripheral/src/plic.rs | 39 +-- riscv/Cargo.toml | 29 +++ 19 files changed, 379 insertions(+), 125 deletions(-) create mode 100644 riscv-pac/tests/test.rs create mode 100644 riscv-pac/tests/ui/fail_empty_macro.rs create mode 100644 riscv-pac/tests/ui/fail_empty_macro.stderr create mode 100644 riscv-pac/tests/ui/fail_more_than_one_trait.rs create mode 100644 riscv-pac/tests/ui/fail_more_than_one_trait.stderr create mode 100644 riscv-pac/tests/ui/fail_no_unsafe.rs create mode 100644 riscv-pac/tests/ui/fail_no_unsafe.stderr create mode 100644 riscv-pac/tests/ui/fail_unknown_trait.rs create mode 100644 riscv-pac/tests/ui/fail_unknown_trait.stderr create mode 100644 riscv-pac/tests/ui/fail_wrong_repr.rs create mode 100644 riscv-pac/tests/ui/fail_wrong_repr.stderr create mode 100644 riscv-pac/tests/ui/pass_test.rs diff --git a/riscv-pac/Cargo.toml b/riscv-pac/Cargo.toml index 44fdeb37..22d247ef 100644 --- a/riscv-pac/Cargo.toml +++ b/riscv-pac/Cargo.toml @@ -19,4 +19,10 @@ targets = [ ] [dependencies] -riscv-pac-macros = { path = "macros", version = "0.1.0" } +riscv-pac-macros = { path = "macros", version = "0.1.0", optional = true } + +[features] +default = ["riscv-pac-macros"] + +[dev-dependencies] +trybuild = "1.0" diff --git a/riscv-pac/macros/Cargo.toml b/riscv-pac/macros/Cargo.toml index f3228fb6..233a6616 100644 --- a/riscv-pac/macros/Cargo.toml +++ b/riscv-pac/macros/Cargo.toml @@ -17,4 +17,4 @@ proc-macro = true [dependencies] proc-macro2 = "1.0" quote = "1.0" -syn = { version = "2.0", default-features = false, features = ["derive", "parsing", "proc-macro"]} +syn = { version = "2.0" } diff --git a/riscv-pac/macros/src/lib.rs b/riscv-pac/macros/src/lib.rs index 524e6ad9..584656e5 100644 --- a/riscv-pac/macros/src/lib.rs +++ b/riscv-pac/macros/src/lib.rs @@ -6,8 +6,8 @@ extern crate syn; use proc_macro::TokenStream; use proc_macro2::TokenStream as TokenStream2; use quote::quote; -use std::{collections::HashMap, convert::TryFrom, ops::Range, str::FromStr}; -use syn::{parse_macro_input, Data, DeriveInput, Error, Ident}; +use std::{collections::HashMap, ops::Range, str::FromStr}; +use syn::{parse_macro_input, Data, DeriveInput, Ident}; struct PacNumberEnum { name: Ident, @@ -15,6 +15,57 @@ struct PacNumberEnum { } impl PacNumberEnum { + fn new(input: &DeriveInput) -> Self { + let variants = match &input.data { + Data::Enum(data) => &data.variants, + _ => panic!("Input is not an enum"), + }; + + // Collect the variants and their associated number discriminants + let mut var_map = HashMap::new(); + let mut numbers = Vec::new(); + for variant in variants { + let ident = &variant.ident; + let value = match &variant.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"), + }; + // check for duplicate discriminant values + var_map.insert(value, ident); + numbers.push(value); + } + + // sort the number discriminants and generate a list of valid ranges + numbers.sort_unstable(); + let mut valid_ranges = Vec::new(); + let mut start = numbers[0]; + let mut end = start; + for &number in &numbers[1..] { + if number == end + 1 { + end = number; + } else { + valid_ranges.push(start..end + 1); + start = number; + end = start; + } + } + valid_ranges.push(start..end + 1); + + Self { + name: input.ident.clone(), + valid_ranges, + } + } + fn valid_condition(&self) -> TokenStream2 { let mut arms = Vec::new(); for range in &self.valid_ranges { @@ -45,7 +96,7 @@ impl PacNumberEnum { let const_name = TokenStream2::from_str(const_name).unwrap(); quote! { - unsafe impl #trait_name for #name { + unsafe impl riscv_pac::#trait_name for #name { const #const_name: #num_type = #max_discriminant; #[inline] @@ -67,93 +118,88 @@ impl PacNumberEnum { } } -impl TryFrom for PacNumberEnum { - type Error = Error; - - fn try_from(input: DeriveInput) -> Result { - let variants = match &input.data { - Data::Enum(data) => &data.variants, - _ => panic!("Input is not an enum"), - }; - - // Collect the variants and their associated number discriminants - let mut var_map = HashMap::new(); - let mut numbers = Vec::new(); - for variant in variants { - let ident = &variant.ident; - let value = match &variant.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"), - }; - // check for duplicate discriminant values - var_map.insert(value, ident); - numbers.push(value); - } - - // sort the number discriminants and generate a list of valid ranges - numbers.sort_unstable(); - let mut valid_ranges = Vec::new(); - let mut start = numbers[0]; - let mut end = start; - for &number in &numbers[1..] { - if number == end + 1 { - end = number; - } else { - valid_ranges.push(start..end + 1); - start = number; - end = start; - } - } - valid_ranges.push(start..end + 1); - - Ok(PacNumberEnum { - name: input.ident.clone(), - valid_ranges, - }) +/// 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. +/// +/// # Note +/// +/// To implement number-to-enum operation, the macro works with ranges of valid discriminant numbers. +/// If the number is within any of the valid ranges, the number is transmuted to the enum variant. +/// In this way, the macro achieves better performance for enums with a large number of consecutive variants. +/// Thus, the enum must comply with the following requirements: +/// +/// - All the enum variants must have a valid discriminant number (i.e., a number that is within the valid range of the enum). +/// - For the `ExceptionNumber`, `InterruptNumber`, and `HartIdNumber` traits, the enum must be annotated as `#[repr(u16)]` +/// - For the `PriorityNumber` trait, the enum must be annotated as `#[repr(u8)]` +/// +/// If the enum does not meet these requirements, you will have to implement the traits manually (e.g., `riscv::mcause::Interrupt`). +/// For enums with a small number of consecutive variants, it might be better to implement the traits manually. +/// +/// # Safety +/// +/// The struct to be implemented must comply with the requirements of the specified trait. +/// +/// # Example +/// +/// ```rust +/// use riscv_pac::*; +/// +/// #[repr(u16)] +/// #[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 = PacNumberEnum::new(&input); + + // attr should be unsafe ExceptionNumber, unsafe InterruptNumber, unsafe PriorityNumber, or unsafe HartIdNumber + // assert that attribute starts with the unsafe token. If not, raise a panic error + let attr = attr.to_string(); + // split string into words and check if the first word is "unsafe" + let attrs = attr.split_whitespace().collect::>(); + if attrs.is_empty() { + panic!("Attribute is empty. Expected: 'riscv_pac::pac_enum(unsafe )'"); + } + if attrs.len() > 2 { + panic!( + "Wrong attribute format. Expected: 'riscv_pac::pac_enum(unsafe )'" + ); + } + if attrs[0] != "unsafe" { + panic!("Attribute does not start with 'unsafe'. Expected: 'riscv_pac::pac_enum(unsafe )'"); } -} - -#[proc_macro_derive(ExceptionNumber)] -pub fn exception_number_derive(input: TokenStream) -> TokenStream { - let input = parse_macro_input!(input as DeriveInput); - let pac_enum = PacNumberEnum::try_from(input).unwrap(); - pac_enum - .quote("ExceptionNumber", "u16", "MAX_EXCEPTION_NUMBER") - .into() -} - -#[proc_macro_derive(InterruptNumber)] -pub fn interrupt_number_derive(input: TokenStream) -> TokenStream { - let input = parse_macro_input!(input as DeriveInput); - let pac_enum = PacNumberEnum::try_from(input).unwrap(); - pac_enum - .quote("InterruptNumber", "u16", "MAX_INTERRUPT_NUMBER") - .into() -} - -#[proc_macro_derive(PriorityNumber)] -pub fn priority_number_derive(input: TokenStream) -> TokenStream { - let input = parse_macro_input!(input as DeriveInput); - let pac_enum = PacNumberEnum::try_from(input).unwrap(); - pac_enum - .quote("PriorityNumber", "u8", "MAX_PRIORITY_NUMBER") - .into() -} -#[proc_macro_derive(HartIdNumber)] -pub fn hart_id_number_derive(input: TokenStream) -> TokenStream { - let input = parse_macro_input!(input as DeriveInput); - let pac_enum = PacNumberEnum::try_from(input).unwrap(); - pac_enum - .quote("HartIdNumber", "u16", "MAX_HART_ID_NUMBER") - .into() + let trait_impl = match attrs[1] { + "ExceptionNumber" => pac_enum.quote("ExceptionNumber", "u16", "MAX_EXCEPTION_NUMBER"), + "InterruptNumber" => pac_enum.quote("InterruptNumber", "u16", "MAX_INTERRUPT_NUMBER"), + "PriorityNumber" => pac_enum.quote("PriorityNumber", "u8", "MAX_PRIORITY_NUMBER"), + "HartIdNumber" => pac_enum.quote("HartIdNumber", "u16", "MAX_HART_ID_NUMBER"), + _ => panic!("Unknown trait '{}'. Expected: 'ExceptionNumber', 'InterruptNumber', 'PriorityNumber', or 'HartIdNumber'", attrs[1]), + }; + quote! { + #input + #trait_impl + } + .into() } diff --git a/riscv-pac/src/lib.rs b/riscv-pac/src/lib.rs index 1a91ffc2..28a9baf1 100644 --- a/riscv-pac/src/lib.rs +++ b/riscv-pac/src/lib.rs @@ -4,12 +4,13 @@ pub mod result; use result::Result; +#[cfg(feature = "riscv-pac-macros")] pub use riscv_pac_macros::*; /// Trait for enums of target-specific exception numbers. /// /// 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 +/// exceptions for a specific device. Alternatively, the `riscv` crate provides a default /// implementation for the RISC-V ISA. Each variant must convert to a `u16` of its exception number. /// /// # Safety @@ -35,7 +36,7 @@ pub unsafe trait ExceptionNumber: Copy { /// 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 +/// interrupts for a specific device. Alternatively, the `riscv` crate provides a default /// implementation for the RISC-V ISA. Each variant must convert to a `u16` of its interrupt number. /// /// # Safety @@ -58,12 +59,11 @@ pub unsafe trait InterruptNumber: Copy { fn from_number(value: u16) -> Result; } -/// Trait for enums of target-specific core interrupt numbers. +/// 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 +/// 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 @@ -71,7 +71,7 @@ pub unsafe trait InterruptNumber: Copy { /// Each enum variant must represent a valid core interrupt number read from the `mcause` CSR. pub unsafe trait CoreInterruptNumber: InterruptNumber {} -/// Trait for enums of target-specific external interrupt numbers. +/// 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. @@ -85,9 +85,8 @@ 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 /// @@ -111,9 +110,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 /// diff --git a/riscv-pac/tests/test.rs b/riscv-pac/tests/test.rs new file mode 100644 index 00000000..c74b861b --- /dev/null +++ b/riscv-pac/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-pac/tests/ui/fail_empty_macro.rs b/riscv-pac/tests/ui/fail_empty_macro.rs new file mode 100644 index 00000000..a47a5711 --- /dev/null +++ b/riscv-pac/tests/ui/fail_empty_macro.rs @@ -0,0 +1,9 @@ +#[riscv_pac::pac_enum] +#[derive(Clone, Copy, Debug, PartialEq)] +enum Interrupt { + I1 = 1, + I2 = 2, + I4 = 4, +} + +fn main() {} diff --git a/riscv-pac/tests/ui/fail_empty_macro.stderr b/riscv-pac/tests/ui/fail_empty_macro.stderr new file mode 100644 index 00000000..37859747 --- /dev/null +++ b/riscv-pac/tests/ui/fail_empty_macro.stderr @@ -0,0 +1,7 @@ +error: custom attribute panicked + --> tests/ui/fail_empty_macro.rs:1:1 + | +1 | #[riscv_pac::pac_enum] + | ^^^^^^^^^^^^^^^^^^^^^^ + | + = help: message: Attribute is empty. Expected: 'riscv_pac::pac_enum(unsafe )' diff --git a/riscv-pac/tests/ui/fail_more_than_one_trait.rs b/riscv-pac/tests/ui/fail_more_than_one_trait.rs new file mode 100644 index 00000000..7db5629e --- /dev/null +++ b/riscv-pac/tests/ui/fail_more_than_one_trait.rs @@ -0,0 +1,9 @@ +#[riscv_pac::pac_enum(unsafe InterruptNumber, unsafe PriorityNumber)] +#[derive(Clone, Copy, Debug, PartialEq)] +enum Interrupt { + I1 = 1, + I2 = 2, + I4 = 4, +} + +fn main() {} diff --git a/riscv-pac/tests/ui/fail_more_than_one_trait.stderr b/riscv-pac/tests/ui/fail_more_than_one_trait.stderr new file mode 100644 index 00000000..881d9f7a --- /dev/null +++ b/riscv-pac/tests/ui/fail_more_than_one_trait.stderr @@ -0,0 +1,7 @@ +error: custom attribute panicked + --> tests/ui/fail_more_than_one_trait.rs:1:1 + | +1 | #[riscv_pac::pac_enum(unsafe InterruptNumber, unsafe PriorityNumber)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: message: Wrong attribute format. Expected: 'riscv_pac::pac_enum(unsafe )' diff --git a/riscv-pac/tests/ui/fail_no_unsafe.rs b/riscv-pac/tests/ui/fail_no_unsafe.rs new file mode 100644 index 00000000..865a68fd --- /dev/null +++ b/riscv-pac/tests/ui/fail_no_unsafe.rs @@ -0,0 +1,9 @@ +#[riscv_pac::pac_enum(InterruptNumber)] +#[derive(Clone, Copy, Debug, PartialEq)] +enum Interrupt { + I1 = 1, + I2 = 2, + I4 = 4, +} + +fn main() {} diff --git a/riscv-pac/tests/ui/fail_no_unsafe.stderr b/riscv-pac/tests/ui/fail_no_unsafe.stderr new file mode 100644 index 00000000..e459a17c --- /dev/null +++ b/riscv-pac/tests/ui/fail_no_unsafe.stderr @@ -0,0 +1,7 @@ +error: custom attribute panicked + --> tests/ui/fail_no_unsafe.rs:1:1 + | +1 | #[riscv_pac::pac_enum(InterruptNumber)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: message: Attribute does not start with 'unsafe'. Expected: 'riscv_pac::pac_enum(unsafe )' diff --git a/riscv-pac/tests/ui/fail_unknown_trait.rs b/riscv-pac/tests/ui/fail_unknown_trait.rs new file mode 100644 index 00000000..50192085 --- /dev/null +++ b/riscv-pac/tests/ui/fail_unknown_trait.rs @@ -0,0 +1,9 @@ +#[riscv_pac::pac_enum(unsafe CoreInterruptNumber)] +#[derive(Clone, Copy, Debug, PartialEq)] +enum Interrupt { + I1 = 1, + I2 = 2, + I4 = 4, +} + +fn main() {} diff --git a/riscv-pac/tests/ui/fail_unknown_trait.stderr b/riscv-pac/tests/ui/fail_unknown_trait.stderr new file mode 100644 index 00000000..5ca1ce48 --- /dev/null +++ b/riscv-pac/tests/ui/fail_unknown_trait.stderr @@ -0,0 +1,7 @@ +error: custom attribute panicked + --> tests/ui/fail_unknown_trait.rs:1:1 + | +1 | #[riscv_pac::pac_enum(unsafe CoreInterruptNumber)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: message: Unknown trait 'CoreInterruptNumber'. Expected: 'ExceptionNumber', 'InterruptNumber', 'PriorityNumber', or 'HartIdNumber' diff --git a/riscv-pac/tests/ui/fail_wrong_repr.rs b/riscv-pac/tests/ui/fail_wrong_repr.rs new file mode 100644 index 00000000..82ebdc52 --- /dev/null +++ b/riscv-pac/tests/ui/fail_wrong_repr.rs @@ -0,0 +1,9 @@ +#[riscv_pac::pac_enum(unsafe InterruptNumber)] +#[derive(Clone, Copy, Debug, PartialEq)] +enum Interrupt { + I1 = 1, + I2 = 2, + I4 = 4, +} + +fn main() {} diff --git a/riscv-pac/tests/ui/fail_wrong_repr.stderr b/riscv-pac/tests/ui/fail_wrong_repr.stderr new file mode 100644 index 00000000..167a47ef --- /dev/null +++ b/riscv-pac/tests/ui/fail_wrong_repr.stderr @@ -0,0 +1,9 @@ +error[E0512]: cannot transmute between types of different sizes, or dependently-sized types + --> tests/ui/fail_wrong_repr.rs:1:1 + | +1 | #[riscv_pac::pac_enum(unsafe InterruptNumber)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: source type: `u16` (16 bits) + = note: target type: `Interrupt` (8 bits) + = note: this error originates in the attribute macro `riscv_pac::pac_enum` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/riscv-pac/tests/ui/pass_test.rs b/riscv-pac/tests/ui/pass_test.rs new file mode 100644 index 00000000..2001a986 --- /dev/null +++ b/riscv-pac/tests/ui/pass_test.rs @@ -0,0 +1,83 @@ +use riscv_pac::*; + +#[repr(u16)] +#[pac_enum(unsafe ExceptionNumber)] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +enum Exception { + E1 = 1, + E3 = 3, +} + +#[repr(u16)] +#[pac_enum(unsafe InterruptNumber)] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +enum Interrupt { + I1 = 1, + I2 = 2, + I4 = 4, + I7 = 7, +} + +#[repr(u8)] +#[pac_enum(unsafe PriorityNumber)] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +enum Priority { + P0 = 0, + P1 = 1, + P2 = 2, + P3 = 3, +} + +#[repr(u16)] +#[pac_enum(unsafe HartIdNumber)] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +enum HartId { + H0 = 0, + H1 = 1, + H2 = 2, +} + +fn main() { + assert_eq!(Exception::E1.number(), 1); + assert_eq!(Exception::E3.number(), 3); + + assert_eq!(Exception::from_number(0), Err(0)); + 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::from_number(4), Err(4)); + + 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(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(3)); + assert_eq!(Interrupt::from_number(4), Ok(Interrupt::I4)); + assert_eq!(Interrupt::from_number(5), Err(5)); + assert_eq!(Interrupt::from_number(6), Err(6)); + assert_eq!(Interrupt::from_number(7), Ok(Interrupt::I7)); + + assert_eq!(Priority::P0.number(), 0); + assert_eq!(Priority::P1.number(), 1); + assert_eq!(Priority::P2.number(), 2); + assert_eq!(Priority::P3.number(), 3); + + assert_eq!(Priority::from_number(0), Ok(Priority::P0)); + assert_eq!(Priority::from_number(1), Ok(Priority::P1)); + assert_eq!(Priority::from_number(2), Ok(Priority::P2)); + assert_eq!(Priority::from_number(3), Ok(Priority::P3)); + assert_eq!(Priority::from_number(4), Err(4)); + + assert_eq!(HartId::H0.number(), 0); + assert_eq!(HartId::H1.number(), 1); + assert_eq!(HartId::H2.number(), 2); + + assert_eq!(HartId::from_number(0), Ok(HartId::H0)); + assert_eq!(HartId::from_number(1), Ok(HartId::H1)); + assert_eq!(HartId::from_number(2), Ok(HartId::H2)); + assert_eq!(HartId::from_number(3), Err(3)); +} diff --git a/riscv-peripheral/Cargo.toml b/riscv-peripheral/Cargo.toml index 4655d9fa..c4461641 100644 --- a/riscv-peripheral/Cargo.toml +++ b/riscv-peripheral/Cargo.toml @@ -17,10 +17,11 @@ license = "ISC" embedded-hal = "1.0.0" embedded-hal-async = { version = "1.0.0", optional = true } riscv = { path = "../riscv", version = "0.11.2" } -riscv-pac = { path = "../riscv-pac", version = "0.1.2" } +riscv-pac = { path = "../riscv-pac", version = "0.1.2", default-features = false } [dev-dependencies] heapless = "0.8.0" +riscv-pac = { path = "../riscv-pac", version = "0.1.2", default-features = true } [features] aclint-hal-async = ["embedded-hal-async"] diff --git a/riscv-peripheral/src/plic.rs b/riscv-peripheral/src/plic.rs index 16952222..5e2950cf 100644 --- a/riscv-peripheral/src/plic.rs +++ b/riscv-peripheral/src/plic.rs @@ -148,6 +148,7 @@ pub(crate) mod test { use riscv_pac::result::{Error, Result}; use riscv_pac::{ExternalInterruptNumber, HartIdNumber, InterruptNumber, PriorityNumber}; + #[pac_enum(unsafe InterruptNumber)] #[derive(Clone, Copy, Debug, Eq, PartialEq)] #[repr(u16)] pub(crate) enum Interrupt { @@ -157,6 +158,7 @@ pub(crate) mod test { I4 = 4, } + #[pac_enum(unsafe PriorityNumber)] #[derive(Clone, Copy, Debug, Eq, PartialEq)] #[repr(u8)] pub(crate) enum Priority { @@ -166,6 +168,7 @@ pub(crate) mod test { P3 = 3, } + #[pac_enum(unsafe HartIdNumber)] #[derive(Clone, Copy, Debug, Eq, PartialEq)] #[repr(u16)] pub(crate) enum Context { @@ -174,13 +177,13 @@ pub(crate) mod test { C2 = 2, } - unsafe impl InterruptNumber for Interrupt { - const MAX_INTERRUPT_NUMBER: u16 = 4; + // unsafe impl InterruptNumber for Interrupt { + // const MAX_INTERRUPT_NUMBER: u16 = 4; - #[inline] - fn number(self) -> u16 { - self as _ - } + // #[inline] + // fn number(self) -> u16 { + // self as _ + // } #[inline] fn from_number(number: u16) -> Result { @@ -195,13 +198,13 @@ pub(crate) mod test { unsafe impl ExternalInterruptNumber for Interrupt {} - unsafe impl PriorityNumber for Priority { - const MAX_PRIORITY_NUMBER: u8 = 3; + // unsafe impl PriorityNumber for Priority { + // const MAX_PRIORITY_NUMBER: u8 = 3; - #[inline] - fn number(self) -> u8 { - self as _ - } + // #[inline] + // fn number(self) -> u8 { + // self as _ + // } #[inline] fn from_number(number: u8) -> Result { @@ -214,13 +217,13 @@ pub(crate) mod test { } } - unsafe impl HartIdNumber for Context { - const MAX_HART_ID_NUMBER: u16 = 2; + // unsafe impl HartIdNumber for Context { + // const MAX_HART_ID_NUMBER: u16 = 2; - #[inline] - fn number(self) -> u16 { - self as _ - } + // #[inline] + // fn number(self) -> u16 { + // self as _ + // } #[inline] fn from_number(number: u16) -> Result { diff --git a/riscv/Cargo.toml b/riscv/Cargo.toml index e69de29b..e55ece39 100644 --- a/riscv/Cargo.toml +++ b/riscv/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "riscv" +version = "0.11.2" +edition = "2021" +rust-version = "1.60" +repository = "https://github.com/rust-embedded/riscv" +authors = ["The RISC-V Team "] +categories = ["embedded", "hardware-support", "no-std"] +description = "Low level access to RISC-V processors" +documentation = "https://docs.rs/riscv" +keywords = ["riscv", "register", "peripheral"] +license = "ISC" + +[package.metadata.docs.rs] +all-features = true +default-target = "riscv64imac-unknown-none-elf" +targets = [ + "riscv32i-unknown-none-elf", "riscv32imc-unknown-none-elf", "riscv32imac-unknown-none-elf", + "riscv64imac-unknown-none-elf", "riscv64gc-unknown-none-elf", +] + +[features] +s-mode = [] +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.2", default-features = false } From 2abefaa8ef4ead54ed73683155feb9df6f157f19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rom=C3=A1n=20C=C3=A1rdenas=20Rodr=C3=ADguez?= Date: Mon, 6 May 2024 12:32:03 +0200 Subject: [PATCH 04/17] clean riscv-peripheral tests --- riscv-pac/macros/src/lib.rs | 1 - riscv-peripheral/src/plic.rs | 2 ++ 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/riscv-pac/macros/src/lib.rs b/riscv-pac/macros/src/lib.rs index 584656e5..a19714e8 100644 --- a/riscv-pac/macros/src/lib.rs +++ b/riscv-pac/macros/src/lib.rs @@ -39,7 +39,6 @@ impl PacNumberEnum { }, _ => panic!("Variant must have a discriminant"), }; - // check for duplicate discriminant values var_map.insert(value, ident); numbers.push(value); } diff --git a/riscv-peripheral/src/plic.rs b/riscv-peripheral/src/plic.rs index 5e2950cf..0ddefa21 100644 --- a/riscv-peripheral/src/plic.rs +++ b/riscv-peripheral/src/plic.rs @@ -158,6 +158,8 @@ pub(crate) mod test { I4 = 4, } + unsafe impl ExternalInterruptNumber for Interrupt {} + #[pac_enum(unsafe PriorityNumber)] #[derive(Clone, Copy, Debug, Eq, PartialEq)] #[repr(u8)] From 094f1d76823078813c623f8e5c3ea050fbec4058 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rom=C3=A1n=20C=C3=A1rdenas=20Rodr=C3=ADguez?= Date: Fri, 17 May 2024 18:31:07 +0200 Subject: [PATCH 05/17] clippy --- riscv/src/register/mcause.rs | 4 ++-- riscv/src/register/scause.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/riscv/src/register/mcause.rs b/riscv/src/register/mcause.rs index fa26608b..cdc09473 100644 --- a/riscv/src/register/mcause.rs +++ b/riscv/src/register/mcause.rs @@ -64,7 +64,7 @@ impl From for Interrupt { Self::Unknown } else { // SAFETY: valid interrupt number - unsafe { core::mem::transmute(nr) } + unsafe { core::mem::transmute::(nr) } } } } @@ -109,7 +109,7 @@ impl From for Exception { Self::Unknown } else { // SAFETY: valid exception number - unsafe { core::mem::transmute(nr) } + unsafe { core::mem::transmute::(nr) } } } } diff --git a/riscv/src/register/scause.rs b/riscv/src/register/scause.rs index 250437be..d03a89d1 100644 --- a/riscv/src/register/scause.rs +++ b/riscv/src/register/scause.rs @@ -98,7 +98,7 @@ impl From for Exception { Self::Unknown } else { // SAFETY: valid exception number - unsafe { core::mem::transmute(nr) } + unsafe { core::mem::transmute::(nr) } } } } From 5322a51dcd9c229067ac1089cbc9ab8b4495929e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rom=C3=A1n=20C=C3=A1rdenas=20Rodr=C3=ADguez?= Date: Fri, 17 May 2024 19:07:33 +0200 Subject: [PATCH 06/17] Use riscv_pac macros in example --- riscv-peripheral/examples/e310x.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/riscv-peripheral/examples/e310x.rs b/riscv-peripheral/examples/e310x.rs index b092861b..00288680 100644 --- a/riscv-peripheral/examples/e310x.rs +++ b/riscv-peripheral/examples/e310x.rs @@ -3,9 +3,10 @@ //! peripheral definitions for a target. use riscv_pac::result::{Error, Result}; -use riscv_pac::{ExternalInterruptNumber, HartIdNumber, InterruptNumber, PriorityNumber}; +use riscv_pac::{pac_enum, ExternalInterruptNumber}; #[repr(u16)] +#[pac_enum(unsafe HartIdNumber)] #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum HartId { H0 = 0, @@ -31,7 +32,6 @@ unsafe impl HartIdNumber for HartId { } #[derive(Copy, Clone, Debug, PartialEq, Eq)] -#[repr(u16)] pub enum Interrupt { WATCHDOG = 1, RTC = 2, @@ -108,8 +108,9 @@ unsafe impl InterruptNumber for Interrupt { unsafe impl ExternalInterruptNumber for Interrupt {} -#[derive(Copy, Clone, Debug, PartialEq, Eq)] #[repr(u8)] +#[pac_enum(unsafe PriorityNumber)] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum Priority { P0 = 0, P1 = 1, From 8da1e9f5516e6fd15956a78427a9ff6021eccc7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rom=C3=A1n=20C=C3=A1rdenas=20Rodr=C3=ADguez?= Date: Thu, 30 May 2024 11:04:22 +0200 Subject: [PATCH 07/17] Generics for interrupts and exceptions --- riscv-pac/macros/src/lib.rs | 8 +- riscv-pac/src/lib.rs | 16 +- riscv-pac/tests/ui/fail_wrong_repr.stderr | 2 +- riscv-pac/tests/ui/pass_test.rs | 4 +- riscv/src/interrupt.rs | 1 + riscv/src/register/mcause.rs | 169 ++++++++++------------ riscv/src/register/scause.rs | 164 +++++++++------------ 7 files changed, 159 insertions(+), 205 deletions(-) diff --git a/riscv-pac/macros/src/lib.rs b/riscv-pac/macros/src/lib.rs index a19714e8..b4211781 100644 --- a/riscv-pac/macros/src/lib.rs +++ b/riscv-pac/macros/src/lib.rs @@ -107,7 +107,7 @@ impl PacNumberEnum { fn from_number(number: #num_type) -> Result { if #valid_condition { // SAFETY: The number is valid for this enum - Ok(unsafe { core::mem::transmute(number) }) + Ok(unsafe { core::mem::transmute::<#num_type, Self>(number) }) } else { Err(number) } @@ -148,7 +148,7 @@ impl PacNumberEnum { /// ```rust /// use riscv_pac::*; /// -/// #[repr(u16)] +/// #[repr(usize)] /// #[pac_enum(unsafe ExceptionNumber)] /// #[derive(Clone, Copy, Debug, Eq, PartialEq)] /// enum Exception { @@ -190,8 +190,8 @@ pub fn pac_enum(attr: TokenStream, item: TokenStream) -> TokenStream { } let trait_impl = match attrs[1] { - "ExceptionNumber" => pac_enum.quote("ExceptionNumber", "u16", "MAX_EXCEPTION_NUMBER"), - "InterruptNumber" => pac_enum.quote("InterruptNumber", "u16", "MAX_INTERRUPT_NUMBER"), + "ExceptionNumber" => pac_enum.quote("ExceptionNumber", "usize", "MAX_EXCEPTION_NUMBER"), + "InterruptNumber" => pac_enum.quote("InterruptNumber", "usize", "MAX_INTERRUPT_NUMBER"), "PriorityNumber" => pac_enum.quote("PriorityNumber", "u8", "MAX_PRIORITY_NUMBER"), "HartIdNumber" => pac_enum.quote("HartIdNumber", "u16", "MAX_HART_ID_NUMBER"), _ => panic!("Unknown trait '{}'. Expected: 'ExceptionNumber', 'InterruptNumber', 'PriorityNumber', or 'HartIdNumber'", attrs[1]), diff --git a/riscv-pac/src/lib.rs b/riscv-pac/src/lib.rs index 28a9baf1..51ed272f 100644 --- a/riscv-pac/src/lib.rs +++ b/riscv-pac/src/lib.rs @@ -11,7 +11,7 @@ pub use riscv_pac_macros::*; /// /// 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 `u16` of its exception number. +/// implementation for the RISC-V ISA. Each variant must convert to a `usize` of its exception number. /// /// # Safety /// @@ -23,21 +23,21 @@ pub use riscv_pac_macros::*; /// * `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: u16; + const MAX_EXCEPTION_NUMBER: usize; /// Converts an exception to its corresponding number. - fn number(self) -> u16; + 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: u16) -> Result; + 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 `u16` of its interrupt number. +/// implementation for the RISC-V ISA. Each variant must convert to a `usize` of its interrupt number. /// /// # Safety /// @@ -49,14 +49,14 @@ pub unsafe trait ExceptionNumber: Copy { /// * `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. diff --git a/riscv-pac/tests/ui/fail_wrong_repr.stderr b/riscv-pac/tests/ui/fail_wrong_repr.stderr index 167a47ef..6a545f0e 100644 --- a/riscv-pac/tests/ui/fail_wrong_repr.stderr +++ b/riscv-pac/tests/ui/fail_wrong_repr.stderr @@ -4,6 +4,6 @@ error[E0512]: cannot transmute between types of different sizes, or dependently- 1 | #[riscv_pac::pac_enum(unsafe InterruptNumber)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: source type: `u16` (16 bits) + = note: source type: `usize` (64 bits) = note: target type: `Interrupt` (8 bits) = note: this error originates in the attribute macro `riscv_pac::pac_enum` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/riscv-pac/tests/ui/pass_test.rs b/riscv-pac/tests/ui/pass_test.rs index 2001a986..2be913eb 100644 --- a/riscv-pac/tests/ui/pass_test.rs +++ b/riscv-pac/tests/ui/pass_test.rs @@ -1,6 +1,6 @@ use riscv_pac::*; -#[repr(u16)] +#[repr(usize)] #[pac_enum(unsafe ExceptionNumber)] #[derive(Clone, Copy, Debug, Eq, PartialEq)] enum Exception { @@ -8,7 +8,7 @@ enum Exception { E3 = 3, } -#[repr(u16)] +#[repr(usize)] #[pac_enum(unsafe InterruptNumber)] #[derive(Clone, Copy, Debug, Eq, PartialEq)] enum Interrupt { diff --git a/riscv/src/interrupt.rs b/riscv/src/interrupt.rs index 0bd03b7e..e77c7915 100644 --- a/riscv/src/interrupt.rs +++ b/riscv/src/interrupt.rs @@ -92,6 +92,7 @@ pub mod machine { r } } + pub mod supervisor { use crate::register::{sepc, sstatus}; diff --git a/riscv/src/register/mcause.rs b/riscv/src/register/mcause.rs index cdc09473..6d89c89f 100644 --- a/riscv/src/register/mcause.rs +++ b/riscv/src/register/mcause.rs @@ -3,27 +3,7 @@ use riscv_pac::CoreInterruptNumber; pub use riscv_pac::{ExceptionNumber, InterruptNumber}; // re-export useful riscv-pac traits -/// mcause register -#[derive(Clone, Copy, Debug)] -pub struct Mcause { - bits: usize, -} - -impl From for Mcause { - #[inline] - fn from(bits: usize) -> Self { - Self { bits } - } -} - -/// Trap Cause -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub enum Trap { - Interrupt(Interrupt), - Exception(Exception), -} - -/// Interrupt +/// Standard M-mode RISC-V interrupts #[derive(Copy, Clone, Debug, PartialEq, Eq)] #[repr(usize)] pub enum Interrupt { @@ -33,10 +13,32 @@ pub enum Interrupt { MachineTimer = 7, SupervisorExternal = 9, MachineExternal = 11, - Unknown, } -/// Exception +/// 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 { + if value > 11 || value % 2 == 0 { + Err(value) + } else { + // SAFETY: valid interrupt number + unsafe { Ok(core::mem::transmute::(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 { @@ -54,93 +56,52 @@ pub enum Exception { InstructionPageFault = 12, LoadPageFault = 13, StorePageFault = 15, - Unknown, -} - -impl From for Interrupt { - #[inline] - fn from(nr: usize) -> Self { - if nr > 11 || nr % 2 == 0 { - Self::Unknown - } else { - // SAFETY: valid interrupt number - unsafe { core::mem::transmute::(nr) } - } - } -} - -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), - } - } } -/// SAFETY: `Interrupt` represents the standard RISC-V interrupts -unsafe impl InterruptNumber for Interrupt { - const MAX_INTERRUPT_NUMBER: u16 = Self::MachineExternal as u16; +/// 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) -> u16 { - self as u16 + fn number(self) -> usize { + self as usize } #[inline] - fn from_number(value: u16) -> Result { - match (value as usize).into() { - Self::Unknown => Err(value), - value => Ok(value), - } - } -} - -/// SAFETY: `Interrupt` represents the standard RISC-V core interrupts -unsafe impl CoreInterruptNumber for Interrupt {} - -impl From for Exception { - #[inline] - fn from(nr: usize) -> Self { - if nr == 10 || nr == 14 || nr > 15 { - Self::Unknown + fn from_number(value: usize) -> Result { + if value == 10 || value == 14 || value > 15 { + Err(value) } else { // SAFETY: valid exception number - unsafe { core::mem::transmute::(nr) } + unsafe { Ok(core::mem::transmute::(value)) } } } } -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), - } - } +/// Trap Cause +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum Trap { + Interrupt(I), + Exception(E), } -/// SAFETY: `Exception` represents the standard RISC-V exceptions -unsafe impl ExceptionNumber for Exception { - const MAX_EXCEPTION_NUMBER: u16 = Self::StorePageFault as u16; +/// Trap Error +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum TrapError { + InvalidInterrupt(usize), + InvalidException(usize), +} - #[inline] - fn number(self) -> u16 { - self as u16 - } +/// mcause register +#[derive(Clone, Copy, Debug)] +pub struct Mcause { + bits: usize, +} +impl From for Mcause { #[inline] - fn from_number(value: u16) -> Result { - match (value as usize).into() { - Self::Unknown => Err(value), - value => Ok(value), - } + fn from(bits: usize) -> Self { + Self { bits } } } @@ -157,16 +118,32 @@ impl Mcause { self.bits & !(1 << (usize::BITS as usize - 1)) } - /// Trap Cause + /// Try to get the trap cause #[inline] - pub fn cause(&self) -> Trap { + pub fn try_cause(&self) -> Result, TrapError> + where + I: CoreInterruptNumber, + E: ExceptionNumber, + { if self.is_interrupt() { - Trap::Interrupt(Interrupt::from(self.code())) + match I::from_number(self.code()) { + Ok(interrupt) => Ok(Trap::Interrupt(interrupt)), + Err(code) => Err(TrapError::InvalidInterrupt(code)), + } } else { - Trap::Exception(Exception::from(self.code())) + match E::from_number(self.code()) { + Ok(exception) => Ok(Trap::Exception(exception)), + Err(code) => Err(TrapError::InvalidException(code)), + } } } + /// Trap Cause + #[inline] + pub fn cause(&self) -> Trap { + self.try_cause().unwrap() + } + /// Is trap cause an interrupt. #[inline] pub fn is_interrupt(&self) -> bool { diff --git a/riscv/src/register/scause.rs b/riscv/src/register/scause.rs index d03a89d1..6379ddf9 100644 --- a/riscv/src/register/scause.rs +++ b/riscv/src/register/scause.rs @@ -3,19 +3,6 @@ use riscv_pac::CoreInterruptNumber; pub use riscv_pac::{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)] @@ -23,9 +10,31 @@ pub enum Interrupt { SupervisorSoft = 1, SupervisorTimer = 5, SupervisorExternal = 9, - Unknown, } +/// 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 { + if value == 1 || value == 5 || value == 9 { + // SAFETY: valid interrupt number + Ok(unsafe { core::mem::transmute::(value) }) + } else { + Err(value) + } + } +} + +/// SAFETY: `Interrupt` represents the standard RISC-V core interrupts +unsafe impl CoreInterruptNumber for Interrupt {} + /// Exception #[derive(Copy, Clone, Debug, Eq, PartialEq)] #[repr(usize)] @@ -43,94 +52,46 @@ pub enum Exception { 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), - } - } } -/// SAFETY: `Interrupt` represents the standard RISC-V interrupts -unsafe impl InterruptNumber for Interrupt { - const MAX_INTERRUPT_NUMBER: u16 = Self::SupervisorExternal as u16; - - #[inline] - fn number(self) -> u16 { - self as u16 - } +/// SAFETY: `Exception` represents the standard RISC-V exceptions +unsafe impl ExceptionNumber for Exception { + const MAX_EXCEPTION_NUMBER: usize = Self::StorePageFault as usize; #[inline] - fn from_number(value: u16) -> Result { - match (value as usize).into() { - Self::Unknown => Err(value), - value => Ok(value), - } + fn number(self) -> usize { + self as usize } -} -/// SAFETY: `Interrupt` represents the standard RISC-V core interrupts -unsafe impl CoreInterruptNumber for Interrupt {} - -impl From for Exception { #[inline] - fn from(nr: usize) -> Self { - if nr == 10 || nr == 11 || nr == 14 || nr > 15 { - Self::Unknown + fn from_number(value: usize) -> Result { + if value == 10 || value == 11 || value == 14 || value > 15 { + Err(value) } else { // SAFETY: valid exception number - unsafe { core::mem::transmute::(nr) } + unsafe { Ok(core::mem::transmute::(value)) } } } } -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), - } - } +/// Trap Cause +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum Trap { + Interrupt(I), + Exception(E), } -/// SAFETY: `Exception` represents the standard RISC-V exceptions -unsafe impl ExceptionNumber for Exception { - const MAX_EXCEPTION_NUMBER: u16 = Self::StorePageFault as u16; - - #[inline] - fn number(self) -> u16 { - self as u16 - } +/// Trap Error +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum TrapError { + InvalidInterrupt(usize), + InvalidException(usize), +} - #[inline] - fn from_number(value: u16) -> Result { - match (value as usize).into() { - Self::Unknown => Err(value), - value => Ok(value), - } - } +/// scause register +#[derive(Clone, Copy)] +pub struct Scause { + bits: usize, } impl Scause { @@ -146,16 +107,32 @@ impl Scause { self.bits & !(1 << (usize::BITS as usize - 1)) } - /// Trap Cause + /// Try to get the trap cause #[inline] - pub fn cause(&self) -> Trap { + pub fn try_cause(&self) -> Result, TrapError> + where + I: CoreInterruptNumber, + E: ExceptionNumber, + { if self.is_interrupt() { - Trap::Interrupt(Interrupt::from(self.code())) + match I::from_number(self.code()) { + Ok(interrupt) => Ok(Trap::Interrupt(interrupt)), + Err(code) => Err(TrapError::InvalidInterrupt(code)), + } } else { - Trap::Exception(Exception::from(self.code())) + match E::from_number(self.code()) { + Ok(exception) => Ok(Trap::Exception(exception)), + Err(code) => Err(TrapError::InvalidException(code)), + } } } + /// Trap Cause + #[inline] + pub fn cause(&self) -> Trap { + self.try_cause().unwrap() + } + /// Is trap cause an interrupt. #[inline] pub fn is_interrupt(&self) -> bool { @@ -180,13 +157,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); } From 6a4e8e3ce282dc1e4fc9972e74a79b8f2184d7a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rom=C3=A1n=20C=C3=A1rdenas=20Rodr=C3=ADguez?= Date: Thu, 30 May 2024 11:13:15 +0200 Subject: [PATCH 08/17] Fix clippy --- riscv-peripheral/src/plic.rs | 2 +- riscv-peripheral/src/plic/enables.rs | 10 +++++----- riscv-peripheral/src/plic/pendings.rs | 2 +- riscv-peripheral/src/plic/priorities.rs | 4 ++-- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/riscv-peripheral/src/plic.rs b/riscv-peripheral/src/plic.rs index 0ddefa21..cd329d31 100644 --- a/riscv-peripheral/src/plic.rs +++ b/riscv-peripheral/src/plic.rs @@ -150,7 +150,7 @@ pub(crate) mod test { #[pac_enum(unsafe InterruptNumber)] #[derive(Clone, Copy, Debug, Eq, PartialEq)] - #[repr(u16)] + #[repr(usize)] pub(crate) enum Interrupt { I1 = 1, I2 = 2, diff --git a/riscv-peripheral/src/plic/enables.rs b/riscv-peripheral/src/plic/enables.rs index 7dfe6140..9891b32c 100644 --- a/riscv-peripheral/src/plic/enables.rs +++ b/riscv-peripheral/src/plic/enables.rs @@ -30,7 +30,7 @@ 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; + 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)) }; @@ -48,7 +48,7 @@ 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; + 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)) }; @@ -73,7 +73,7 @@ impl ENABLES { 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)) }; @@ -87,7 +87,7 @@ 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; + 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)) }; @@ -111,7 +111,7 @@ impl ENABLES { 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)) }; diff --git a/riscv-peripheral/src/plic/pendings.rs b/riscv-peripheral/src/plic/pendings.rs index c166ec86..594991da 100644 --- a/riscv-peripheral/src/plic/pendings.rs +++ b/riscv-peripheral/src/plic/pendings.rs @@ -30,7 +30,7 @@ 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; + 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 7662831f..b5e44857 100644 --- a/riscv-peripheral/src/plic/priorities.rs +++ b/riscv-peripheral/src/plic/priorities.rs @@ -31,7 +31,7 @@ impl PRIORITIES { #[inline] pub fn get_priority(self, source: I) -> P { // SAFETY: valid interrupt number - let reg: Reg = unsafe { Reg::new(self.ptr.offset(source.number() as _)) }; + let reg: Reg = unsafe { Reg::new(self.ptr.add(source.number())) }; P::from_number(reg.read() as _).unwrap() } @@ -47,7 +47,7 @@ impl PRIORITIES { 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 _); } From 1bbb26a3151af70298f8b110da5d929a8b2305d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rom=C3=A1n=20C=C3=A1rdenas=20Rodr=C3=ADguez?= Date: Wed, 5 Jun 2024 09:12:16 +0200 Subject: [PATCH 09/17] use safe match pattern --- riscv-pac/macros/src/lib.rs | 115 +++++++--------------- riscv-pac/tests/ui/fail_wrong_repr.rs | 9 -- riscv-pac/tests/ui/fail_wrong_repr.stderr | 9 -- riscv/src/register/mcause.rs | 34 +++++-- riscv/src/register/scause.rs | 30 ++++-- 5 files changed, 82 insertions(+), 115 deletions(-) delete mode 100644 riscv-pac/tests/ui/fail_wrong_repr.rs delete mode 100644 riscv-pac/tests/ui/fail_wrong_repr.stderr diff --git a/riscv-pac/macros/src/lib.rs b/riscv-pac/macros/src/lib.rs index b4211781..4f783db8 100644 --- a/riscv-pac/macros/src/lib.rs +++ b/riscv-pac/macros/src/lib.rs @@ -6,89 +6,66 @@ extern crate syn; use proc_macro::TokenStream; use proc_macro2::TokenStream as TokenStream2; use quote::quote; -use std::{collections::HashMap, ops::Range, str::FromStr}; +use std::str::FromStr; use syn::{parse_macro_input, Data, DeriveInput, Ident}; struct PacNumberEnum { name: Ident, - valid_ranges: Vec>, + numbers: Vec<(Ident, usize)>, } impl PacNumberEnum { fn new(input: &DeriveInput) -> Self { + let name = input.ident.clone(); + let variants = match &input.data { Data::Enum(data) => &data.variants, _ => panic!("Input is not an enum"), }; - - // Collect the variants and their associated number discriminants - let mut var_map = HashMap::new(); - let mut numbers = Vec::new(); - for variant in variants { - let ident = &variant.ident; - let value = match &variant.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"), + let numbers = variants + .iter() + .map(|variant| { + let ident = &variant.ident; + let value = match &variant.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!("All variant discriminants must be unsigned integers"), - }, - _ => panic!("Variant must have a discriminant"), - }; - var_map.insert(value, ident); - numbers.push(value); - } + _ => panic!("Variant must have a discriminant"), + }; + (ident.clone(), value) + }) + .collect(); - // sort the number discriminants and generate a list of valid ranges - numbers.sort_unstable(); - let mut valid_ranges = Vec::new(); - let mut start = numbers[0]; - let mut end = start; - for &number in &numbers[1..] { - if number == end + 1 { - end = number; - } else { - valid_ranges.push(start..end + 1); - start = number; - end = start; - } - } - valid_ranges.push(start..end + 1); - - Self { - name: input.ident.clone(), - valid_ranges, - } - } - - fn valid_condition(&self) -> TokenStream2 { - let mut arms = Vec::new(); - for range in &self.valid_ranges { - let (start, end) = (range.start, range.end); - if end - start == 1 { - arms.push(TokenStream2::from_str(&format!("number == {start}")).unwrap()); - } else { - arms.push( - TokenStream2::from_str(&format!("({start}..{end}).contains(&number)")).unwrap(), - ); - } - } - quote! { #(#arms) || * } + Self { name, numbers } } fn max_discriminant(&self) -> TokenStream2 { - let max_discriminant = self.valid_ranges.last().expect("invalid range").end - 1; + let max_discriminant = self.numbers.iter().map(|(_, num)| num).max().unwrap(); TokenStream2::from_str(&format!("{max_discriminant}")).unwrap() } + fn valid_matches(&self) -> Vec { + self.numbers + .iter() + .map(|(ident, num)| { + TokenStream2::from_str(&format!("{num} => Ok(Self::{ident})")).unwrap() + }) + .collect() + } + fn quote(&self, trait_name: &str, num_type: &str, const_name: &str) -> TokenStream2 { let name = &self.name; let max_discriminant = self.max_discriminant(); - let valid_condition = self.valid_condition(); + let valid_matches = self.valid_matches(); let trait_name = TokenStream2::from_str(trait_name).unwrap(); let num_type = TokenStream2::from_str(num_type).unwrap(); @@ -105,11 +82,9 @@ impl PacNumberEnum { #[inline] fn from_number(number: #num_type) -> Result { - if #valid_condition { - // SAFETY: The number is valid for this enum - Ok(unsafe { core::mem::transmute::<#num_type, Self>(number) }) - } else { - Err(number) + match number { + #(#valid_matches,)* + _ => Err(number), } } } @@ -125,20 +100,6 @@ impl PacNumberEnum { /// The trait name must be one of `ExceptionNumber`, `InterruptNumber`, `PriorityNumber`, or `HartIdNumber`. /// Marker traits `CoreInterruptNumber` and `ExternalInterruptNumber` cannot be implemented using this macro. /// -/// # Note -/// -/// To implement number-to-enum operation, the macro works with ranges of valid discriminant numbers. -/// If the number is within any of the valid ranges, the number is transmuted to the enum variant. -/// In this way, the macro achieves better performance for enums with a large number of consecutive variants. -/// Thus, the enum must comply with the following requirements: -/// -/// - All the enum variants must have a valid discriminant number (i.e., a number that is within the valid range of the enum). -/// - For the `ExceptionNumber`, `InterruptNumber`, and `HartIdNumber` traits, the enum must be annotated as `#[repr(u16)]` -/// - For the `PriorityNumber` trait, the enum must be annotated as `#[repr(u8)]` -/// -/// If the enum does not meet these requirements, you will have to implement the traits manually (e.g., `riscv::mcause::Interrupt`). -/// For enums with a small number of consecutive variants, it might be better to implement the traits manually. -/// /// # Safety /// /// The struct to be implemented must comply with the requirements of the specified trait. diff --git a/riscv-pac/tests/ui/fail_wrong_repr.rs b/riscv-pac/tests/ui/fail_wrong_repr.rs deleted file mode 100644 index 82ebdc52..00000000 --- a/riscv-pac/tests/ui/fail_wrong_repr.rs +++ /dev/null @@ -1,9 +0,0 @@ -#[riscv_pac::pac_enum(unsafe InterruptNumber)] -#[derive(Clone, Copy, Debug, PartialEq)] -enum Interrupt { - I1 = 1, - I2 = 2, - I4 = 4, -} - -fn main() {} diff --git a/riscv-pac/tests/ui/fail_wrong_repr.stderr b/riscv-pac/tests/ui/fail_wrong_repr.stderr deleted file mode 100644 index 6a545f0e..00000000 --- a/riscv-pac/tests/ui/fail_wrong_repr.stderr +++ /dev/null @@ -1,9 +0,0 @@ -error[E0512]: cannot transmute between types of different sizes, or dependently-sized types - --> tests/ui/fail_wrong_repr.rs:1:1 - | -1 | #[riscv_pac::pac_enum(unsafe InterruptNumber)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = note: source type: `usize` (64 bits) - = note: target type: `Interrupt` (8 bits) - = note: this error originates in the attribute macro `riscv_pac::pac_enum` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/riscv/src/register/mcause.rs b/riscv/src/register/mcause.rs index 6d89c89f..69924b91 100644 --- a/riscv/src/register/mcause.rs +++ b/riscv/src/register/mcause.rs @@ -26,11 +26,14 @@ unsafe impl InterruptNumber for Interrupt { #[inline] fn from_number(value: usize) -> Result { - if value > 11 || value % 2 == 0 { - Err(value) - } else { - // SAFETY: valid interrupt number - unsafe { Ok(core::mem::transmute::(value)) } + 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(value), } } } @@ -69,11 +72,22 @@ unsafe impl ExceptionNumber for Exception { #[inline] fn from_number(value: usize) -> Result { - if value == 10 || value == 14 || value > 15 { - Err(value) - } else { - // SAFETY: valid exception number - unsafe { Ok(core::mem::transmute::(value)) } + 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(value), } } } diff --git a/riscv/src/register/scause.rs b/riscv/src/register/scause.rs index 6379ddf9..e9d7f5e6 100644 --- a/riscv/src/register/scause.rs +++ b/riscv/src/register/scause.rs @@ -23,11 +23,11 @@ unsafe impl InterruptNumber for Interrupt { #[inline] fn from_number(value: usize) -> Result { - if value == 1 || value == 5 || value == 9 { - // SAFETY: valid interrupt number - Ok(unsafe { core::mem::transmute::(value) }) - } else { - Err(value) + match value { + 1 => Ok(Self::SupervisorSoft), + 5 => Ok(Self::SupervisorTimer), + 9 => Ok(Self::SupervisorExternal), + _ => Err(value), } } } @@ -65,11 +65,21 @@ unsafe impl ExceptionNumber for Exception { #[inline] fn from_number(value: usize) -> Result { - if value == 10 || value == 11 || value == 14 || value > 15 { - Err(value) - } else { - // SAFETY: valid exception number - unsafe { Ok(core::mem::transmute::(value)) } + 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(value), } } } From c6846ec16db5883f3b1186cb539fbeb5fe297155 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rom=C3=A1n=20C=C3=A1rdenas=20Rodr=C3=ADguez?= Date: Wed, 5 Jun 2024 10:08:55 +0200 Subject: [PATCH 10/17] Migrate interrupt and exception enums to riscv::interrupt --- riscv/src/interrupt.rs | 210 +++++------------------ riscv/src/interrupt/machine.rs | 269 ++++++++++++++++++++++++++++++ riscv/src/interrupt/supervisor.rs | 256 ++++++++++++++++++++++++++++ riscv/src/register/mcause.rs | 205 ++--------------------- riscv/src/register/scause.rs | 194 ++------------------- 5 files changed, 592 insertions(+), 542 deletions(-) create mode 100644 riscv/src/interrupt/machine.rs create mode 100644 riscv/src/interrupt/supervisor.rs diff --git a/riscv/src/interrupt.rs b/riscv/src/interrupt.rs index e77c7915..90d9a1da 100644 --- a/riscv/src/interrupt.rs +++ b/riscv/src/interrupt.rs @@ -2,188 +2,70 @@ // NOTE: Adapted from cortex-m/src/interrupt.rs -pub mod machine { - use crate::register::{mepc, mstatus}; +pub use riscv_pac::{CoreInterruptNumber, ExceptionNumber, InterruptNumber}; // re-export useful riscv-pac traits - /// Disables all interrupts in the current hart (machine mode). - #[inline] - pub fn disable() { - // SAFETY: It is safe to disable interrupts - unsafe { mstatus::clear_mie() } - } +pub mod machine; +pub mod supervisor; - /// 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() - } - - /// 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, TrapError> 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) => match I::from_number(code) { + Ok(interrupt) => Ok(Trap::Interrupt(interrupt)), + Err(code) => Err(TrapError::InvalidInterrupt(code)), + }, + Trap::Exception(code) => match E::from_number(code) { + Ok(exception) => Ok(Trap::Exception(exception)), + Err(code) => Err(TrapError::InvalidException(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..40233f56 --- /dev/null +++ b/riscv/src/interrupt/machine.rs @@ -0,0 +1,269 @@ +use crate::{ + interrupt::{Trap, TrapError}, + register::{mcause, mepc, mstatus}, +}; +use riscv_pac::{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(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(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, TrapError> { + 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(2)); + assert_eq!(Interrupt::from_number(3), Ok(MachineSoft)); + assert_eq!(Interrupt::from_number(4), Err(4)); + assert_eq!(Interrupt::from_number(5), Ok(SupervisorTimer)); + assert_eq!(Interrupt::from_number(6), Err(6)); + assert_eq!(Interrupt::from_number(7), Ok(MachineTimer)); + assert_eq!(Interrupt::from_number(8), Err(8)); + assert_eq!(Interrupt::from_number(9), Ok(SupervisorExternal)); + assert_eq!(Interrupt::from_number(10), Err(10)); + assert_eq!(Interrupt::from_number(11), Ok(MachineExternal)); + assert_eq!(Interrupt::from_number(12), Err(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(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(14)); + assert_eq!(Exception::from_number(15), Ok(StorePageFault)); + assert_eq!(Exception::from_number(16), Err(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..1d032e6f --- /dev/null +++ b/riscv/src/interrupt/supervisor.rs @@ -0,0 +1,256 @@ +use crate::{ + interrupt::{Trap, TrapError}, + register::{scause, sepc, sstatus}, +}; +use riscv_pac::{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(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(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, TrapError> { + 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(2)); + assert_eq!(Interrupt::from_number(3), Err(3)); + assert_eq!(Interrupt::from_number(4), Err(4)); + assert_eq!(Interrupt::from_number(5), Ok(SupervisorTimer)); + assert_eq!(Interrupt::from_number(6), Err(6)); + assert_eq!(Interrupt::from_number(7), Err(7)); + assert_eq!(Interrupt::from_number(8), Err(8)); + assert_eq!(Interrupt::from_number(9), Ok(SupervisorExternal)); + assert_eq!(Interrupt::from_number(10), Err(10)); + assert_eq!(Interrupt::from_number(11), Err(11)); + assert_eq!(Interrupt::from_number(12), Err(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(10)); + assert_eq!(Exception::from_number(11), Err(11)); + assert_eq!(Exception::from_number(12), Ok(InstructionPageFault)); + assert_eq!(Exception::from_number(13), Ok(LoadPageFault)); + assert_eq!(Exception::from_number(14), Err(14)); + assert_eq!(Exception::from_number(15), Ok(StorePageFault)); + assert_eq!(Exception::from_number(16), Err(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/register/mcause.rs b/riscv/src/register/mcause.rs index 69924b91..fde22af4 100644 --- a/riscv/src/register/mcause.rs +++ b/riscv/src/register/mcause.rs @@ -1,110 +1,6 @@ //! mcause register -use riscv_pac::CoreInterruptNumber; -pub use riscv_pac::{ExceptionNumber, InterruptNumber}; // re-export useful riscv-pac traits - -/// 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(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(value), - } - } -} - -/// Trap Cause -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub enum Trap { - Interrupt(I), - Exception(E), -} - -/// Trap Error -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub enum TrapError { - InvalidInterrupt(usize), - InvalidException(usize), -} +pub use crate::interrupt::Trap; /// mcause register #[derive(Clone, Copy, Debug)] @@ -132,32 +28,21 @@ impl Mcause { self.bits & !(1 << (usize::BITS as usize - 1)) } - /// Try to get the 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 try_cause(&self) -> Result, TrapError> - where - I: CoreInterruptNumber, - E: ExceptionNumber, - { + pub fn cause(&self) -> Trap { if self.is_interrupt() { - match I::from_number(self.code()) { - Ok(interrupt) => Ok(Trap::Interrupt(interrupt)), - Err(code) => Err(TrapError::InvalidInterrupt(code)), - } + Trap::Interrupt(self.code()) } else { - match E::from_number(self.code()) { - Ok(exception) => Ok(Trap::Exception(exception)), - Err(code) => Err(TrapError::InvalidException(code)), - } + Trap::Exception(self.code()) } } - /// Trap Cause - #[inline] - pub fn cause(&self) -> Trap { - self.try_cause().unwrap() - } - /// Is trap cause an interrupt. #[inline] pub fn is_interrupt(&self) -> bool { @@ -172,73 +57,3 @@ impl Mcause { } read_csr_as!(Mcause, 0x342); - -#[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(2)); - assert_eq!(Interrupt::from_number(3), Ok(MachineSoft)); - assert_eq!(Interrupt::from_number(4), Err(4)); - assert_eq!(Interrupt::from_number(5), Ok(SupervisorTimer)); - assert_eq!(Interrupt::from_number(6), Err(6)); - assert_eq!(Interrupt::from_number(7), Ok(MachineTimer)); - assert_eq!(Interrupt::from_number(8), Err(8)); - assert_eq!(Interrupt::from_number(9), Ok(SupervisorExternal)); - assert_eq!(Interrupt::from_number(10), Err(10)); - assert_eq!(Interrupt::from_number(11), Ok(MachineExternal)); - assert_eq!(Interrupt::from_number(12), Err(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(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(14)); - assert_eq!(Exception::from_number(15), Ok(StorePageFault)); - assert_eq!(Exception::from_number(16), Err(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/register/scause.rs b/riscv/src/register/scause.rs index e9d7f5e6..ed6ada60 100644 --- a/riscv/src/register/scause.rs +++ b/riscv/src/register/scause.rs @@ -1,102 +1,7 @@ //! scause register -use riscv_pac::CoreInterruptNumber; -pub use riscv_pac::{ExceptionNumber, InterruptNumber}; // re-export useful riscv-pac traits - -/// 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(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(value), - } - } -} - -/// Trap Cause -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub enum Trap { - Interrupt(I), - Exception(E), -} - -/// Trap Error -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub enum TrapError { - InvalidInterrupt(usize), - InvalidException(usize), -} +pub use crate::interrupt::Trap; +pub use riscv_pac::{CoreInterruptNumber, ExceptionNumber, InterruptNumber}; // re-export useful riscv-pac traits /// scause register #[derive(Clone, Copy)] @@ -117,32 +22,21 @@ impl Scause { self.bits & !(1 << (usize::BITS as usize - 1)) } - /// Try to get the 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 try_cause(&self) -> Result, TrapError> - where - I: CoreInterruptNumber, - E: ExceptionNumber, - { + pub fn cause(&self) -> Trap { if self.is_interrupt() { - match I::from_number(self.code()) { - Ok(interrupt) => Ok(Trap::Interrupt(interrupt)), - Err(code) => Err(TrapError::InvalidInterrupt(code)), - } + Trap::Interrupt(self.code()) } else { - match E::from_number(self.code()) { - Ok(exception) => Ok(Trap::Exception(exception)), - Err(code) => Err(TrapError::InvalidException(code)), - } + Trap::Exception(self.code()) } } - /// Trap Cause - #[inline] - pub fn cause(&self) -> Trap { - self.try_cause().unwrap() - } - /// Is trap cause an interrupt. #[inline] pub fn is_interrupt(&self) -> bool { @@ -176,69 +70,3 @@ pub unsafe fn set(cause: Trap) }; _write(bits); } - -#[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(2)); - assert_eq!(Interrupt::from_number(3), Err(3)); - assert_eq!(Interrupt::from_number(4), Err(4)); - assert_eq!(Interrupt::from_number(5), Ok(SupervisorTimer)); - assert_eq!(Interrupt::from_number(6), Err(6)); - assert_eq!(Interrupt::from_number(7), Err(7)); - assert_eq!(Interrupt::from_number(8), Err(8)); - assert_eq!(Interrupt::from_number(9), Ok(SupervisorExternal)); - assert_eq!(Interrupt::from_number(10), Err(10)); - assert_eq!(Interrupt::from_number(11), Err(11)); - assert_eq!(Interrupt::from_number(12), Err(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(10)); - assert_eq!(Exception::from_number(11), Err(11)); - assert_eq!(Exception::from_number(12), Ok(InstructionPageFault)); - assert_eq!(Exception::from_number(13), Ok(LoadPageFault)); - assert_eq!(Exception::from_number(14), Err(14)); - assert_eq!(Exception::from_number(15), Ok(StorePageFault)); - assert_eq!(Exception::from_number(16), Err(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) - } -} From 15b42b820592e0498f984957eaf193d2a2c5b480 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rom=C3=A1n=20C=C3=A1rdenas=20Rodr=C3=ADguez?= Date: Fri, 7 Jun 2024 18:51:52 +0200 Subject: [PATCH 11/17] adapt riscv-rt to new approach --- riscv-rt/src/lib.rs | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/riscv-rt/src/lib.rs b/riscv-rt/src/lib.rs index 0ffb3441..14d91c28 100644 --- a/riscv-rt/src/lib.rs +++ b/riscv-rt/src/lib.rs @@ -523,23 +523,21 @@ pub unsafe extern "C" fn start_trap_rust(trap_frame: *const TrapFrame) { fn _dispatch_interrupt(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); + match xcause::read().cause() { + xcause::Trap::Interrupt(code) => _dispatch_interrupt(code), + xcause::Trap::Exception(code) => { + 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 { - ExceptionHandler(trap_frame); } - } else { - _dispatch_interrupt(code); } } From d7d7cb97ab8d9640b16cd3313ded86583f3cd15f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rom=C3=A1n=20C=C3=A1rdenas=20Rodr=C3=ADguez?= Date: Wed, 26 Jun 2024 16:02:25 +0200 Subject: [PATCH 12/17] Expand macro for interrupt --- .github/workflows/riscv-pac.yaml | 6 +- .github/workflows/riscv-peripheral.yaml | 2 +- .github/workflows/riscv-semihosting.yaml | 4 +- .github/workflows/riscv.yaml | 6 +- riscv-pac/Cargo.toml | 2 +- riscv-pac/macros/Cargo.toml | 1 + riscv-pac/macros/src/lib.rs | 290 ++++++++++++++---- riscv-pac/tests/ui/fail_empty_macro.stderr | 4 +- .../tests/ui/fail_more_than_one_trait.rs | 9 - .../tests/ui/fail_more_than_one_trait.stderr | 7 - riscv-pac/tests/ui/fail_no_unsafe.stderr | 8 +- riscv-pac/tests/ui/fail_unknown_trait.rs | 2 +- riscv-pac/tests/ui/fail_unknown_trait.stderr | 10 +- riscv-pac/tests/ui/pass_test.rs | 38 ++- riscv-peripheral/examples/e310x.rs | 2 +- riscv-peripheral/src/plic.rs | 4 +- riscv-rt/CHANGELOG.md | 2 +- riscv-rt/macros/Cargo.toml | 1 + riscv-rt/src/lib.rs | 8 +- riscv-semihosting/CHANGELOG.md | 1 + riscv-semihosting/Cargo.toml | 2 +- riscv/CHANGELOG.md | 1 + riscv/Cargo.toml | 2 +- 23 files changed, 296 insertions(+), 116 deletions(-) delete mode 100644 riscv-pac/tests/ui/fail_more_than_one_trait.rs delete mode 100644 riscv-pac/tests/ui/fail_more_than_one_trait.stderr diff --git a/.github/workflows/riscv-pac.yaml b/.github/workflows/riscv-pac.yaml index 9ab4c5db..a7dfe5af 100644 --- a/.github/workflows/riscv-pac.yaml +++ b/.github/workflows/riscv-pac.yaml @@ -1,6 +1,6 @@ on: push: - branches: [ master, riscv-pac ] + branches: [ master ] pull_request: merge_group: @@ -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-peripheral.yaml b/.github/workflows/riscv-peripheral.yaml index ce2b45e6..57b986ab 100644 --- a/.github/workflows/riscv-peripheral.yaml +++ b/.github/workflows/riscv-peripheral.yaml @@ -1,6 +1,6 @@ on: push: - branches: [ master, riscv-pac ] + branches: [ master ] pull_request: merge_group: 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 b52145a5..28e72126 100644 --- a/.github/workflows/riscv.yaml +++ b/.github/workflows/riscv.yaml @@ -1,6 +1,6 @@ on: push: - branches: [ master, riscv-pac ] + branches: [ master ] pull_request: merge_group: @@ -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-pac/Cargo.toml b/riscv-pac/Cargo.toml index 22d247ef..61c300c1 100644 --- a/riscv-pac/Cargo.toml +++ b/riscv-pac/Cargo.toml @@ -2,7 +2,7 @@ name = "riscv-pac" version = "0.1.2" 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/macros/Cargo.toml b/riscv-pac/macros/Cargo.toml index 233a6616..c559182f 100644 --- a/riscv-pac/macros/Cargo.toml +++ b/riscv-pac/macros/Cargo.toml @@ -10,6 +10,7 @@ license = "MIT OR Apache-2.0" name = "riscv-pac-macros" repository = "https://github.com/rust-embedded/riscv" version = "0.1.0" +edition = "2021" [lib] proc-macro = true diff --git a/riscv-pac/macros/src/lib.rs b/riscv-pac/macros/src/lib.rs index 4f783db8..01090b1d 100644 --- a/riscv-pac/macros/src/lib.rs +++ b/riscv-pac/macros/src/lib.rs @@ -1,77 +1,214 @@ -extern crate proc_macro; -extern crate proc_macro2; -extern crate quote; -extern crate syn; - use proc_macro::TokenStream; use proc_macro2::TokenStream as TokenStream2; use quote::quote; +use std::collections::HashMap; use std::str::FromStr; -use syn::{parse_macro_input, Data, DeriveInput, Ident}; +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'", + )), + } + } +} -struct PacNumberEnum { +/// 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, - numbers: Vec<(Ident, usize)>, + /// The maximum discriminant value + max_number: usize, + /// A map from discriminant values to variant names + numbers: HashMap, } -impl PacNumberEnum { +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"), }; - let numbers = variants - .iter() - .map(|variant| { - let ident = &variant.ident; - let value = match &variant.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"), + 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!("Variant must have a discriminant"), - }; - (ident.clone(), value) - }) - .collect(); + _ => 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, numbers } + Self { + name, + max_number, + numbers, + } } + /// Returns a token stream representing the maximum discriminant value of the enum fn max_discriminant(&self) -> TokenStream2 { - let max_discriminant = self.numbers.iter().map(|(_, num)| num).max().unwrap(); - TokenStream2::from_str(&format!("{max_discriminant}")).unwrap() + 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(|(ident, num)| { + .map(|(num, ident)| { TokenStream2::from_str(&format!("{num} => Ok(Self::{ident})")).unwrap() }) .collect() } - fn quote(&self, trait_name: &str, num_type: &str, const_name: &str) -> TokenStream2 { + /// 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 + } + + /// 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(); - let trait_name = TokenStream2::from_str(trait_name).unwrap(); - let num_type = TokenStream2::from_str(num_type).unwrap(); - let const_name = TokenStream2::from_str(const_name).unwrap(); - - quote! { + // Push the trait implementation + res.push(quote! { unsafe impl riscv_pac::#trait_name for #name { const #const_name: #num_type = #max_discriminant; @@ -88,7 +225,53 @@ impl PacNumberEnum { } } } + }); + + // 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_pac::#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() { + let h = &#isr_array_name[code]; + if let Some(handler) = h { + handler(); + } else { + DefaultHandler(); + } + } else { + DefaultHandler(); + } + } + }); } + + res } } @@ -131,35 +314,14 @@ impl PacNumberEnum { #[proc_macro_attribute] pub fn pac_enum(attr: TokenStream, item: TokenStream) -> TokenStream { let input = parse_macro_input!(item as DeriveInput); - let pac_enum = PacNumberEnum::new(&input); - - // attr should be unsafe ExceptionNumber, unsafe InterruptNumber, unsafe PriorityNumber, or unsafe HartIdNumber - // assert that attribute starts with the unsafe token. If not, raise a panic error - let attr = attr.to_string(); - // split string into words and check if the first word is "unsafe" - let attrs = attr.split_whitespace().collect::>(); - if attrs.is_empty() { - panic!("Attribute is empty. Expected: 'riscv_pac::pac_enum(unsafe )'"); - } - if attrs.len() > 2 { - panic!( - "Wrong attribute format. Expected: 'riscv_pac::pac_enum(unsafe )'" - ); - } - if attrs[0] != "unsafe" { - panic!("Attribute does not start with 'unsafe'. Expected: 'riscv_pac::pac_enum(unsafe )'"); - } + let pac_enum = PacEnumItem::new(&input); + + let attr = parse_macro_input!(attr as PacTrait); - let trait_impl = match attrs[1] { - "ExceptionNumber" => pac_enum.quote("ExceptionNumber", "usize", "MAX_EXCEPTION_NUMBER"), - "InterruptNumber" => pac_enum.quote("InterruptNumber", "usize", "MAX_INTERRUPT_NUMBER"), - "PriorityNumber" => pac_enum.quote("PriorityNumber", "u8", "MAX_PRIORITY_NUMBER"), - "HartIdNumber" => pac_enum.quote("HartIdNumber", "u16", "MAX_HART_ID_NUMBER"), - _ => panic!("Unknown trait '{}'. Expected: 'ExceptionNumber', 'InterruptNumber', 'PriorityNumber', or 'HartIdNumber'", attrs[1]), - }; + let trait_impl = pac_enum.impl_trait(&attr); quote! { #input - #trait_impl + #(#trait_impl)* } .into() } diff --git a/riscv-pac/tests/ui/fail_empty_macro.stderr b/riscv-pac/tests/ui/fail_empty_macro.stderr index 37859747..22211826 100644 --- a/riscv-pac/tests/ui/fail_empty_macro.stderr +++ b/riscv-pac/tests/ui/fail_empty_macro.stderr @@ -1,7 +1,7 @@ -error: custom attribute panicked +error: unexpected end of input, expected `unsafe` --> tests/ui/fail_empty_macro.rs:1:1 | 1 | #[riscv_pac::pac_enum] | ^^^^^^^^^^^^^^^^^^^^^^ | - = help: message: Attribute is empty. Expected: 'riscv_pac::pac_enum(unsafe )' + = note: this error originates in the attribute macro `riscv_pac::pac_enum` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/riscv-pac/tests/ui/fail_more_than_one_trait.rs b/riscv-pac/tests/ui/fail_more_than_one_trait.rs deleted file mode 100644 index 7db5629e..00000000 --- a/riscv-pac/tests/ui/fail_more_than_one_trait.rs +++ /dev/null @@ -1,9 +0,0 @@ -#[riscv_pac::pac_enum(unsafe InterruptNumber, unsafe PriorityNumber)] -#[derive(Clone, Copy, Debug, PartialEq)] -enum Interrupt { - I1 = 1, - I2 = 2, - I4 = 4, -} - -fn main() {} diff --git a/riscv-pac/tests/ui/fail_more_than_one_trait.stderr b/riscv-pac/tests/ui/fail_more_than_one_trait.stderr deleted file mode 100644 index 881d9f7a..00000000 --- a/riscv-pac/tests/ui/fail_more_than_one_trait.stderr +++ /dev/null @@ -1,7 +0,0 @@ -error: custom attribute panicked - --> tests/ui/fail_more_than_one_trait.rs:1:1 - | -1 | #[riscv_pac::pac_enum(unsafe InterruptNumber, unsafe PriorityNumber)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = help: message: Wrong attribute format. Expected: 'riscv_pac::pac_enum(unsafe )' diff --git a/riscv-pac/tests/ui/fail_no_unsafe.stderr b/riscv-pac/tests/ui/fail_no_unsafe.stderr index e459a17c..a12dfad7 100644 --- a/riscv-pac/tests/ui/fail_no_unsafe.stderr +++ b/riscv-pac/tests/ui/fail_no_unsafe.stderr @@ -1,7 +1,5 @@ -error: custom attribute panicked - --> tests/ui/fail_no_unsafe.rs:1:1 +error: expected `unsafe` + --> tests/ui/fail_no_unsafe.rs:1:23 | 1 | #[riscv_pac::pac_enum(InterruptNumber)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = help: message: Attribute does not start with 'unsafe'. Expected: 'riscv_pac::pac_enum(unsafe )' + | ^^^^^^^^^^^^^^^ diff --git a/riscv-pac/tests/ui/fail_unknown_trait.rs b/riscv-pac/tests/ui/fail_unknown_trait.rs index 50192085..82ebdc52 100644 --- a/riscv-pac/tests/ui/fail_unknown_trait.rs +++ b/riscv-pac/tests/ui/fail_unknown_trait.rs @@ -1,4 +1,4 @@ -#[riscv_pac::pac_enum(unsafe CoreInterruptNumber)] +#[riscv_pac::pac_enum(unsafe InterruptNumber)] #[derive(Clone, Copy, Debug, PartialEq)] enum Interrupt { I1 = 1, diff --git a/riscv-pac/tests/ui/fail_unknown_trait.stderr b/riscv-pac/tests/ui/fail_unknown_trait.stderr index 5ca1ce48..4f03439d 100644 --- a/riscv-pac/tests/ui/fail_unknown_trait.stderr +++ b/riscv-pac/tests/ui/fail_unknown_trait.stderr @@ -1,7 +1,5 @@ -error: custom attribute panicked - --> tests/ui/fail_unknown_trait.rs:1:1 +error: Unknown trait name. Expected: 'ExceptionNumber', 'CoreInterruptNumber', 'ExternalInterruptNumber', 'PriorityNumber', or 'HartIdNumber' + --> tests/ui/fail_unknown_trait.rs:1:30 | -1 | #[riscv_pac::pac_enum(unsafe CoreInterruptNumber)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = help: message: Unknown trait 'CoreInterruptNumber'. Expected: 'ExceptionNumber', 'InterruptNumber', 'PriorityNumber', or 'HartIdNumber' +1 | #[riscv_pac::pac_enum(unsafe InterruptNumber)] + | ^^^^^^^^^^^^^^^ diff --git a/riscv-pac/tests/ui/pass_test.rs b/riscv-pac/tests/ui/pass_test.rs index 2be913eb..07141391 100644 --- a/riscv-pac/tests/ui/pass_test.rs +++ b/riscv-pac/tests/ui/pass_test.rs @@ -9,7 +9,7 @@ enum Exception { } #[repr(usize)] -#[pac_enum(unsafe InterruptNumber)] +#[pac_enum(unsafe CoreInterruptNumber)] #[derive(Clone, Copy, Debug, Eq, PartialEq)] enum Interrupt { I1 = 1, @@ -37,6 +37,23 @@ enum HartId { 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); @@ -47,6 +64,8 @@ fn main() { assert_eq!(Exception::from_number(3), Ok(Exception::E3)); assert_eq!(Exception::from_number(4), Err(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); @@ -61,6 +80,19 @@ fn main() { assert_eq!(Interrupt::from_number(6), Err(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); @@ -72,6 +104,8 @@ fn main() { assert_eq!(Priority::from_number(3), Ok(Priority::P3)); assert_eq!(Priority::from_number(4), Err(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); @@ -80,4 +114,6 @@ fn main() { assert_eq!(HartId::from_number(1), Ok(HartId::H1)); assert_eq!(HartId::from_number(2), Ok(HartId::H2)); assert_eq!(HartId::from_number(3), Err(3)); + + assert_eq!(HartId::MAX_HART_ID_NUMBER, 2); } diff --git a/riscv-peripheral/examples/e310x.rs b/riscv-peripheral/examples/e310x.rs index 00288680..970108c1 100644 --- a/riscv-peripheral/examples/e310x.rs +++ b/riscv-peripheral/examples/e310x.rs @@ -2,8 +2,8 @@ //! This is a simple example of how to use the `riscv-peripheral` crate to generate //! peripheral definitions for a target. +use riscv_pac::pac_enum; use riscv_pac::result::{Error, Result}; -use riscv_pac::{pac_enum, ExternalInterruptNumber}; #[repr(u16)] #[pac_enum(unsafe HartIdNumber)] diff --git a/riscv-peripheral/src/plic.rs b/riscv-peripheral/src/plic.rs index cd329d31..d8e32ffc 100644 --- a/riscv-peripheral/src/plic.rs +++ b/riscv-peripheral/src/plic.rs @@ -148,7 +148,7 @@ pub(crate) mod test { use riscv_pac::result::{Error, Result}; use riscv_pac::{ExternalInterruptNumber, HartIdNumber, InterruptNumber, PriorityNumber}; - #[pac_enum(unsafe InterruptNumber)] + #[pac_enum(unsafe ExternalInterruptNumber)] #[derive(Clone, Copy, Debug, Eq, PartialEq)] #[repr(usize)] pub(crate) enum Interrupt { @@ -158,8 +158,6 @@ pub(crate) mod test { I4 = 4, } - unsafe impl ExternalInterruptNumber for Interrupt {} - #[pac_enum(unsafe PriorityNumber)] #[derive(Clone, Copy, Debug, Eq, PartialEq)] #[repr(u8)] diff --git a/riscv-rt/CHANGELOG.md b/riscv-rt/CHANGELOG.md index 59b407c1..9a0a8e6c 100644 --- a/riscv-rt/CHANGELOG.md +++ b/riscv-rt/CHANGELOG.md @@ -21,7 +21,7 @@ If `v-trap` feature is enabled, this macro also generates its corresponding 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`. +to `_dispatch_core_interrupt`. - Upgrade rust-version to 1.61 - Update `syn` to version 2.0 diff --git a/riscv-rt/macros/Cargo.toml b/riscv-rt/macros/Cargo.toml index 19f6690d..c2ace408 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/lib.rs b/riscv-rt/src/lib.rs index 14d91c28..142728d8 100644 --- a/riscv-rt/src/lib.rs +++ b/riscv-rt/src/lib.rs @@ -520,11 +520,11 @@ pub struct TrapFrame { 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); } match xcause::read().cause() { - xcause::Trap::Interrupt(code) => _dispatch_interrupt(code), + xcause::Trap::Interrupt(code) => _dispatch_core_interrupt(code), xcause::Trap::Exception(code) => { let trap_frame = &*trap_frame; if code < __EXCEPTIONS.len() { @@ -579,8 +579,8 @@ pub static __EXCEPTIONS: [Option; 16] = [ Some(StorePageFault), ]; -#[export_name = "_dispatch_interrupt"] -unsafe extern "C" fn dispatch_interrupt(code: usize) { +#[export_name = "_dispatch_core_interrupt"] +unsafe extern "C" fn dispatch_core_interrupt(code: usize) { extern "C" { fn DefaultHandler(); } diff --git a/riscv-semihosting/CHANGELOG.md b/riscv-semihosting/CHANGELOG.md index 17351113..64daca16 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 MSRV to 1.61. - Made `cfg` variable selection more robust for custom targets - Fixed debug::exit() on riscv64 QEMU simulation diff --git a/riscv-semihosting/Cargo.toml b/riscv-semihosting/Cargo.toml index 8b715436..7a590bda 100644 --- a/riscv-semihosting/Cargo.toml +++ b/riscv-semihosting/Cargo.toml @@ -14,7 +14,7 @@ readme = "README.md" repository = "https://github.com/riscv-rust/riscv" version = "0.1.0" edition = "2021" -rust-version = "1.60.0" +rust-version = "1.61.0" [features] u-mode = [] diff --git a/riscv/CHANGELOG.md b/riscv/CHANGELOG.md index b47ff4ad..980ae6f2 100644 --- a/riscv/CHANGELOG.md +++ b/riscv/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Added +- 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 diff --git a/riscv/Cargo.toml b/riscv/Cargo.toml index e55ece39..5e37ddd5 100644 --- a/riscv/Cargo.toml +++ b/riscv/Cargo.toml @@ -2,7 +2,7 @@ name = "riscv" version = "0.11.2" 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"] From 1dd02501bef62df0f2d984a1be8787db22b4e286 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rom=C3=A1n=20C=C3=A1rdenas=20Rodr=C3=ADguez?= Date: Thu, 27 Jun 2024 17:40:15 +0200 Subject: [PATCH 13/17] Add vector table --- riscv-pac/macros/src/lib.rs | 40 ++++++++++++++++++++++ riscv-rt/src/asm.rs | 32 ----------------- riscv-rt/src/interrupt.rs | 68 +++++++++++++++++++++++++++++++++++++ riscv-rt/src/lib.rs | 46 ++----------------------- riscv/README.md | 2 +- 5 files changed, 111 insertions(+), 77 deletions(-) create mode 100644 riscv-rt/src/interrupt.rs diff --git a/riscv-pac/macros/src/lib.rs b/riscv-pac/macros/src/lib.rs index 01090b1d..7924372a 100644 --- a/riscv-pac/macros/src/lib.rs +++ b/riscv-pac/macros/src/lib.rs @@ -192,6 +192,42 @@ impl PacEnumItem { vectors } + fn vector_table(&self) -> TokenStream2 { + let mut asm = String::from( + r#" +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. @@ -269,6 +305,10 @@ impl PacEnumItem { } } }); + + if let InterruptType::Core = interrupt_type { + res.push(self.vector_table()); + } } res 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/interrupt.rs b/riscv-rt/src/interrupt.rs new file mode 100644 index 00000000..01f23eb9 --- /dev/null +++ b/riscv-rt/src/interrupt.rs @@ -0,0 +1,68 @@ +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"] +unsafe extern "C" fn dispatch_core_interrupt(code: usize) { + if code < __CORE_INTERRUPTS.len() { + let h = &__CORE_INTERRUPTS[code]; + if let Some(handler) = h { + handler(); + } else { + 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 142728d8..bffe9037 100644 --- a/riscv-rt/src/lib.rs +++ b/riscv-rt/src/lib.rs @@ -460,6 +460,8 @@ #[cfg(riscv)] mod asm; +mod interrupt; + #[cfg(feature = "s-mode")] use riscv::register::scause as xcause; @@ -578,47 +580,3 @@ pub static __EXCEPTIONS: [Option; 16] = [ None, Some(StorePageFault), ]; - -#[export_name = "_dispatch_core_interrupt"] -unsafe extern "C" fn dispatch_core_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/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 From 7c54dab231d35bf9b85e91ce0fc6c8a4c84b5f76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rom=C3=A1n=20C=C3=A1rdenas=20Rodr=C3=ADguez?= Date: Thu, 4 Jul 2024 08:34:34 +0200 Subject: [PATCH 14/17] Update riscv-rt/src/lib.rs Co-authored-by: rmsyn <117854522+rmsyn@users.noreply.github.com> --- riscv-rt/src/lib.rs | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/riscv-rt/src/lib.rs b/riscv-rt/src/lib.rs index bffe9037..178abdb1 100644 --- a/riscv-rt/src/lib.rs +++ b/riscv-rt/src/lib.rs @@ -527,17 +527,13 @@ pub unsafe extern "C" fn start_trap_rust(trap_frame: *const TrapFrame) { match xcause::read().cause() { xcause::Trap::Interrupt(code) => _dispatch_core_interrupt(code), - xcause::Trap::Exception(code) => { - 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); + xcause::Trap::Exception(code) if code < __EXCEPTIONS.len() => { + match __EXCEPTIONS[code].as_ref() { + Some(handler) => handler(&*trap_frame), + None => ExceptionHandler(&*trap_frame), + } + } + xcause::Trap::Exception(_) => ExceptionHandler(&*trap_frame), } } } From 4b800a235a88f6cb571b1f08af1fd1bad7f216dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rom=C3=A1n=20C=C3=A1rdenas=20Rodr=C3=ADguez?= Date: Thu, 4 Jul 2024 19:32:19 +0200 Subject: [PATCH 15/17] add default feature to riscv --- riscv-rt/src/lib.rs | 2 -- riscv/Cargo.toml | 1 + 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/riscv-rt/src/lib.rs b/riscv-rt/src/lib.rs index 178abdb1..8fb212e0 100644 --- a/riscv-rt/src/lib.rs +++ b/riscv-rt/src/lib.rs @@ -534,8 +534,6 @@ pub unsafe extern "C" fn start_trap_rust(trap_frame: *const TrapFrame) { } } xcause::Trap::Exception(_) => ExceptionHandler(&*trap_frame), - } - } } } diff --git a/riscv/Cargo.toml b/riscv/Cargo.toml index 5e37ddd5..20b10518 100644 --- a/riscv/Cargo.toml +++ b/riscv/Cargo.toml @@ -20,6 +20,7 @@ targets = [ ] [features] +default = ["riscv-pac/default"] s-mode = [] critical-section-single-hart = ["critical-section/restore-state-bool"] From 3f8b00c4c3770081ca7796a492b0823e0d35ead8 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 16:48:46 +0200 Subject: [PATCH 16/17] adapt to master --- riscv-pac/Cargo.toml | 9 -- riscv-pac/src/lib.rs | 102 ++++++++---------- riscv-pac/tests/ui/fail_empty_macro.stderr | 7 -- riscv-pac/tests/ui/fail_no_unsafe.stderr | 5 - riscv-peripheral/Cargo.toml | 5 +- riscv-peripheral/examples/e310x.rs | 26 ++--- riscv-peripheral/src/aclint/mswi.rs | 13 --- riscv-peripheral/src/aclint/sswi.rs | 31 ------ riscv-peripheral/src/lib.rs | 1 + riscv-peripheral/src/macros.rs | 54 +++++++--- riscv-peripheral/src/plic.rs | 73 +++++++------ riscv/Cargo.toml | 8 +- {riscv-pac => riscv}/macros/Cargo.toml | 6 +- {riscv-pac => riscv}/macros/src/lib.rs | 11 +- riscv/src/interrupt.rs | 19 ++-- riscv/src/interrupt/machine.rs | 35 +++--- riscv/src/interrupt/supervisor.rs | 43 ++++---- riscv/src/lib.rs | 4 + {riscv-pac => riscv}/tests/test.rs | 0 .../tests/ui/fail_empty_macro.rs | 2 +- riscv/tests/ui/fail_empty_macro.stderr | 7 ++ .../tests/ui/fail_no_unsafe.rs | 2 +- riscv/tests/ui/fail_no_unsafe.stderr | 5 + .../tests/ui/fail_unknown_trait.rs | 2 +- .../tests/ui/fail_unknown_trait.stderr | 6 +- {riscv-pac => riscv}/tests/ui/pass_test.rs | 25 ++--- 26 files changed, 232 insertions(+), 269 deletions(-) delete mode 100644 riscv-pac/tests/ui/fail_empty_macro.stderr delete mode 100644 riscv-pac/tests/ui/fail_no_unsafe.stderr rename {riscv-pac => riscv}/macros/Cargo.toml (75%) rename {riscv-pac => riscv}/macros/src/lib.rs (96%) rename {riscv-pac => riscv}/tests/test.rs (100%) rename {riscv-pac => riscv}/tests/ui/fail_empty_macro.rs (82%) create mode 100644 riscv/tests/ui/fail_empty_macro.stderr rename {riscv-pac => riscv}/tests/ui/fail_no_unsafe.rs (73%) create mode 100644 riscv/tests/ui/fail_no_unsafe.stderr rename {riscv-pac => riscv}/tests/ui/fail_unknown_trait.rs (70%) rename {riscv-pac => riscv}/tests/ui/fail_unknown_trait.stderr (50%) rename {riscv-pac => riscv}/tests/ui/pass_test.rs (79%) diff --git a/riscv-pac/Cargo.toml b/riscv-pac/Cargo.toml index 61c300c1..6a5c6210 100644 --- a/riscv-pac/Cargo.toml +++ b/riscv-pac/Cargo.toml @@ -17,12 +17,3 @@ targets = [ "riscv32i-unknown-none-elf", "riscv32imc-unknown-none-elf", "riscv32imac-unknown-none-elf", "riscv64imac-unknown-none-elf", "riscv64gc-unknown-none-elf", ] - -[dependencies] -riscv-pac-macros = { path = "macros", version = "0.1.0", optional = true } - -[features] -default = ["riscv-pac-macros"] - -[dev-dependencies] -trybuild = "1.0" diff --git a/riscv-pac/src/lib.rs b/riscv-pac/src/lib.rs index 51ed272f..935d5200 100644 --- a/riscv-pac/src/lib.rs +++ b/riscv-pac/src/lib.rs @@ -4,9 +4,6 @@ pub mod result; use result::Result; -#[cfg(feature = "riscv-pac-macros")] -pub use riscv_pac_macros::*; - /// Trait for enums of target-specific exception numbers. /// /// This trait should be implemented by a peripheral access crate (PAC) on its enum of available @@ -136,24 +133,22 @@ pub unsafe trait HartIdNumber: Copy { #[cfg(test)] mod test { use super::*; + use crate::result::Error; - #[derive(Clone, Copy, Debug, Eq, PartialEq, ExceptionNumber)] - #[repr(u16)] + #[derive(Clone, Copy, Debug, Eq, PartialEq)] enum Exception { E1 = 1, E3 = 3, } - #[derive(Clone, Copy, Debug, Eq, PartialEq, InterruptNumber)] - #[repr(u16)] + #[derive(Clone, Copy, Debug, Eq, PartialEq)] enum Interrupt { I1 = 1, I2 = 2, I4 = 4, } - #[derive(Clone, Copy, Debug, Eq, PartialEq, PriorityNumber)] - #[repr(u8)] + #[derive(Clone, Copy, Debug, Eq, PartialEq)] enum Priority { P0 = 0, P1 = 1, @@ -161,50 +156,46 @@ mod test { P3 = 3, } - #[derive(Clone, Copy, Debug, Eq, PartialEq, HartIdNumber)] - #[repr(u16)] - enum Context { - C0 = 0, - C1 = 1, - C2 = 2, + #[derive(Clone, Copy, Debug, Eq, PartialEq)] + enum HartId { + H0 = 0, + H1 = 1, + H2 = 2, } unsafe impl ExceptionNumber for Exception { - const MAX_EXCEPTION_NUMBER: u16 = Self::E3 as u16; + const MAX_EXCEPTION_NUMBER: usize = Self::E3 as usize; #[inline] - fn number(self) -> u16 { + fn number(self) -> usize { self as _ } #[inline] - fn from_number(number: u16) -> Result { - if number > Self::MAX_EXCEPTION_NUMBER || number == 0 { - Err(number) - } else if number == 1 || number == 3 { - // SAFETY: valid exception number - Ok(unsafe { core::mem::transmute(number) }) - } else { - Err(number) + 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: u16 = Self::I4 as u16; + const MAX_INTERRUPT_NUMBER: usize = Self::I4 as usize; #[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(number) - } 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), + 4 => Ok(Interrupt::I4), + _ => Err(Error::InvalidVariant(number)), } } } @@ -218,18 +209,19 @@ mod test { } #[inline] - fn from_number(number: u8) -> Result { - if number > Self::MAX_PRIORITY_NUMBER { - Err(number) - } else { - // SAFETY: valid priority number - Ok(unsafe { core::mem::transmute(number) }) + 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 Context { - const MAX_HART_ID_NUMBER: u16 = Self::C2 as u16; + unsafe impl HartIdNumber for HartId { + const MAX_HART_ID_NUMBER: u16 = Self::H2 as u16; #[inline] fn number(self) -> u16 { @@ -237,12 +229,12 @@ mod test { } #[inline] - fn from_number(number: u16) -> Result { - if number > Self::MAX_HART_ID_NUMBER { - Err(number) - } else { - // SAFETY: valid context number - Ok(unsafe { core::mem::transmute(number) }) + 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 _)), } } } @@ -252,11 +244,11 @@ mod test { assert_eq!(Exception::E1.number(), 1); assert_eq!(Exception::E3.number(), 3); - assert_eq!(Exception::from_number(0), Err(0)); + 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(2)); + 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(4)); + assert_eq!(Exception::from_number(4), Err(Error::InvalidVariant(4))); } #[test] @@ -265,12 +257,12 @@ mod test { assert_eq!(Interrupt::I2.number(), 2); assert_eq!(Interrupt::I4.number(), 4); - assert_eq!(Interrupt::from_number(0), Err(0)); + 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(3)); + 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(5)); + assert_eq!(Interrupt::from_number(5), Err(Error::InvalidVariant(5))); } #[test] @@ -284,7 +276,7 @@ mod test { assert_eq!(Priority::from_number(1), Ok(Priority::P1)); assert_eq!(Priority::from_number(2), Ok(Priority::P2)); assert_eq!(Priority::from_number(3), Ok(Priority::P3)); - assert_eq!(Priority::from_number(4), Err(4)); + assert_eq!(Priority::from_number(4), Err(Error::InvalidVariant(4))); } #[test] @@ -296,6 +288,6 @@ mod test { assert_eq!(HartId::from_number(0), Ok(HartId::H0)); assert_eq!(HartId::from_number(1), Ok(HartId::H1)); assert_eq!(HartId::from_number(2), Ok(HartId::H2)); - assert_eq!(HartId::from_number(3), Err(3)); + assert_eq!(HartId::from_number(3), Err(Error::InvalidVariant(3))); } } diff --git a/riscv-pac/tests/ui/fail_empty_macro.stderr b/riscv-pac/tests/ui/fail_empty_macro.stderr deleted file mode 100644 index 22211826..00000000 --- a/riscv-pac/tests/ui/fail_empty_macro.stderr +++ /dev/null @@ -1,7 +0,0 @@ -error: unexpected end of input, expected `unsafe` - --> tests/ui/fail_empty_macro.rs:1:1 - | -1 | #[riscv_pac::pac_enum] - | ^^^^^^^^^^^^^^^^^^^^^^ - | - = note: this error originates in the attribute macro `riscv_pac::pac_enum` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/riscv-pac/tests/ui/fail_no_unsafe.stderr b/riscv-pac/tests/ui/fail_no_unsafe.stderr deleted file mode 100644 index a12dfad7..00000000 --- a/riscv-pac/tests/ui/fail_no_unsafe.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: expected `unsafe` - --> tests/ui/fail_no_unsafe.rs:1:23 - | -1 | #[riscv_pac::pac_enum(InterruptNumber)] - | ^^^^^^^^^^^^^^^ diff --git a/riscv-peripheral/Cargo.toml b/riscv-peripheral/Cargo.toml index c4461641..b719e73d 100644 --- a/riscv-peripheral/Cargo.toml +++ b/riscv-peripheral/Cargo.toml @@ -16,12 +16,11 @@ license = "ISC" [dependencies] embedded-hal = "1.0.0" embedded-hal-async = { version = "1.0.0", optional = true } -riscv = { path = "../riscv", version = "0.11.2" } -riscv-pac = { path = "../riscv-pac", version = "0.1.2", default-features = false } +riscv = { path = "../riscv", version = "0.11.2", default-features = false } +riscv-pac = { path = "../riscv-pac", version = "0.1.2" } [dev-dependencies] heapless = "0.8.0" -riscv-pac = { path = "../riscv-pac", version = "0.1.2", default-features = true } [features] aclint-hal-async = ["embedded-hal-async"] diff --git a/riscv-peripheral/examples/e310x.rs b/riscv-peripheral/examples/e310x.rs index 970108c1..65d237e8 100644 --- a/riscv-peripheral/examples/e310x.rs +++ b/riscv-peripheral/examples/e310x.rs @@ -2,11 +2,11 @@ //! This is a simple example of how to use the `riscv-peripheral` crate to generate //! peripheral definitions for a target. -use riscv_pac::pac_enum; -use riscv_pac::result::{Error, Result}; +use riscv_pac::{ + result::{Error, Result}, + ExternalInterruptNumber, HartIdNumber, InterruptNumber, PriorityNumber, +}; -#[repr(u16)] -#[pac_enum(unsafe HartIdNumber)] #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum HartId { H0 = 0, @@ -22,11 +22,9 @@ 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(HartId::H0), + _ => Err(Error::InvalidVariant(number as usize)), } } } @@ -88,28 +86,26 @@ pub enum Interrupt { } unsafe impl InterruptNumber for Interrupt { - const MAX_INTERRUPT_NUMBER: u16 = Self::I2C0 as u16; + const MAX_INTERRUPT_NUMBER: usize = Self::I2C0 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 == 0 || number > Self::MAX_INTERRUPT_NUMBER { Err(Error::InvalidVariant(number as usize)) } else { // SAFETY: valid interrupt number - Ok(unsafe { core::mem::transmute(number) }) + Ok(unsafe { core::mem::transmute(number as u8) }) } } } unsafe impl ExternalInterruptNumber for Interrupt {} -#[repr(u8)] -#[pac_enum(unsafe PriorityNumber)] #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum Priority { P0 = 0, diff --git a/riscv-peripheral/src/aclint/mswi.rs b/riscv-peripheral/src/aclint/mswi.rs index 4e85121a..3af4af75 100644 --- a/riscv-peripheral/src/aclint/mswi.rs +++ b/riscv-peripheral/src/aclint/mswi.rs @@ -35,19 +35,6 @@ impl MSWI { // SAFETY: `hart_id` is valid for the target unsafe { MSIP::new(self.msip0.get_ptr().offset(hart_id.number() as _) as _) } } - - /// Returns the `MSIP` register for the current HART. - /// - /// # Note - /// - /// This function determines the current HART ID by reading the [`riscv::register::mhartid`] CSR. - /// Thus, it can only be used in M-mode. For S-mode, use [`MSWI::msip`] instead. - #[inline] - pub fn msip_mhartid(&self) -> MSIP { - let hart_id = riscv::register::mhartid::read(); - // SAFETY: `hart_id` is valid for the target and is the current hart - unsafe { MSIP::new(self.msip0.get_ptr().add(hart_id) as _) } - } } unsafe_peripheral!(MSIP, u32, RW); diff --git a/riscv-peripheral/src/aclint/sswi.rs b/riscv-peripheral/src/aclint/sswi.rs index 51072d66..01cf137d 100644 --- a/riscv-peripheral/src/aclint/sswi.rs +++ b/riscv-peripheral/src/aclint/sswi.rs @@ -25,37 +25,6 @@ impl SSWI { } } - /// Returns `true` if a supervisor software interrupt is pending. - #[inline] - pub fn is_interrupting() -> bool { - riscv::register::sip::read().ssoft() - } - - /// Returns `true` if Supervisor Software Interrupts are enabled. - #[inline] - pub fn is_enabled() -> bool { - riscv::register::mie::read().ssoft() - } - - /// Sets the Supervisor Software Interrupt bit of the `mie` CSR. - /// This bit must be set for the `SSWI` to trigger supervisor software interrupts. - /// - /// # Safety - /// - /// Enabling the `SSWI` may break mask-based critical sections. - #[inline] - pub unsafe fn enable() { - riscv::register::mie::set_ssoft(); - } - - /// Clears the Supervisor Software Interrupt bit of the `mie` CSR. - /// When cleared, the `SSWI` cannot trigger supervisor software interrupts. - #[inline] - pub fn disable() { - // SAFETY: it is safe to disable interrupts - unsafe { riscv::register::mie::clear_ssoft() }; - } - /// Returns the `SETSSIP` register for the HART which ID is `hart_id`. /// /// # Note 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..4f74569a 100644 --- a/riscv-peripheral/src/macros.rs +++ b/riscv-peripheral/src/macros.rs @@ -1,12 +1,14 @@ -//! Utility macros for generating standard peripherals-related code in RISC-V PACs. +//! macros for easing the definition of peripherals in PACs /// Macro to create interfaces to CLINT peripherals in PACs. /// The resulting struct will be named `CLINT`, and will provide safe access to the CLINT registers. /// -/// This macro expects 4 different argument types: +/// 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,28 @@ 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 5 different argument types: +/// +/// - Base address (**MANDATORY**): base address of the PLIC peripheral of the target. +/// - Per-HART mtimecmp registers (**OPTIONAL**): a list of `mtimecmp` registers for easing access to per-HART mtimecmp regs. +/// - Per-HART msip registers (**OPTIONAL**): a list of `msip` registers for easing access to per-HART msip regs. +/// +/// Check the examples below for more details about the usage and syntax of this macro. +/// +/// # Example +/// +/// ## Base address only +/// +/// ``` +/// use riscv_peripheral::clint_codegen; +/// +/// riscv_peripheral::plic_codegen!(base 0x0C00_0000,); // do not forget the ending comma! +/// +/// let priorities = PLIC::priorities(); // MSWI peripheral +/// let pendings = PLIC::pendings(); // MTIMER peripheral +/// ``` #[macro_export] macro_rules! plic_codegen { () => { diff --git a/riscv-peripheral/src/plic.rs b/riscv-peripheral/src/plic.rs index d8e32ffc..6f5ee98f 100644 --- a/riscv-peripheral/src/plic.rs +++ b/riscv-peripheral/src/plic.rs @@ -148,7 +148,6 @@ pub(crate) mod test { use riscv_pac::result::{Error, Result}; use riscv_pac::{ExternalInterruptNumber, HartIdNumber, InterruptNumber, PriorityNumber}; - #[pac_enum(unsafe ExternalInterruptNumber)] #[derive(Clone, Copy, Debug, Eq, PartialEq)] #[repr(usize)] pub(crate) enum Interrupt { @@ -158,7 +157,6 @@ pub(crate) mod test { I4 = 4, } - #[pac_enum(unsafe PriorityNumber)] #[derive(Clone, Copy, Debug, Eq, PartialEq)] #[repr(u8)] pub(crate) enum Priority { @@ -168,7 +166,6 @@ pub(crate) mod test { P3 = 3, } - #[pac_enum(unsafe HartIdNumber)] #[derive(Clone, Copy, Debug, Eq, PartialEq)] #[repr(u16)] pub(crate) enum Context { @@ -177,61 +174,63 @@ pub(crate) mod test { C2 = 2, } - // unsafe impl InterruptNumber for Interrupt { - // const MAX_INTERRUPT_NUMBER: u16 = 4; + unsafe impl InterruptNumber for Interrupt { + const MAX_INTERRUPT_NUMBER: usize = 4; - // #[inline] - // fn number(self) -> u16 { - // self as _ - // } + #[inline] + 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)), } } } unsafe impl ExternalInterruptNumber for Interrupt {} - // unsafe impl PriorityNumber for Priority { - // const MAX_PRIORITY_NUMBER: u8 = 3; + unsafe impl PriorityNumber for Priority { + const MAX_PRIORITY_NUMBER: u8 = 3; - // #[inline] - // fn number(self) -> u8 { - // self as _ - // } + #[inline] + fn number(self) -> u8 { + self as _ + } #[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)), } } } - // unsafe impl HartIdNumber for Context { - // const MAX_HART_ID_NUMBER: u16 = 2; + unsafe impl HartIdNumber for Context { + const MAX_HART_ID_NUMBER: u16 = 2; - // #[inline] - // fn number(self) -> u16 { - // self as _ - // } + #[inline] + fn number(self) -> u16 { + self as _ + } #[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/Cargo.toml b/riscv/Cargo.toml index 20b10518..cf27d5e2 100644 --- a/riscv/Cargo.toml +++ b/riscv/Cargo.toml @@ -20,11 +20,15 @@ targets = [ ] [features] -default = ["riscv-pac/default"] +default = ["riscv-macros"] s-mode = [] 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.2", default-features = false } +riscv-pac = { path = "../riscv-pac", version = "0.1.2" } +riscv-macros = { path = "macros", version = "0.1.0", optional = true } + +[dev-dependencies] +trybuild = "1.0" diff --git a/riscv-pac/macros/Cargo.toml b/riscv/macros/Cargo.toml similarity index 75% rename from riscv-pac/macros/Cargo.toml rename to riscv/macros/Cargo.toml index c559182f..94ef7090 100644 --- a/riscv-pac/macros/Cargo.toml +++ b/riscv/macros/Cargo.toml @@ -3,11 +3,11 @@ authors = [ "The RISC-V Team ", ] categories = ["embedded", "no-std"] -description = "Derive macros re-exported in `riscv-pac`" -documentation = "https://docs.rs/riscv-rt" +description = "Procedural macros re-exported in `riscv`" +documentation = "https://docs.rs/riscv" keywords = ["riscv", "register", "peripheral"] license = "MIT OR Apache-2.0" -name = "riscv-pac-macros" +name = "riscv-macros" repository = "https://github.com/rust-embedded/riscv" version = "0.1.0" edition = "2021" diff --git a/riscv-pac/macros/src/lib.rs b/riscv/macros/src/lib.rs similarity index 96% rename from riscv-pac/macros/src/lib.rs rename to riscv/macros/src/lib.rs index 7924372a..898856cb 100644 --- a/riscv-pac/macros/src/lib.rs +++ b/riscv/macros/src/lib.rs @@ -195,6 +195,7 @@ impl PacEnumItem { 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 @@ -245,7 +246,7 @@ core::arch::global_asm!(" // Push the trait implementation res.push(quote! { - unsafe impl riscv_pac::#trait_name for #name { + unsafe impl riscv::#trait_name for #name { const #const_name: #num_type = #max_discriminant; #[inline] @@ -254,10 +255,10 @@ core::arch::global_asm!(" } #[inline] - fn from_number(number: #num_type) -> Result { + fn from_number(number: #num_type) -> riscv::result::Result { match number { #(#valid_matches,)* - _ => Err(number), + _ => Err(riscv::result::Error::InvalidVariant(number as _)), } } } @@ -271,7 +272,7 @@ core::arch::global_asm!(" let dispatch_fn_name = interrupt_type.dispatch_fn_name(); // Push the marker trait implementation - res.push(quote! { unsafe impl riscv_pac::#marker_trait_name for #name {} }); + res.push(quote! { unsafe impl riscv::#marker_trait_name for #name {} }); let interrupt_handlers = self.interrupt_handlers(); let interrupt_array = self.interrupt_array(); @@ -330,7 +331,7 @@ core::arch::global_asm!(" /// # Example /// /// ```rust -/// use riscv_pac::*; +/// use riscv::*; /// /// #[repr(usize)] /// #[pac_enum(unsafe ExceptionNumber)] diff --git a/riscv/src/interrupt.rs b/riscv/src/interrupt.rs index 90d9a1da..697d0345 100644 --- a/riscv/src/interrupt.rs +++ b/riscv/src/interrupt.rs @@ -2,7 +2,10 @@ // NOTE: Adapted from cortex-m/src/interrupt.rs -pub use riscv_pac::{CoreInterruptNumber, ExceptionNumber, InterruptNumber}; // re-export useful riscv-pac traits +use crate::result::Result; + +// re-export useful riscv-pac traits +pub use riscv_pac::{CoreInterruptNumber, ExceptionNumber, InterruptNumber}; pub mod machine; pub mod supervisor; @@ -38,20 +41,14 @@ impl Trap { /// Tries to convert the generic trap cause to a target-specific trap cause #[inline] - pub fn try_into(self) -> Result, TrapError> + pub fn try_into(self) -> Result> where I: CoreInterruptNumber, E: ExceptionNumber, { match self { - Trap::Interrupt(code) => match I::from_number(code) { - Ok(interrupt) => Ok(Trap::Interrupt(interrupt)), - Err(code) => Err(TrapError::InvalidInterrupt(code)), - }, - Trap::Exception(code) => match E::from_number(code) { - Ok(exception) => Ok(Trap::Exception(exception)), - Err(code) => Err(TrapError::InvalidException(code)), - }, + Trap::Interrupt(code) => Ok(Trap::Interrupt(I::from_number(code)?)), + Trap::Exception(code) => Ok(Trap::Exception(E::from_number(code)?)), } } } @@ -65,7 +62,7 @@ impl Trap { /// Tries to convert the generic trap cause to a target-specific trap cause #[inline] - pub fn try_from(trap: Trap) -> Result { + pub fn try_from(trap: Trap) -> Result { trap.try_into() } } diff --git a/riscv/src/interrupt/machine.rs b/riscv/src/interrupt/machine.rs index 40233f56..d88ceb46 100644 --- a/riscv/src/interrupt/machine.rs +++ b/riscv/src/interrupt/machine.rs @@ -1,8 +1,11 @@ use crate::{ - interrupt::{Trap, TrapError}, + interrupt::Trap, register::{mcause, mepc, mstatus}, }; -use riscv_pac::{CoreInterruptNumber, ExceptionNumber, InterruptNumber}; +use riscv_pac::{ + result::{Error, Result}, + CoreInterruptNumber, ExceptionNumber, InterruptNumber, +}; /// Standard M-mode RISC-V interrupts #[derive(Copy, Clone, Debug, PartialEq, Eq)] @@ -26,7 +29,7 @@ unsafe impl InterruptNumber for Interrupt { } #[inline] - fn from_number(value: usize) -> Result { + fn from_number(value: usize) -> Result { match value { 1 => Ok(Self::SupervisorSoft), 3 => Ok(Self::MachineSoft), @@ -34,7 +37,7 @@ unsafe impl InterruptNumber for Interrupt { 7 => Ok(Self::MachineTimer), 9 => Ok(Self::SupervisorExternal), 11 => Ok(Self::MachineExternal), - _ => Err(value), + _ => Err(Error::InvalidVariant(value)), } } } @@ -72,7 +75,7 @@ unsafe impl ExceptionNumber for Exception { } #[inline] - fn from_number(value: usize) -> Result { + fn from_number(value: usize) -> Result { match value { 0 => Ok(Self::InstructionMisaligned), 1 => Ok(Self::InstructionFault), @@ -88,7 +91,7 @@ unsafe impl ExceptionNumber for Exception { 12 => Ok(Self::InstructionPageFault), 13 => Ok(Self::LoadPageFault), 15 => Ok(Self::StorePageFault), - _ => Err(value), + _ => Err(Error::InvalidVariant(value)), } } } @@ -115,7 +118,7 @@ pub unsafe fn enable() { /// 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, TrapError> { +pub fn try_cause() -> Result> { mcause::read().cause().try_into() } @@ -207,17 +210,17 @@ mod test { #[test] fn test_interrupt() { assert_eq!(Interrupt::from_number(1), Ok(SupervisorSoft)); - assert_eq!(Interrupt::from_number(2), Err(2)); + 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(4)); + 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(6)); + 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(8)); + 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(10)); + 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(12)); + assert_eq!(Interrupt::from_number(12), Err(Error::InvalidVariant(12))); assert_eq!(SupervisorSoft.number(), 1); assert_eq!(MachineSoft.number(), 3); @@ -241,13 +244,13 @@ mod test { 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(10)); + 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(14)); + 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(16)); + assert_eq!(Exception::from_number(16), Err(Error::InvalidVariant(16))); assert_eq!(InstructionMisaligned.number(), 0); assert_eq!(InstructionFault.number(), 1); diff --git a/riscv/src/interrupt/supervisor.rs b/riscv/src/interrupt/supervisor.rs index 1d032e6f..ed25d236 100644 --- a/riscv/src/interrupt/supervisor.rs +++ b/riscv/src/interrupt/supervisor.rs @@ -1,8 +1,11 @@ use crate::{ - interrupt::{Trap, TrapError}, + interrupt::Trap, register::{scause, sepc, sstatus}, }; -use riscv_pac::{CoreInterruptNumber, ExceptionNumber, InterruptNumber}; +use riscv_pac::{ + result::{Error, Result}, + CoreInterruptNumber, ExceptionNumber, InterruptNumber, +}; /// Interrupt #[derive(Copy, Clone, Debug, Eq, PartialEq)] @@ -23,12 +26,12 @@ unsafe impl InterruptNumber for Interrupt { } #[inline] - fn from_number(value: usize) -> Result { + fn from_number(value: usize) -> Result { match value { 1 => Ok(Self::SupervisorSoft), 5 => Ok(Self::SupervisorTimer), 9 => Ok(Self::SupervisorExternal), - _ => Err(value), + _ => Err(Error::InvalidVariant(value)), } } } @@ -65,7 +68,7 @@ unsafe impl ExceptionNumber for Exception { } #[inline] - fn from_number(value: usize) -> Result { + fn from_number(value: usize) -> Result { match value { 0 => Ok(Self::InstructionMisaligned), 1 => Ok(Self::InstructionFault), @@ -80,7 +83,7 @@ unsafe impl ExceptionNumber for Exception { 12 => Ok(Self::InstructionPageFault), 13 => Ok(Self::LoadPageFault), 15 => Ok(Self::StorePageFault), - _ => Err(value), + _ => Err(Error::InvalidVariant(value)), } } } @@ -107,7 +110,7 @@ pub unsafe fn enable() { /// 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, TrapError> { +pub fn try_cause() -> Result> { scause::read().cause().try_into() } @@ -198,17 +201,17 @@ mod test { #[test] fn test_interrupt() { assert_eq!(Interrupt::from_number(1), Ok(SupervisorSoft)); - assert_eq!(Interrupt::from_number(2), Err(2)); - assert_eq!(Interrupt::from_number(3), Err(3)); - assert_eq!(Interrupt::from_number(4), Err(4)); + 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(6)); - assert_eq!(Interrupt::from_number(7), Err(7)); - assert_eq!(Interrupt::from_number(8), Err(8)); + 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(10)); - assert_eq!(Interrupt::from_number(11), Err(11)); - assert_eq!(Interrupt::from_number(12), Err(12)); + 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); @@ -229,13 +232,13 @@ mod test { 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(10)); - assert_eq!(Exception::from_number(11), Err(11)); + 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(14)); + 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(16)); + assert_eq!(Exception::from_number(16), Err(Error::InvalidVariant(16))); assert_eq!(InstructionMisaligned.number(), 0); assert_eq!(InstructionFault.number(), 1); 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-pac/tests/test.rs b/riscv/tests/test.rs similarity index 100% rename from riscv-pac/tests/test.rs rename to riscv/tests/test.rs diff --git a/riscv-pac/tests/ui/fail_empty_macro.rs b/riscv/tests/ui/fail_empty_macro.rs similarity index 82% rename from riscv-pac/tests/ui/fail_empty_macro.rs rename to riscv/tests/ui/fail_empty_macro.rs index a47a5711..8891da0a 100644 --- a/riscv-pac/tests/ui/fail_empty_macro.rs +++ b/riscv/tests/ui/fail_empty_macro.rs @@ -1,4 +1,4 @@ -#[riscv_pac::pac_enum] +#[riscv::pac_enum] #[derive(Clone, Copy, Debug, PartialEq)] enum Interrupt { I1 = 1, 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-pac/tests/ui/fail_no_unsafe.rs b/riscv/tests/ui/fail_no_unsafe.rs similarity index 73% rename from riscv-pac/tests/ui/fail_no_unsafe.rs rename to riscv/tests/ui/fail_no_unsafe.rs index 865a68fd..a304fa9e 100644 --- a/riscv-pac/tests/ui/fail_no_unsafe.rs +++ b/riscv/tests/ui/fail_no_unsafe.rs @@ -1,4 +1,4 @@ -#[riscv_pac::pac_enum(InterruptNumber)] +#[riscv::pac_enum(InterruptNumber)] #[derive(Clone, Copy, Debug, PartialEq)] enum Interrupt { I1 = 1, 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-pac/tests/ui/fail_unknown_trait.rs b/riscv/tests/ui/fail_unknown_trait.rs similarity index 70% rename from riscv-pac/tests/ui/fail_unknown_trait.rs rename to riscv/tests/ui/fail_unknown_trait.rs index 82ebdc52..dc6c5d44 100644 --- a/riscv-pac/tests/ui/fail_unknown_trait.rs +++ b/riscv/tests/ui/fail_unknown_trait.rs @@ -1,4 +1,4 @@ -#[riscv_pac::pac_enum(unsafe InterruptNumber)] +#[riscv::pac_enum(unsafe InterruptNumber)] #[derive(Clone, Copy, Debug, PartialEq)] enum Interrupt { I1 = 1, diff --git a/riscv-pac/tests/ui/fail_unknown_trait.stderr b/riscv/tests/ui/fail_unknown_trait.stderr similarity index 50% rename from riscv-pac/tests/ui/fail_unknown_trait.stderr rename to riscv/tests/ui/fail_unknown_trait.stderr index 4f03439d..337f8548 100644 --- a/riscv-pac/tests/ui/fail_unknown_trait.stderr +++ b/riscv/tests/ui/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:30 + --> tests/ui/fail_unknown_trait.rs:1:26 | -1 | #[riscv_pac::pac_enum(unsafe InterruptNumber)] - | ^^^^^^^^^^^^^^^ +1 | #[riscv::pac_enum(unsafe InterruptNumber)] + | ^^^^^^^^^^^^^^^ diff --git a/riscv-pac/tests/ui/pass_test.rs b/riscv/tests/ui/pass_test.rs similarity index 79% rename from riscv-pac/tests/ui/pass_test.rs rename to riscv/tests/ui/pass_test.rs index 07141391..c80ead3e 100644 --- a/riscv-pac/tests/ui/pass_test.rs +++ b/riscv/tests/ui/pass_test.rs @@ -1,6 +1,6 @@ -use riscv_pac::*; +use riscv::result::Error; +use riscv::*; -#[repr(usize)] #[pac_enum(unsafe ExceptionNumber)] #[derive(Clone, Copy, Debug, Eq, PartialEq)] enum Exception { @@ -8,7 +8,6 @@ enum Exception { E3 = 3, } -#[repr(usize)] #[pac_enum(unsafe CoreInterruptNumber)] #[derive(Clone, Copy, Debug, Eq, PartialEq)] enum Interrupt { @@ -18,7 +17,6 @@ enum Interrupt { I7 = 7, } -#[repr(u8)] #[pac_enum(unsafe PriorityNumber)] #[derive(Clone, Copy, Debug, Eq, PartialEq)] enum Priority { @@ -28,7 +26,6 @@ enum Priority { P3 = 3, } -#[repr(u16)] #[pac_enum(unsafe HartIdNumber)] #[derive(Clone, Copy, Debug, Eq, PartialEq)] enum HartId { @@ -58,11 +55,11 @@ fn main() { assert_eq!(Exception::E1.number(), 1); assert_eq!(Exception::E3.number(), 3); - assert_eq!(Exception::from_number(0), Err(0)); + 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(2)); + 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(4)); + assert_eq!(Exception::from_number(4), Err(Error::InvalidVariant(4))); assert_eq!(Exception::MAX_EXCEPTION_NUMBER, 3); @@ -71,13 +68,13 @@ fn main() { assert_eq!(Interrupt::I4.number(), 4); assert_eq!(Interrupt::I7.number(), 7); - assert_eq!(Interrupt::from_number(0), Err(0)); + 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(3)); + 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(5)); - assert_eq!(Interrupt::from_number(6), Err(6)); + 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); @@ -102,7 +99,7 @@ fn main() { assert_eq!(Priority::from_number(1), Ok(Priority::P1)); assert_eq!(Priority::from_number(2), Ok(Priority::P2)); assert_eq!(Priority::from_number(3), Ok(Priority::P3)); - assert_eq!(Priority::from_number(4), Err(4)); + assert_eq!(Priority::from_number(4), Err(Error::InvalidVariant(4))); assert_eq!(Priority::MAX_PRIORITY_NUMBER, 3); @@ -113,7 +110,7 @@ fn main() { assert_eq!(HartId::from_number(0), Ok(HartId::H0)); assert_eq!(HartId::from_number(1), Ok(HartId::H1)); assert_eq!(HartId::from_number(2), Ok(HartId::H2)); - assert_eq!(HartId::from_number(3), Err(3)); + assert_eq!(HartId::from_number(3), Err(Error::InvalidVariant(3))); assert_eq!(HartId::MAX_HART_ID_NUMBER, 2); } From ab998d82c461e1204b20527db77c4b01e9148348 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:53:44 +0200 Subject: [PATCH 17/17] minor changes --- 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 980ae6f2..f96c6f2d 100644 --- a/riscv/CHANGELOG.md +++ b/riscv/CHANGELOG.md @@ -26,6 +26,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Changed +- Modified delay function to avoid using binary labels in inline assembly. - More efficient implementation of `From` traits for `Interrupt` and `Exception` enums. ### Fixed 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), )