diff --git a/.github/workflows/riscv-pac.yaml b/.github/workflows/riscv-pac.yaml new file mode 100644 index 00000000..a7dfe5af --- /dev/null +++ b/.github/workflows/riscv-pac.yaml @@ -0,0 +1,60 @@ +on: + push: + branches: [ master ] + 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.61.0 + toolchain: [ stable, nightly, 1.61.0 ] + target: + - riscv32i-unknown-none-elf + - riscv32imc-unknown-none-elf + - riscv32imac-unknown-none-elf + - riscv64imac-unknown-none-elf + - riscv64gc-unknown-none-elf + include: + # Nightly is only for reference and allowed to fail + - toolchain: nightly + experimental: true + runs-on: ubuntu-latest + continue-on-error: ${{ matrix.experimental || false }} + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ matrix.toolchain }} + targets: ${{ matrix.target }} + - name: Build + 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..57b986ab 100644 --- a/.github/workflows/riscv-peripheral.yaml +++ b/.github/workflows/riscv-peripheral.yaml @@ -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-semihosting.yaml b/.github/workflows/riscv-semihosting.yaml index 4f6da8b0..3ba711ec 100644 --- a/.github/workflows/riscv-semihosting.yaml +++ b/.github/workflows/riscv-semihosting.yaml @@ -11,8 +11,8 @@ jobs: build-riscv: strategy: matrix: - # All generated code should be running on stable now, MRSV is 1.60.0 - toolchain: [ stable, nightly, 1.60.0 ] + # All generated code should be running on stable now, MRSV is 1.61.0 + toolchain: [ stable, nightly, 1.61.0 ] target: - riscv32i-unknown-none-elf - riscv32imc-unknown-none-elf diff --git a/.github/workflows/riscv.yaml b/.github/workflows/riscv.yaml index cd03b872..28e72126 100644 --- a/.github/workflows/riscv.yaml +++ b/.github/workflows/riscv.yaml @@ -11,8 +11,8 @@ jobs: build-riscv: strategy: matrix: - # All generated code should be running on stable now, MRSV is 1.60.0 - toolchain: [ stable, nightly, 1.60.0 ] + # All generated code should be running on stable now, MRSV is 1.61.0 + toolchain: [ stable, nightly, 1.61.0 ] target: - riscv32i-unknown-none-elf - riscv32imc-unknown-none-elf @@ -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..6a5c6210 100644 --- a/riscv-pac/Cargo.toml +++ b/riscv-pac/Cargo.toml @@ -1,8 +1,8 @@ [package] name = "riscv-pac" -version = "0.1.1" +version = "0.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/src/lib.rs b/riscv-pac/src/lib.rs index 4da1170a..935d5200 100644 --- a/riscv-pac/src/lib.rs +++ b/riscv-pac/src/lib.rs @@ -4,37 +4,86 @@ pub mod result; use result::Result; -/// Trait for enums of target-specific external interrupt numbers. +/// Trait for enums of target-specific exception numbers. /// -/// This trait should be implemented by a peripheral access crate (PAC) -/// on its enum of available external interrupts for a specific device. -/// Each variant must convert to a `u16` of its interrupt number. +/// This trait should be implemented by a peripheral access crate (PAC) on its enum of available +/// exceptions for a specific device. Alternatively, the `riscv` crate provides a default +/// implementation for the RISC-V ISA. Each variant must convert to a `usize` of its exception number. /// /// # Safety /// -/// * This trait must only be implemented on a PAC of a RISC-V target. -/// * This trait must only be implemented on enums of external interrupts. +/// * This trait must only be implemented on the `riscv` crate or on a PAC of a RISC-V target. +/// * This trait must only be implemented on enums of exceptions. +/// * Each enum variant must represent a distinct value (no duplicates are permitted), +/// * Each enum variant must always return the same value (do not change at runtime). +/// * All the exception numbers must be less than or equal to `MAX_EXCEPTION_NUMBER`. +/// * `MAX_EXCEPTION_NUMBER` must coincide with the highest allowed exception number. +pub unsafe trait ExceptionNumber: Copy { + /// Highest number assigned to an exception. + const MAX_EXCEPTION_NUMBER: usize; + + /// Converts an exception to its corresponding number. + fn number(self) -> usize; + + /// Tries to convert a number to a valid exception. + /// If the conversion fails, it returns an error with the number back. + fn from_number(value: usize) -> Result; +} + +/// Trait for enums of target-specific interrupt numbers. +/// +/// This trait should be implemented by a peripheral access crate (PAC) on its enum of available +/// interrupts for a specific device. Alternatively, the `riscv` crate provides a default +/// implementation for the RISC-V ISA. Each variant must convert to a `usize` of its interrupt number. +/// +/// # Safety +/// +/// * This trait must only be implemented on the `riscv` crate or on a PAC of a RISC-V target. +/// * This trait must only be implemented on enums of interrupts. /// * Each enum variant must represent a distinct value (no duplicates are permitted), /// * Each enum variant must always return the same value (do not change at runtime). /// * All the interrupt numbers must be less than or equal to `MAX_INTERRUPT_NUMBER`. /// * `MAX_INTERRUPT_NUMBER` must coincide with the highest allowed interrupt number. pub unsafe trait InterruptNumber: Copy { /// Highest number assigned to an interrupt source. - const MAX_INTERRUPT_NUMBER: u16; + const MAX_INTERRUPT_NUMBER: usize; /// Converts an interrupt source to its corresponding number. - fn number(self) -> u16; + fn number(self) -> usize; /// Tries to convert a number to a valid interrupt source. /// If the conversion fails, it returns an error with the number back. - fn from_number(value: u16) -> Result; + fn from_number(value: usize) -> Result; } +/// Marker trait for enums of target-specific core interrupt numbers. +/// +/// Core interrupts are interrupts are retrieved from the `mcause` CSR. Usually, vectored mode is +/// only available for core interrupts. The `riscv` crate provides a default implementation for +/// the RISC-V ISA. However, a PAC may override the default implementation if the target has a +/// different interrupt numbering scheme (e.g., ESP32C3). +/// +/// # Safety +/// +/// Each enum variant must represent a valid core interrupt number read from the `mcause` CSR. +pub unsafe trait CoreInterruptNumber: InterruptNumber {} + +/// Marker trait for enums of target-specific external interrupt numbers. +/// +/// External interrupts are interrupts caused by external sources (e.g., GPIO, UART, SPI). +/// External interrupts are **not** retrieved from the `mcause` CSR. +/// Instead, RISC-V processors have a single core interrupt for all external interrupts. +/// An additional peripheral (e.g., PLIC) is used to multiplex the external interrupts. +/// +/// # Safety +/// +/// Each enum variant must represent a valid external interrupt number. +pub unsafe trait ExternalInterruptNumber: InterruptNumber {} + /// Trait for enums of priority levels. /// -/// This trait should be implemented by a peripheral access crate (PAC) -/// on its enum of available priority numbers for a specific device. -/// Each variant must convert to a `u8` of its priority level. +/// This trait should be implemented by a peripheral access crate (PAC) on its enum of available +/// priority numbers for a specific device. Each variant must convert to a `u8` of its priority level. /// /// # Safety /// @@ -58,9 +107,8 @@ pub unsafe trait PriorityNumber: Copy { /// Trait for enums of HART identifiers. /// -/// This trait should be implemented by a peripheral access crate (PAC) -/// on its enum of available HARTs for a specific device. -/// Each variant must convert to a `u16` of its HART ID number. +/// This trait should be implemented by a peripheral access crate (PAC) on its enum of available +/// HARTs for a specific device. Each variant must convert to a `u16` of its HART ID number. /// /// # Safety /// @@ -81,3 +129,165 @@ pub unsafe trait HartIdNumber: Copy { /// If the conversion fails, it returns an error with the number back. fn from_number(value: u16) -> Result; } + +#[cfg(test)] +mod test { + use super::*; + use crate::result::Error; + + #[derive(Clone, Copy, Debug, Eq, PartialEq)] + enum Exception { + E1 = 1, + E3 = 3, + } + + #[derive(Clone, Copy, Debug, Eq, PartialEq)] + enum Interrupt { + I1 = 1, + I2 = 2, + I4 = 4, + } + + #[derive(Clone, Copy, Debug, Eq, PartialEq)] + enum Priority { + P0 = 0, + P1 = 1, + P2 = 2, + P3 = 3, + } + + #[derive(Clone, Copy, Debug, Eq, PartialEq)] + enum HartId { + H0 = 0, + H1 = 1, + H2 = 2, + } + + unsafe impl ExceptionNumber for Exception { + const MAX_EXCEPTION_NUMBER: usize = Self::E3 as usize; + + #[inline] + fn number(self) -> usize { + self as _ + } + + #[inline] + fn from_number(number: usize) -> Result { + match number { + 1 => Ok(Exception::E1), + 3 => Ok(Exception::E3), + _ => Err(Error::InvalidVariant(number)), + } + } + } + + unsafe impl InterruptNumber for Interrupt { + const MAX_INTERRUPT_NUMBER: usize = Self::I4 as usize; + + #[inline] + fn number(self) -> usize { + self as _ + } + + #[inline] + fn from_number(number: usize) -> Result { + match number { + 1 => Ok(Interrupt::I1), + 2 => Ok(Interrupt::I2), + 4 => Ok(Interrupt::I4), + _ => Err(Error::InvalidVariant(number)), + } + } + } + + unsafe impl PriorityNumber for Priority { + const MAX_PRIORITY_NUMBER: u8 = Self::P3 as u8; + + #[inline] + fn number(self) -> u8 { + self as _ + } + + #[inline] + fn from_number(number: u8) -> Result { + match number { + 0 => Ok(Priority::P0), + 1 => Ok(Priority::P1), + 2 => Ok(Priority::P2), + 3 => Ok(Priority::P3), + _ => Err(Error::InvalidVariant(number as _)), + } + } + } + + unsafe impl HartIdNumber for HartId { + const MAX_HART_ID_NUMBER: u16 = Self::H2 as u16; + + #[inline] + fn number(self) -> u16 { + self as _ + } + + #[inline] + fn from_number(number: u16) -> Result { + match number { + 0 => Ok(HartId::H0), + 1 => Ok(HartId::H1), + 2 => Ok(HartId::H2), + _ => Err(Error::InvalidVariant(number as _)), + } + } + } + + #[test] + fn check_exception_enum() { + assert_eq!(Exception::E1.number(), 1); + assert_eq!(Exception::E3.number(), 3); + + assert_eq!(Exception::from_number(0), Err(Error::InvalidVariant(0))); + assert_eq!(Exception::from_number(1), Ok(Exception::E1)); + assert_eq!(Exception::from_number(2), Err(Error::InvalidVariant(2))); + assert_eq!(Exception::from_number(3), Ok(Exception::E3)); + assert_eq!(Exception::from_number(4), Err(Error::InvalidVariant(4))); + } + + #[test] + fn check_interrupt_enum() { + assert_eq!(Interrupt::I1.number(), 1); + assert_eq!(Interrupt::I2.number(), 2); + assert_eq!(Interrupt::I4.number(), 4); + + assert_eq!(Interrupt::from_number(0), Err(Error::InvalidVariant(0))); + assert_eq!(Interrupt::from_number(1), Ok(Interrupt::I1)); + assert_eq!(Interrupt::from_number(2), Ok(Interrupt::I2)); + assert_eq!(Interrupt::from_number(3), Err(Error::InvalidVariant(3))); + assert_eq!(Interrupt::from_number(4), Ok(Interrupt::I4)); + assert_eq!(Interrupt::from_number(5), Err(Error::InvalidVariant(5))); + } + + #[test] + fn check_priority_enum() { + assert_eq!(Priority::P0.number(), 0); + assert_eq!(Priority::P1.number(), 1); + assert_eq!(Priority::P2.number(), 2); + assert_eq!(Priority::P3.number(), 3); + + assert_eq!(Priority::from_number(0), Ok(Priority::P0)); + assert_eq!(Priority::from_number(1), Ok(Priority::P1)); + assert_eq!(Priority::from_number(2), Ok(Priority::P2)); + assert_eq!(Priority::from_number(3), Ok(Priority::P3)); + assert_eq!(Priority::from_number(4), Err(Error::InvalidVariant(4))); + } + + #[test] + fn check_hart_id_enum() { + assert_eq!(HartId::H0.number(), 0); + assert_eq!(HartId::H1.number(), 1); + assert_eq!(HartId::H2.number(), 2); + + assert_eq!(HartId::from_number(0), Ok(HartId::H0)); + assert_eq!(HartId::from_number(1), Ok(HartId::H1)); + assert_eq!(HartId::from_number(2), Ok(HartId::H2)); + assert_eq!(HartId::from_number(3), Err(Error::InvalidVariant(3))); + } +} diff --git a/riscv-peripheral/CHANGELOG.md b/riscv-peripheral/CHANGELOG.md index 3feb9601..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..b719e73d 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", default-features = false } +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..65d237e8 100644 --- a/riscv-peripheral/examples/e310x.rs +++ b/riscv-peripheral/examples/e310x.rs @@ -2,17 +2,18 @@ //! This is a simple example of how to use the `riscv-peripheral` crate to generate //! peripheral definitions for a target. -use riscv_pac::result::{Error, Result}; -use riscv_pac::{HartIdNumber, InterruptNumber, PriorityNumber}; +use riscv_pac::{ + result::{Error, Result}, + ExternalInterruptNumber, HartIdNumber, InterruptNumber, PriorityNumber, +}; -#[repr(u16)] #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum HartId { H0 = 0, } unsafe impl HartIdNumber for HartId { - const MAX_HART_ID_NUMBER: u16 = 0; + const MAX_HART_ID_NUMBER: u16 = Self::H0 as u16; #[inline] fn number(self) -> u16 { @@ -21,17 +22,14 @@ 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)), } } } #[derive(Copy, Clone, Debug, PartialEq, Eq)] -#[repr(u16)] pub enum Interrupt { WATCHDOG = 1, RTC = 2, @@ -88,26 +86,27 @@ pub enum Interrupt { } unsafe impl InterruptNumber for Interrupt { - const MAX_INTERRUPT_NUMBER: u16 = 52; + 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 {} + #[derive(Copy, Clone, Debug, PartialEq, Eq)] -#[repr(u8)] pub enum Priority { P0 = 0, P1 = 1, @@ -120,7 +119,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/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 116689a6..6f5ee98f 100644 --- a/riscv-peripheral/src/plic.rs +++ b/riscv-peripheral/src/plic.rs @@ -145,11 +145,11 @@ impl CTX

{ #[cfg(test)] pub(crate) mod test { - use super::{HartIdNumber, InterruptNumber, PriorityNumber}; use riscv_pac::result::{Error, Result}; + use riscv_pac::{ExternalInterruptNumber, HartIdNumber, InterruptNumber, PriorityNumber}; #[derive(Clone, Copy, Debug, Eq, PartialEq)] - #[repr(u16)] + #[repr(usize)] pub(crate) enum Interrupt { I1 = 1, I2 = 2, @@ -175,24 +175,27 @@ pub(crate) mod test { } unsafe impl InterruptNumber for Interrupt { - const MAX_INTERRUPT_NUMBER: u16 = 4; + const MAX_INTERRUPT_NUMBER: usize = 4; #[inline] - fn number(self) -> u16 { + fn number(self) -> usize { self as _ } #[inline] - fn from_number(number: u16) -> Result { - if number > Self::MAX_INTERRUPT_NUMBER || number == 0 { - Err(Error::InvalidVariant(number as usize)) - } else { - // SAFETY: valid interrupt number - Ok(unsafe { core::mem::transmute(number) }) + fn from_number(number: usize) -> Result { + match number { + 1 => Ok(Interrupt::I1), + 2 => Ok(Interrupt::I2), + 3 => Ok(Interrupt::I3), + 4 => Ok(Interrupt::I4), + _ => Err(Error::InvalidVariant(number)), } } } + unsafe impl ExternalInterruptNumber for Interrupt {} + unsafe impl PriorityNumber for Priority { const MAX_PRIORITY_NUMBER: u8 = 3; @@ -203,11 +206,12 @@ pub(crate) mod test { #[inline] fn from_number(number: u8) -> Result { - if number > Self::MAX_PRIORITY_NUMBER { - Err(Error::InvalidVariant(number as usize)) - } else { - // SAFETY: valid priority number - Ok(unsafe { core::mem::transmute(number) }) + match number { + 0 => Ok(Priority::P0), + 1 => Ok(Priority::P1), + 2 => Ok(Priority::P2), + 3 => Ok(Priority::P3), + _ => Err(Error::InvalidVariant(number as usize)), } } } @@ -222,11 +226,11 @@ pub(crate) mod test { #[inline] fn from_number(number: u16) -> Result { - if number > Self::MAX_HART_ID_NUMBER { - Err(Error::InvalidVariant(number as usize)) - } else { - // SAFETY: valid context number - Ok(unsafe { core::mem::transmute(number) }) + match number { + 0 => Ok(Context::C0), + 1 => Ok(Context::C1), + 2 => Ok(Context::C2), + _ => Err(Error::InvalidVariant(number as usize)), } } } diff --git a/riscv-peripheral/src/plic/claim.rs b/riscv-peripheral/src/plic/claim.rs index dbf5b378..d99a543d 100644 --- a/riscv-peripheral/src/plic/claim.rs +++ b/riscv-peripheral/src/plic/claim.rs @@ -1,6 +1,7 @@ //! Interrupt claim/complete register -use crate::{common::unsafe_peripheral, plic::InterruptNumber}; +use crate::common::unsafe_peripheral; +use riscv_pac::ExternalInterruptNumber; unsafe_peripheral!(CLAIM, u32, RW); @@ -8,7 +9,7 @@ impl CLAIM { /// Claims the number of a pending interrupt for for the PLIC context. /// If no interrupt is pending for this context, it returns [`None`]. #[inline] - pub fn claim(self) -> Option { + pub fn claim(self) -> Option { match self.register.read() { 0 => None, i => Some(I::from_number(i as _).unwrap()), @@ -22,7 +23,7 @@ impl CLAIM { /// If the source ID does not match an interrupt source that is /// currently enabled for the target, the completion is silently ignored. #[inline] - pub fn complete(self, source: I) { + pub fn complete(self, source: I) { self.register.write(source.number() as _) } } @@ -31,6 +32,7 @@ impl CLAIM { mod test { use super::super::test::Interrupt; use super::*; + use riscv_pac::InterruptNumber; #[test] fn test_claim() { diff --git a/riscv-peripheral/src/plic/enables.rs b/riscv-peripheral/src/plic/enables.rs index 03ab9a35..9891b32c 100644 --- a/riscv-peripheral/src/plic/enables.rs +++ b/riscv-peripheral/src/plic/enables.rs @@ -1,9 +1,7 @@ //! Interrupt enables register of a PLIC context. -use crate::{ - common::{Reg, RW}, - plic::InterruptNumber, -}; +use crate::common::{Reg, RW}; +use riscv_pac::ExternalInterruptNumber; /// Enables register of a PLIC context. #[derive(Clone, Copy, Debug, Eq, PartialEq)] @@ -31,8 +29,8 @@ impl ENABLES { /// Checks if an interrupt source is enabled for the PLIC context. #[inline] - pub fn is_enabled(self, source: I) -> bool { - let source = source.number() as usize; + pub fn is_enabled(self, source: I) -> bool { + let source = source.number(); let offset = (source / u32::BITS as usize) as _; // SAFETY: valid interrupt number let reg: Reg = unsafe { Reg::new(self.ptr.offset(offset)) }; @@ -49,8 +47,8 @@ impl ENABLES { /// /// * Enabling an interrupt source can break mask-based critical sections. #[inline] - pub unsafe fn enable(self, source: I) { - let source = source.number() as usize; + pub unsafe fn enable(self, source: I) { + let source = source.number(); let offset = (source / u32::BITS as usize) as _; // SAFETY: valid interrupt number let reg: Reg = unsafe { Reg::new(self.ptr.offset(offset)) }; @@ -70,12 +68,12 @@ impl ENABLES { /// * Register must be properly aligned **for atomic operations**. /// * The register must not be accessed through non-atomic operations until this function returns. #[inline] - pub unsafe fn atomic_enable( + pub unsafe fn atomic_enable( self, source: I, order: core::sync::atomic::Ordering, ) { - let source = source.number() as usize; + let source = source.number(); let offset = (source / u32::BITS as usize) as _; // SAFETY: valid interrupt number let reg: Reg = unsafe { Reg::new(self.ptr.offset(offset)) }; @@ -88,8 +86,8 @@ impl ENABLES { /// /// It performs non-atomic read-modify-write operations, which may lead to **wrong** behavior. #[inline] - pub fn disable(self, source: I) { - let source = source.number() as usize; + pub fn disable(self, source: I) { + let source = source.number(); let offset = (source / u32::BITS as usize) as _; // SAFETY: valid interrupt number let reg: Reg = unsafe { Reg::new(self.ptr.offset(offset)) }; @@ -108,12 +106,12 @@ impl ENABLES { /// * Register must be properly aligned **for atomic operations**. /// * The register must not be accessed through non-atomic operations until this function returns. #[inline] - pub unsafe fn atomic_disable( + pub unsafe fn atomic_disable( self, source: I, order: core::sync::atomic::Ordering, ) { - let source = source.number() as usize; + let source = source.number(); let offset = (source / u32::BITS as usize) as _; // SAFETY: valid interrupt number let reg: Reg = unsafe { Reg::new(self.ptr.offset(offset)) }; @@ -126,7 +124,7 @@ impl ENABLES { /// ///* Enabling all interrupt sources can break mask-based critical sections. #[inline] - pub unsafe fn enable_all(self) { + pub unsafe fn enable_all(self) { for offset in 0..=(I::MAX_INTERRUPT_NUMBER as u32 / u32::BITS) as isize { // SAFETY: valid offset let reg: Reg = unsafe { Reg::new(self.ptr.offset(offset)) }; @@ -136,7 +134,7 @@ impl ENABLES { /// Disables all the external interrupt sources for the PLIC context. #[inline] - pub fn disable_all(self) { + pub fn disable_all(self) { for offset in 0..=(I::MAX_INTERRUPT_NUMBER as u32 / u32::BITS) as _ { // SAFETY: valid offset let reg: Reg = unsafe { Reg::new(self.ptr.offset(offset)) }; diff --git a/riscv-peripheral/src/plic/pendings.rs b/riscv-peripheral/src/plic/pendings.rs index 8185d9b8..594991da 100644 --- a/riscv-peripheral/src/plic/pendings.rs +++ b/riscv-peripheral/src/plic/pendings.rs @@ -1,9 +1,7 @@ //! Interrupt pending bits register. -use crate::{ - common::{Reg, RO}, - plic::InterruptNumber, -}; +use crate::common::{Reg, RO}; +use riscv_pac::ExternalInterruptNumber; /// Interrupts pending bits register. #[derive(Clone, Copy, Debug, Eq, PartialEq)] @@ -31,8 +29,8 @@ impl PENDINGS { /// Checks if an interrupt triggered by a given source is pending. #[inline] - pub fn is_pending(self, source: I) -> bool { - let source = source.number() as usize; + pub fn is_pending(self, source: I) -> bool { + let source = source.number(); let offset = (source / u32::BITS as usize) as _; // SAFETY: valid interrupt number let reg: Reg = unsafe { Reg::new(self.ptr.offset(offset)) }; diff --git a/riscv-peripheral/src/plic/priorities.rs b/riscv-peripheral/src/plic/priorities.rs index a9726838..b5e44857 100644 --- a/riscv-peripheral/src/plic/priorities.rs +++ b/riscv-peripheral/src/plic/priorities.rs @@ -1,9 +1,7 @@ //! Interrupts Priorities register. -use crate::{ - common::{Reg, RW}, - plic::{InterruptNumber, PriorityNumber}, -}; +use crate::common::{Reg, RW}; +use riscv_pac::{ExternalInterruptNumber, PriorityNumber}; /// Interrupts priorities register. #[derive(Clone, Copy, Debug, Eq, PartialEq)] @@ -31,9 +29,9 @@ impl PRIORITIES { /// Returns the priority assigned to a given interrupt source. #[inline] - pub fn get_priority(self, source: I) -> P { + pub fn get_priority(self, source: I) -> P { // SAFETY: valid interrupt number - let reg: Reg = unsafe { Reg::new(self.ptr.offset(source.number() as _)) }; + let reg: Reg = unsafe { Reg::new(self.ptr.add(source.number())) }; P::from_number(reg.read() as _).unwrap() } @@ -43,13 +41,13 @@ impl PRIORITIES { /// /// Changing the priority level can break priority-based critical sections. #[inline] - pub unsafe fn set_priority( + pub unsafe fn set_priority( self, source: I, priority: P, ) { // SAFETY: valid interrupt number - let reg: Reg = unsafe { Reg::new(self.ptr.offset(source.number() as _)) }; + let reg: Reg = unsafe { Reg::new(self.ptr.add(source.number())) }; reg.write(priority.number() as _); } @@ -60,7 +58,7 @@ impl PRIORITIES { /// Priority level 0 is reserved for "no interrupt". /// Thus, this method effectively disables the all the external interrupts. #[inline] - pub fn reset(self) { + pub fn reset(self) { for source in 0..=I::MAX_INTERRUPT_NUMBER as _ { // SAFETY: interrupt number within range let reg: Reg = unsafe { Reg::new(self.ptr.offset(source)) }; @@ -73,6 +71,7 @@ impl PRIORITIES { mod test { use super::super::test::{Interrupt, Priority}; use super::*; + use riscv_pac::InterruptNumber; #[test] fn test_priorities() { 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-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/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 0ffb3441..8fb212e0 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; @@ -520,26 +522,18 @@ 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); } - let cause = xcause::read(); - let code = cause.code(); - - if cause.is_exception() { - let trap_frame = &*trap_frame; - if code < __EXCEPTIONS.len() { - let h = &__EXCEPTIONS[code]; - if let Some(handler) = h { - handler(trap_frame); - } else { - ExceptionHandler(trap_frame); + match xcause::read().cause() { + xcause::Trap::Interrupt(code) => _dispatch_core_interrupt(code), + xcause::Trap::Exception(code) if code < __EXCEPTIONS.len() => { + match __EXCEPTIONS[code].as_ref() { + Some(handler) => handler(&*trap_frame), + None => ExceptionHandler(&*trap_frame), } - } else { - ExceptionHandler(trap_frame); } - } else { - _dispatch_interrupt(code); + xcause::Trap::Exception(_) => ExceptionHandler(&*trap_frame), } } @@ -580,47 +574,3 @@ pub static __EXCEPTIONS: [Option; 16] = [ None, Some(StorePageFault), ]; - -#[export_name = "_dispatch_interrupt"] -unsafe extern "C" fn dispatch_interrupt(code: usize) { - extern "C" { - fn DefaultHandler(); - } - - if code < __INTERRUPTS.len() { - let h = &__INTERRUPTS[code]; - if let Some(handler) = h { - handler(); - } else { - DefaultHandler(); - } - } else { - DefaultHandler(); - } -} - -extern "C" { - fn SupervisorSoft(); - fn MachineSoft(); - fn SupervisorTimer(); - fn MachineTimer(); - fn SupervisorExternal(); - fn MachineExternal(); -} - -#[doc(hidden)] -#[no_mangle] -pub static __INTERRUPTS: [Option; 12] = [ - None, - Some(SupervisorSoft), - None, - Some(MachineSoft), - None, - Some(SupervisorTimer), - None, - Some(MachineTimer), - None, - Some(SupervisorExternal), - None, - Some(MachineExternal), -]; diff --git a/riscv-semihosting/CHANGELOG.md b/riscv-semihosting/CHANGELOG.md index 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 7b2aa2fb..f96c6f2d 100644 --- a/riscv/CHANGELOG.md +++ b/riscv/CHANGELOG.md @@ -9,6 +9,9 @@ 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 - Add `Mstatus::from(usize)` for use in unit tests - Add `Mstatus.bits()` @@ -21,6 +24,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Add `Mstatus` vector extension support - Add fallible counterparts to all functions that `panic` +### Changed + +- Modified delay function to avoid using binary labels in inline assembly. +- 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..cf27d5e2 100644 --- a/riscv/Cargo.toml +++ b/riscv/Cargo.toml @@ -1,8 +1,8 @@ [package] name = "riscv" -version = "0.11.1" +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"] @@ -20,10 +20,15 @@ targets = [ ] [features] +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.1", 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/README.md b/riscv/README.md index f67ac78c..2a958981 100644 --- a/riscv/README.md +++ b/riscv/README.md @@ -11,7 +11,7 @@ This project is developed and maintained by the [RISC-V team][team]. ## Minimum Supported Rust Version (MSRV) -This crate is guaranteed to compile on stable Rust 1.60 and up. It *might* +This crate is guaranteed to compile on stable Rust 1.61 and up. It *might* compile with older versions but that may change in any new patch release. ## License diff --git a/riscv/macros/Cargo.toml b/riscv/macros/Cargo.toml new file mode 100644 index 00000000..94ef7090 --- /dev/null +++ b/riscv/macros/Cargo.toml @@ -0,0 +1,21 @@ +[package] +authors = [ + "The RISC-V Team ", +] +categories = ["embedded", "no-std"] +description = "Procedural macros re-exported in `riscv`" +documentation = "https://docs.rs/riscv" +keywords = ["riscv", "register", "peripheral"] +license = "MIT OR Apache-2.0" +name = "riscv-macros" +repository = "https://github.com/rust-embedded/riscv" +version = "0.1.0" +edition = "2021" + +[lib] +proc-macro = true + +[dependencies] +proc-macro2 = "1.0" +quote = "1.0" +syn = { version = "2.0" } diff --git a/riscv/macros/src/lib.rs b/riscv/macros/src/lib.rs new file mode 100644 index 00000000..898856cb --- /dev/null +++ b/riscv/macros/src/lib.rs @@ -0,0 +1,368 @@ +use proc_macro::TokenStream; +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; +use std::collections::HashMap; +use std::str::FromStr; +use syn::{ + parse::{Parse, ParseStream}, + parse_macro_input, + spanned::Spanned, + Data, DeriveInput, Ident, Token, +}; + +/// Traits that can be implemented using the `pac_enum` macro +enum PacTrait { + Exception, + Interrupt(InterruptType), + Priority, + HartId, +} + +impl PacTrait { + /// Returns a token stream representing the trait name + fn trait_name(&self) -> TokenStream2 { + match self { + Self::Exception => quote!(ExceptionNumber), + Self::Interrupt(_) => quote!(InterruptNumber), + Self::Priority => quote!(PriorityNumber), + Self::HartId => quote!(HartIdNumber), + } + } + + /// Returns a token stream representing the data type used to represent the number + fn num_type(&self) -> TokenStream2 { + match self { + Self::Exception => quote!(usize), + Self::Interrupt(_) => quote!(usize), + Self::Priority => quote!(u8), + Self::HartId => quote!(u16), + } + } + + /// Returns a token stream representing the name of the constant that holds the maximum number + fn const_name(&self) -> TokenStream2 { + match self { + Self::Exception => quote!(MAX_EXCEPTION_NUMBER), + Self::Interrupt(_) => quote!(MAX_INTERRUPT_NUMBER), + Self::Priority => quote!(MAX_PRIORITY_NUMBER), + Self::HartId => quote!(MAX_HART_ID_NUMBER), + } + } +} + +impl Parse for PacTrait { + fn parse(input: ParseStream) -> syn::Result { + input.parse::()?; + let trait_name: TokenStream2 = input.parse()?; + match trait_name.to_string().as_str() { + "ExceptionNumber" => Ok(Self::Exception), + "CoreInterruptNumber" => Ok(Self::Interrupt(InterruptType::Core)), + "ExternalInterruptNumber" => Ok(Self::Interrupt(InterruptType::External)), + "PriorityNumber" => Ok(Self::Priority), + "HartIdNumber" => Ok(Self::HartId), + _ => Err(syn::Error::new( + trait_name.span(), + "Unknown trait name. Expected: 'ExceptionNumber', 'CoreInterruptNumber', 'ExternalInterruptNumber', 'PriorityNumber', or 'HartIdNumber'", + )), + } + } +} + +/// Marker traits for interrupts +enum InterruptType { + Core, + External, +} + +impl InterruptType { + /// Returns a token stream representing the name of the marker trait + fn marker_trait_name(&self) -> TokenStream2 { + match self { + Self::Core => quote!(CoreInterruptNumber), + Self::External => quote!(ExternalInterruptNumber), + } + } + + /// Returns a token stream representing the name of the array of interrupt service routines + fn isr_array_name(&self) -> TokenStream2 { + match self { + Self::Core => quote!(__CORE_INTERRUPTS), + Self::External => quote!(__EXTERNAL_INTERRUPTS), + } + } + + /// Returns a token stream representing the name of the interrupt dispatch function + fn dispatch_fn_name(&self) -> TokenStream2 { + match self { + Self::Core => quote!(_dispatch_core_interrupt), + Self::External => quote!(_dispatch_external_interrupt), + } + } +} + +/// Struct containing the information needed to implement the `riscv-pac` traits for an enum +struct PacEnumItem { + /// The name of the enum + name: Ident, + /// The maximum discriminant value + max_number: usize, + /// A map from discriminant values to variant names + numbers: HashMap, +} + +impl PacEnumItem { + fn new(input: &DeriveInput) -> Self { + let name = input.ident.clone(); + let (mut numbers, mut max_number) = (HashMap::new(), 0); + + let variants = match &input.data { + Data::Enum(data) => &data.variants, + _ => panic!("Input is not an enum"), + }; + for v in variants.iter() { + let ident = v.ident.clone(); + let value = match &v.discriminant { + Some(d) => match &d.1 { + syn::Expr::Lit(expr_lit) => match &expr_lit.lit { + syn::Lit::Int(lit_int) => match lit_int.base10_parse::() { + Ok(num) => num, + Err(_) => { + panic!("All variant discriminants must be unsigned integers") + } + }, + _ => panic!("All variant discriminants must be unsigned integers"), + }, + _ => panic!("All variant discriminants must be unsigned integers"), + }, + _ => panic!("Variant must have a discriminant"), + }; + + if numbers.insert(value, ident).is_some() { + panic!("Duplicate discriminant value"); + } + if value > max_number { + max_number = value; + } + } + + Self { + name, + max_number, + numbers, + } + } + + /// Returns a token stream representing the maximum discriminant value of the enum + fn max_discriminant(&self) -> TokenStream2 { + TokenStream2::from_str(&format!("{}", self.max_number)).unwrap() + } + + /// Returns a vector of token streams representing the valid matches in the `pac::from_number` function + fn valid_matches(&self) -> Vec { + self.numbers + .iter() + .map(|(num, ident)| { + TokenStream2::from_str(&format!("{num} => Ok(Self::{ident})")).unwrap() + }) + .collect() + } + + /// Returns a vector of token streams representing the interrupt handler functions + fn interrupt_handlers(&self) -> Vec { + self.numbers + .values() + .map(|ident| { + quote! { fn #ident () } + }) + .collect() + } + + /// Returns a sorted vector of token streams representing all the elements of the interrupt array. + /// If an interrupt number is not present in the enum, the corresponding element is `None`. + /// Otherwise, it is `Some()`. + fn interrupt_array(&self) -> Vec { + let mut vectors = vec![]; + for i in 0..=self.max_number { + if let Some(ident) = self.numbers.get(&i) { + vectors.push(quote! { Some(#ident) }); + } else { + vectors.push(quote! { None }); + } + } + vectors + } + + fn vector_table(&self) -> TokenStream2 { + let mut asm = String::from( + r#" +#[cfg(all(feature = "v-trap", any(target_arch = "riscv32", target_arch = "riscv64")))] +core::arch::global_asm!(" + .section .trap, \"ax\" + .global _vector_table + .type _vector_table, @function + + .option push + .balign 0x4 // TODO check if this is the correct alignment + .option norelax + .option norvc + + _vector_table: + j _start_trap // Interrupt 0 is used for exceptions +"#, + ); + + for i in 1..=self.max_number { + if let Some(ident) = self.numbers.get(&i) { + asm.push_str(&format!(" j _start_{ident}_trap\n")); + } else { + asm.push_str(&format!( + " j _start_DefaultHandler_trap // Interrupt {i} is reserved\n" + )); + } + } + + asm.push_str( + r#" .option pop" +);"#, + ); + + TokenStream2::from_str(&asm).unwrap() + } + + /// Returns a vector of token streams representing the trait implementations for + /// the enum. If the trait is an interrupt trait, the implementation also includes + /// the interrupt handler functions and the interrupt array. + fn impl_trait(&self, attr: &PacTrait) -> Vec { + let mut res = vec![]; + + let name = &self.name; + + let trait_name = attr.trait_name(); + let num_type = attr.num_type(); + let const_name = attr.const_name(); + + let max_discriminant = self.max_discriminant(); + let valid_matches = self.valid_matches(); + + // Push the trait implementation + res.push(quote! { + unsafe impl riscv::#trait_name for #name { + const #const_name: #num_type = #max_discriminant; + + #[inline] + fn number(self) -> #num_type { + self as _ + } + + #[inline] + fn from_number(number: #num_type) -> riscv::result::Result { + match number { + #(#valid_matches,)* + _ => Err(riscv::result::Error::InvalidVariant(number as _)), + } + } + } + }); + + // Interrupt traits require additional code + if let PacTrait::Interrupt(interrupt_type) = attr { + let marker_trait_name = interrupt_type.marker_trait_name(); + + let isr_array_name = interrupt_type.isr_array_name(); + let dispatch_fn_name = interrupt_type.dispatch_fn_name(); + + // Push the marker trait implementation + res.push(quote! { unsafe impl riscv::#marker_trait_name for #name {} }); + + let interrupt_handlers = self.interrupt_handlers(); + let interrupt_array = self.interrupt_array(); + + // Push the interrupt handler functions and the interrupt array + res.push(quote! { + extern "C" { + #(#interrupt_handlers;)* + } + + #[no_mangle] + pub static #isr_array_name: [Option; #max_discriminant + 1] = [ + #(#interrupt_array),* + ]; + + #[no_mangle] + unsafe extern "C" fn #dispatch_fn_name(code: usize) { + extern "C" { + fn DefaultHandler(); + } + + if code < #isr_array_name.len() { + let h = &#isr_array_name[code]; + if let Some(handler) = h { + handler(); + } else { + DefaultHandler(); + } + } else { + DefaultHandler(); + } + } + }); + + if let InterruptType::Core = interrupt_type { + res.push(self.vector_table()); + } + } + + res + } +} + +/// Attribute-like macro that implements the traits of the `riscv-pac` crate for a given enum. +/// +/// As these traits are unsafe, the macro must be called with the `unsafe` keyword followed by the trait name. +/// In this way, we warn callers that they must comply with the requirements of the trait. +/// +/// The trait name must be one of `ExceptionNumber`, `InterruptNumber`, `PriorityNumber`, or `HartIdNumber`. +/// Marker traits `CoreInterruptNumber` and `ExternalInterruptNumber` cannot be implemented using this macro. +/// +/// # Safety +/// +/// The struct to be implemented must comply with the requirements of the specified trait. +/// +/// # Example +/// +/// ```rust +/// use riscv::*; +/// +/// #[repr(usize)] +/// #[pac_enum(unsafe ExceptionNumber)] +/// #[derive(Clone, Copy, Debug, Eq, PartialEq)] +/// enum Exception { +/// E1 = 1, +/// E3 = 3, +/// } +/// +/// fn main() { +/// assert_eq!(Exception::E1.number(), 1); +/// assert_eq!(Exception::E3.number(), 3); +/// +/// assert_eq!(Exception::from_number(1), Ok(Exception::E1)); +/// assert_eq!(Exception::from_number(2), Err(2)); +/// assert_eq!(Exception::from_number(3), Ok(Exception::E3)); +/// +/// assert_eq!(Exception::MAX_EXCEPTION_NUMBER, 3); +/// } +///``` +#[proc_macro_attribute] +pub fn pac_enum(attr: TokenStream, item: TokenStream) -> TokenStream { + let input = parse_macro_input!(item as DeriveInput); + let pac_enum = PacEnumItem::new(&input); + + let attr = parse_macro_input!(attr as PacTrait); + + let trait_impl = pac_enum.impl_trait(&attr); + quote! { + #input + #(#trait_impl)* + } + .into() +} diff --git a/riscv/src/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), ) diff --git a/riscv/src/interrupt.rs b/riscv/src/interrupt.rs index 0bd03b7e..697d0345 100644 --- a/riscv/src/interrupt.rs +++ b/riscv/src/interrupt.rs @@ -2,187 +2,67 @@ // NOTE: Adapted from cortex-m/src/interrupt.rs -pub mod machine { - use crate::register::{mepc, mstatus}; +use crate::result::Result; - /// Disables all interrupts in the current hart (machine mode). - #[inline] - pub fn disable() { - // SAFETY: It is safe to disable interrupts - unsafe { mstatus::clear_mie() } - } +// re-export useful riscv-pac traits +pub use riscv_pac::{CoreInterruptNumber, ExceptionNumber, InterruptNumber}; - /// Enables all the interrupts in the current hart (machine mode). - /// - /// # Safety - /// - /// Do not call this function inside a critical section. - #[inline] - pub unsafe fn enable() { - mstatus::set_mie() - } +pub mod machine; +pub mod supervisor; - /// Execute closure `f` with interrupts disabled in the current hart (machine mode). - /// - /// This method does not synchronise multiple harts, so it is not suitable for - /// using as a critical section. See the `critical-section` crate for a cross-platform - /// way to enter a critical section which provides a `CriticalSection` token. - /// - /// This crate provides an implementation for `critical-section` suitable for single-hart systems, - /// based on disabling all interrupts. It can be enabled with the `critical-section-single-hart` feature. - #[inline] - pub fn free(f: F) -> R - where - F: FnOnce() -> R, - { - let mstatus = mstatus::read(); +#[cfg(not(feature = "s-mode"))] +pub use machine::*; +#[cfg(feature = "s-mode")] +pub use supervisor::*; - // disable interrupts - disable(); +/// Trap Cause +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum Trap { + Interrupt(I), + Exception(E), +} - let r = f(); +/// Trap Error +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum TrapError { + InvalidInterrupt(usize), + InvalidException(usize), +} - // If the interrupts were active before our `disable` call, then re-enable - // them. Otherwise, keep them disabled - if mstatus.mie() { - unsafe { enable() }; +impl Trap { + /// Converts a target-specific trap cause to a generic trap cause + #[inline] + pub fn from(trap: Trap) -> Self { + match trap { + Trap::Interrupt(interrupt) => Trap::Interrupt(interrupt.number()), + Trap::Exception(exception) => Trap::Exception(exception.number()), } - - r } - /// Execute closure `f` with interrupts enabled in the current hart (machine mode). - /// - /// This method is assumed to be called within an interrupt handler, and allows - /// nested interrupts to occur. After the closure `f` is executed, the [`mstatus`] - /// and [`mepc`] registers are properly restored to their previous values. - /// - /// # Safety - /// - /// - Do not call this function inside a critical section. - /// - This method is assumed to be called within an interrupt handler. - /// - Make sure to clear the interrupt flag that caused the interrupt before calling - /// this method. Otherwise, the interrupt will be re-triggered before executing `f`. + /// Tries to convert the generic trap cause to a target-specific trap cause #[inline] - pub unsafe fn nested(f: F) -> R + pub fn try_into(self) -> Result> where - F: FnOnce() -> R, + I: CoreInterruptNumber, + E: ExceptionNumber, { - let mstatus = mstatus::read(); - let mepc = mepc::read(); - - // enable interrupts to allow nested interrupts - enable(); - - let r = f(); - - // If the interrupts were inactive before our `enable` call, then re-disable - // them. Otherwise, keep them enabled - if !mstatus.mie() { - disable(); + match self { + Trap::Interrupt(code) => Ok(Trap::Interrupt(I::from_number(code)?)), + Trap::Exception(code) => Ok(Trap::Exception(E::from_number(code)?)), } - - // Restore MSTATUS.PIE, MSTATUS.MPP, and SEPC - if mstatus.mpie() { - mstatus::set_mpie(); - } - mstatus::set_mpp(mstatus.mpp()); - mepc::write(mepc); - - r } } -pub mod supervisor { - use crate::register::{sepc, sstatus}; - - /// Disables all interrupts in the current hart (supervisor mode). - #[inline] - pub fn disable() { - // SAFETY: It is safe to disable interrupts - unsafe { sstatus::clear_sie() } - } - /// Enables all the interrupts in the current hart (supervisor mode). - /// - /// # Safety - /// - /// Do not call this function inside a critical section. +impl Trap { + /// Converts a target-specific trap cause to a generic trap cause #[inline] - pub unsafe fn enable() { - sstatus::set_sie() + pub fn into(self) -> Trap { + Trap::from(self) } - /// Execute closure `f` with interrupts disabled in the current hart (supervisor mode). - /// - /// This method does not synchronise multiple harts, so it is not suitable for - /// using as a critical section. See the `critical-section` crate for a cross-platform - /// way to enter a critical section which provides a `CriticalSection` token. - /// - /// This crate provides an implementation for `critical-section` suitable for single-hart systems, - /// based on disabling all interrupts. It can be enabled with the `critical-section-single-hart` feature. + /// Tries to convert the generic trap cause to a target-specific trap cause #[inline] - pub fn free(f: F) -> R - where - F: FnOnce() -> R, - { - let sstatus = sstatus::read(); - - // disable interrupts - disable(); - - let r = f(); - - // If the interrupts were active before our `disable` call, then re-enable - // them. Otherwise, keep them disabled - if sstatus.sie() { - unsafe { enable() }; - } - - r - } - - /// Execute closure `f` with interrupts enabled in the current hart (supervisor mode). - /// This method is assumed to be called within an interrupt handler, and allows - /// nested interrupts to occur. After the closure `f` is executed, the [`sstatus`] - /// and [`sepc`] registers are properly restored to their previous values. - /// - /// # Safety - /// - /// - Do not call this function inside a critical section. - /// - This method is assumed to be called within an interrupt handler. - /// - Make sure to clear the interrupt flag that caused the interrupt before calling - /// this method. Otherwise, the interrupt will be re-triggered before executing `f`. - #[inline] - pub unsafe fn nested(f: F) -> R - where - F: FnOnce() -> R, - { - let sstatus = sstatus::read(); - let sepc = sepc::read(); - - // enable interrupts to allow nested interrupts - enable(); - - let r = f(); - - // If the interrupts were inactive before our `enable` call, then re-disable - // them. Otherwise, keep them enabled - if !sstatus.sie() { - disable(); - } - - // Restore SSTATUS.SPIE, SSTATUS.SPP, and SEPC - if sstatus.spie() { - sstatus::set_spie(); - } - sstatus::set_spp(sstatus.spp()); - sepc::write(sepc); - - r + pub fn try_from(trap: Trap) -> Result { + trap.try_into() } } - -#[cfg(not(feature = "s-mode"))] -pub use machine::*; -#[cfg(feature = "s-mode")] -pub use supervisor::*; diff --git a/riscv/src/interrupt/machine.rs b/riscv/src/interrupt/machine.rs new file mode 100644 index 00000000..d88ceb46 --- /dev/null +++ b/riscv/src/interrupt/machine.rs @@ -0,0 +1,272 @@ +use crate::{ + interrupt::Trap, + register::{mcause, mepc, mstatus}, +}; +use riscv_pac::{ + result::{Error, Result}, + CoreInterruptNumber, ExceptionNumber, InterruptNumber, +}; + +/// Standard M-mode RISC-V interrupts +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[repr(usize)] +pub enum Interrupt { + SupervisorSoft = 1, + MachineSoft = 3, + SupervisorTimer = 5, + MachineTimer = 7, + SupervisorExternal = 9, + MachineExternal = 11, +} + +/// SAFETY: `Interrupt` represents the standard RISC-V interrupts +unsafe impl InterruptNumber for Interrupt { + const MAX_INTERRUPT_NUMBER: usize = Self::MachineExternal as usize; + + #[inline] + fn number(self) -> usize { + self as usize + } + + #[inline] + fn from_number(value: usize) -> Result { + match value { + 1 => Ok(Self::SupervisorSoft), + 3 => Ok(Self::MachineSoft), + 5 => Ok(Self::SupervisorTimer), + 7 => Ok(Self::MachineTimer), + 9 => Ok(Self::SupervisorExternal), + 11 => Ok(Self::MachineExternal), + _ => Err(Error::InvalidVariant(value)), + } + } +} + +/// SAFETY: `Interrupt` represents the standard RISC-V core interrupts +unsafe impl CoreInterruptNumber for Interrupt {} + +/// Standard M-mode RISC-V exceptions +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[repr(usize)] +pub enum Exception { + InstructionMisaligned = 0, + InstructionFault = 1, + IllegalInstruction = 2, + Breakpoint = 3, + LoadMisaligned = 4, + LoadFault = 5, + StoreMisaligned = 6, + StoreFault = 7, + UserEnvCall = 8, + SupervisorEnvCall = 9, + MachineEnvCall = 11, + InstructionPageFault = 12, + LoadPageFault = 13, + StorePageFault = 15, +} + +/// SAFETY: `Exception` represents the standard RISC-V exceptions +unsafe impl ExceptionNumber for Exception { + const MAX_EXCEPTION_NUMBER: usize = Self::StorePageFault as usize; + + #[inline] + fn number(self) -> usize { + self as usize + } + + #[inline] + fn from_number(value: usize) -> Result { + match value { + 0 => Ok(Self::InstructionMisaligned), + 1 => Ok(Self::InstructionFault), + 2 => Ok(Self::IllegalInstruction), + 3 => Ok(Self::Breakpoint), + 4 => Ok(Self::LoadMisaligned), + 5 => Ok(Self::LoadFault), + 6 => Ok(Self::StoreMisaligned), + 7 => Ok(Self::StoreFault), + 8 => Ok(Self::UserEnvCall), + 9 => Ok(Self::SupervisorEnvCall), + 11 => Ok(Self::MachineEnvCall), + 12 => Ok(Self::InstructionPageFault), + 13 => Ok(Self::LoadPageFault), + 15 => Ok(Self::StorePageFault), + _ => Err(Error::InvalidVariant(value)), + } + } +} + +/// Disables all interrupts in the current hart (machine mode). +#[inline] +pub fn disable() { + // SAFETY: It is safe to disable interrupts + unsafe { mstatus::clear_mie() } +} + +/// Enables all the interrupts in the current hart (machine mode). +/// +/// # Safety +/// +/// Do not call this function inside a critical section. +#[inline] +pub unsafe fn enable() { + mstatus::set_mie() +} + +/// Retrieves the cause of a trap in the current hart (machine mode). +/// +/// This function expects the target-specific interrupt and exception types. +/// If the raw cause is not a valid interrupt or exception for the target, it returns an error. +#[inline] +pub fn try_cause() -> Result> { + mcause::read().cause().try_into() +} + +/// Retrieves the cause of a trap in the current hart (machine mode). +/// +/// This function expects the target-specific interrupt and exception types. +/// If the raw cause is not a valid interrupt or exception for the target, it panics. +#[inline] +pub fn cause() -> Trap { + try_cause().unwrap() +} + +/// Execute closure `f` with interrupts disabled in the current hart (machine mode). +/// +/// This method does not synchronise multiple harts, so it is not suitable for +/// using as a critical section. See the `critical-section` crate for a cross-platform +/// way to enter a critical section which provides a `CriticalSection` token. +/// +/// This crate provides an implementation for `critical-section` suitable for single-hart systems, +/// based on disabling all interrupts. It can be enabled with the `critical-section-single-hart` feature. +#[inline] +pub fn free(f: F) -> R +where + F: FnOnce() -> R, +{ + let mstatus = mstatus::read(); + + // disable interrupts + disable(); + + let r = f(); + + // If the interrupts were active before our `disable` call, then re-enable + // them. Otherwise, keep them disabled + if mstatus.mie() { + unsafe { enable() }; + } + + r +} + +/// Execute closure `f` with interrupts enabled in the current hart (machine mode). +/// +/// This method is assumed to be called within an interrupt handler, and allows +/// nested interrupts to occur. After the closure `f` is executed, the [`mstatus`] +/// and [`mepc`] registers are properly restored to their previous values. +/// +/// # Safety +/// +/// - Do not call this function inside a critical section. +/// - This method is assumed to be called within an interrupt handler. +/// - Make sure to clear the interrupt flag that caused the interrupt before calling +/// this method. Otherwise, the interrupt will be re-triggered before executing `f`. +#[inline] +pub unsafe fn nested(f: F) -> R +where + F: FnOnce() -> R, +{ + let mstatus = mstatus::read(); + let mepc = mepc::read(); + + // enable interrupts to allow nested interrupts + enable(); + + let r = f(); + + // If the interrupts were inactive before our `enable` call, then re-disable + // them. Otherwise, keep them enabled + if !mstatus.mie() { + disable(); + } + + // Restore MSTATUS.PIE, MSTATUS.MPP, and SEPC + if mstatus.mpie() { + mstatus::set_mpie(); + } + mstatus::set_mpp(mstatus.mpp()); + mepc::write(mepc); + + r +} + +#[cfg(test)] +mod test { + use super::*; + use Exception::*; + use Interrupt::*; + + #[test] + fn test_interrupt() { + assert_eq!(Interrupt::from_number(1), Ok(SupervisorSoft)); + assert_eq!(Interrupt::from_number(2), Err(Error::InvalidVariant(2))); + assert_eq!(Interrupt::from_number(3), Ok(MachineSoft)); + assert_eq!(Interrupt::from_number(4), Err(Error::InvalidVariant(4))); + assert_eq!(Interrupt::from_number(5), Ok(SupervisorTimer)); + assert_eq!(Interrupt::from_number(6), Err(Error::InvalidVariant(6))); + assert_eq!(Interrupt::from_number(7), Ok(MachineTimer)); + assert_eq!(Interrupt::from_number(8), Err(Error::InvalidVariant(8))); + assert_eq!(Interrupt::from_number(9), Ok(SupervisorExternal)); + assert_eq!(Interrupt::from_number(10), Err(Error::InvalidVariant(10))); + assert_eq!(Interrupt::from_number(11), Ok(MachineExternal)); + assert_eq!(Interrupt::from_number(12), Err(Error::InvalidVariant(12))); + + assert_eq!(SupervisorSoft.number(), 1); + assert_eq!(MachineSoft.number(), 3); + assert_eq!(SupervisorTimer.number(), 5); + assert_eq!(MachineTimer.number(), 7); + assert_eq!(SupervisorExternal.number(), 9); + assert_eq!(MachineExternal.number(), 11); + + assert_eq!(MachineExternal.number(), Interrupt::MAX_INTERRUPT_NUMBER) + } + + #[test] + fn test_exception() { + assert_eq!(Exception::from_number(0), Ok(InstructionMisaligned)); + assert_eq!(Exception::from_number(1), Ok(InstructionFault)); + assert_eq!(Exception::from_number(2), Ok(IllegalInstruction)); + assert_eq!(Exception::from_number(3), Ok(Breakpoint)); + assert_eq!(Exception::from_number(4), Ok(LoadMisaligned)); + assert_eq!(Exception::from_number(5), Ok(LoadFault)); + assert_eq!(Exception::from_number(6), Ok(StoreMisaligned)); + assert_eq!(Exception::from_number(7), Ok(StoreFault)); + assert_eq!(Exception::from_number(8), Ok(UserEnvCall)); + assert_eq!(Exception::from_number(9), Ok(SupervisorEnvCall)); + assert_eq!(Exception::from_number(10), Err(Error::InvalidVariant(10))); + assert_eq!(Exception::from_number(11), Ok(MachineEnvCall)); + assert_eq!(Exception::from_number(12), Ok(InstructionPageFault)); + assert_eq!(Exception::from_number(13), Ok(LoadPageFault)); + assert_eq!(Exception::from_number(14), Err(Error::InvalidVariant(14))); + assert_eq!(Exception::from_number(15), Ok(StorePageFault)); + assert_eq!(Exception::from_number(16), Err(Error::InvalidVariant(16))); + + assert_eq!(InstructionMisaligned.number(), 0); + assert_eq!(InstructionFault.number(), 1); + assert_eq!(IllegalInstruction.number(), 2); + assert_eq!(Breakpoint.number(), 3); + assert_eq!(LoadMisaligned.number(), 4); + assert_eq!(LoadFault.number(), 5); + assert_eq!(StoreMisaligned.number(), 6); + assert_eq!(StoreFault.number(), 7); + assert_eq!(UserEnvCall.number(), 8); + assert_eq!(SupervisorEnvCall.number(), 9); + assert_eq!(MachineEnvCall.number(), 11); + assert_eq!(InstructionPageFault.number(), 12); + assert_eq!(LoadPageFault.number(), 13); + assert_eq!(StorePageFault.number(), 15); + + assert_eq!(StorePageFault.number(), Exception::MAX_EXCEPTION_NUMBER) + } +} diff --git a/riscv/src/interrupt/supervisor.rs b/riscv/src/interrupt/supervisor.rs new file mode 100644 index 00000000..ed25d236 --- /dev/null +++ b/riscv/src/interrupt/supervisor.rs @@ -0,0 +1,259 @@ +use crate::{ + interrupt::Trap, + register::{scause, sepc, sstatus}, +}; +use riscv_pac::{ + result::{Error, Result}, + CoreInterruptNumber, ExceptionNumber, InterruptNumber, +}; + +/// Interrupt +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +#[repr(usize)] +pub enum Interrupt { + SupervisorSoft = 1, + SupervisorTimer = 5, + SupervisorExternal = 9, +} + +/// SAFETY: `Interrupt` represents the standard RISC-V interrupts +unsafe impl InterruptNumber for Interrupt { + const MAX_INTERRUPT_NUMBER: usize = Self::SupervisorExternal as usize; + + #[inline] + fn number(self) -> usize { + self as usize + } + + #[inline] + fn from_number(value: usize) -> Result { + match value { + 1 => Ok(Self::SupervisorSoft), + 5 => Ok(Self::SupervisorTimer), + 9 => Ok(Self::SupervisorExternal), + _ => Err(Error::InvalidVariant(value)), + } + } +} + +/// SAFETY: `Interrupt` represents the standard RISC-V core interrupts +unsafe impl CoreInterruptNumber for Interrupt {} + +/// Exception +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +#[repr(usize)] +pub enum Exception { + InstructionMisaligned = 0, + InstructionFault = 1, + IllegalInstruction = 2, + Breakpoint = 3, + LoadMisaligned = 4, + LoadFault = 5, + StoreMisaligned = 6, + StoreFault = 7, + UserEnvCall = 8, + SupervisorEnvCall = 9, + InstructionPageFault = 12, + LoadPageFault = 13, + StorePageFault = 15, +} + +/// SAFETY: `Exception` represents the standard RISC-V exceptions +unsafe impl ExceptionNumber for Exception { + const MAX_EXCEPTION_NUMBER: usize = Self::StorePageFault as usize; + + #[inline] + fn number(self) -> usize { + self as usize + } + + #[inline] + fn from_number(value: usize) -> Result { + match value { + 0 => Ok(Self::InstructionMisaligned), + 1 => Ok(Self::InstructionFault), + 2 => Ok(Self::IllegalInstruction), + 3 => Ok(Self::Breakpoint), + 4 => Ok(Self::LoadMisaligned), + 5 => Ok(Self::LoadFault), + 6 => Ok(Self::StoreMisaligned), + 7 => Ok(Self::StoreFault), + 8 => Ok(Self::UserEnvCall), + 9 => Ok(Self::SupervisorEnvCall), + 12 => Ok(Self::InstructionPageFault), + 13 => Ok(Self::LoadPageFault), + 15 => Ok(Self::StorePageFault), + _ => Err(Error::InvalidVariant(value)), + } + } +} + +/// Disables all interrupts in the current hart (supervisor mode). +#[inline] +pub fn disable() { + // SAFETY: It is safe to disable interrupts + unsafe { sstatus::clear_sie() } +} + +/// Enables all the interrupts in the current hart (supervisor mode). +/// +/// # Safety +/// +/// Do not call this function inside a critical section. +#[inline] +pub unsafe fn enable() { + sstatus::set_sie() +} + +/// Retrieves the cause of a trap in the current hart (supervisor mode). +/// +/// This function expects the target-specific interrupt and exception types. +/// If the raw cause is not a valid interrupt or exception for the target, it returns an error. +#[inline] +pub fn try_cause() -> Result> { + scause::read().cause().try_into() +} + +/// Retrieves the cause of a trap in the current hart (supervisor mode). +/// +/// This function expects the target-specific interrupt and exception types. +/// If the raw cause is not a valid interrupt or exception for the target, it panics. +#[inline] +pub fn cause() -> Trap { + try_cause().unwrap() +} + +/// Execute closure `f` with interrupts disabled in the current hart (supervisor mode). +/// +/// This method does not synchronise multiple harts, so it is not suitable for +/// using as a critical section. See the `critical-section` crate for a cross-platform +/// way to enter a critical section which provides a `CriticalSection` token. +/// +/// This crate provides an implementation for `critical-section` suitable for single-hart systems, +/// based on disabling all interrupts. It can be enabled with the `critical-section-single-hart` feature. +#[inline] +pub fn free(f: F) -> R +where + F: FnOnce() -> R, +{ + let sstatus = sstatus::read(); + + // disable interrupts + disable(); + + let r = f(); + + // If the interrupts were active before our `disable` call, then re-enable + // them. Otherwise, keep them disabled + if sstatus.sie() { + unsafe { enable() }; + } + + r +} + +/// Execute closure `f` with interrupts enabled in the current hart (supervisor mode). +/// This method is assumed to be called within an interrupt handler, and allows +/// nested interrupts to occur. After the closure `f` is executed, the [`sstatus`] +/// and [`sepc`] registers are properly restored to their previous values. +/// +/// # Safety +/// +/// - Do not call this function inside a critical section. +/// - This method is assumed to be called within an interrupt handler. +/// - Make sure to clear the interrupt flag that caused the interrupt before calling +/// this method. Otherwise, the interrupt will be re-triggered before executing `f`. +#[inline] +pub unsafe fn nested(f: F) -> R +where + F: FnOnce() -> R, +{ + let sstatus = sstatus::read(); + let sepc = sepc::read(); + + // enable interrupts to allow nested interrupts + enable(); + + let r = f(); + + // If the interrupts were inactive before our `enable` call, then re-disable + // them. Otherwise, keep them enabled + if !sstatus.sie() { + disable(); + } + + // Restore SSTATUS.SPIE, SSTATUS.SPP, and SEPC + if sstatus.spie() { + sstatus::set_spie(); + } + sstatus::set_spp(sstatus.spp()); + sepc::write(sepc); + + r +} + +#[cfg(test)] +mod test { + use super::*; + use Exception::*; + use Interrupt::*; + + #[test] + fn test_interrupt() { + assert_eq!(Interrupt::from_number(1), Ok(SupervisorSoft)); + assert_eq!(Interrupt::from_number(2), Err(Error::InvalidVariant(2))); + assert_eq!(Interrupt::from_number(3), Err(Error::InvalidVariant(3))); + assert_eq!(Interrupt::from_number(4), Err(Error::InvalidVariant(4))); + assert_eq!(Interrupt::from_number(5), Ok(SupervisorTimer)); + assert_eq!(Interrupt::from_number(6), Err(Error::InvalidVariant(6))); + assert_eq!(Interrupt::from_number(7), Err(Error::InvalidVariant(7))); + assert_eq!(Interrupt::from_number(8), Err(Error::InvalidVariant(8))); + assert_eq!(Interrupt::from_number(9), Ok(SupervisorExternal)); + assert_eq!(Interrupt::from_number(10), Err(Error::InvalidVariant(10))); + assert_eq!(Interrupt::from_number(11), Err(Error::InvalidVariant(11))); + assert_eq!(Interrupt::from_number(12), Err(Error::InvalidVariant(12))); + + assert_eq!(SupervisorSoft.number(), 1); + assert_eq!(SupervisorTimer.number(), 5); + assert_eq!(SupervisorExternal.number(), 9); + + assert_eq!(SupervisorExternal.number(), Interrupt::MAX_INTERRUPT_NUMBER) + } + + #[test] + fn test_exception() { + assert_eq!(Exception::from_number(0), Ok(InstructionMisaligned)); + assert_eq!(Exception::from_number(1), Ok(InstructionFault)); + assert_eq!(Exception::from_number(2), Ok(IllegalInstruction)); + assert_eq!(Exception::from_number(3), Ok(Breakpoint)); + assert_eq!(Exception::from_number(4), Ok(LoadMisaligned)); + assert_eq!(Exception::from_number(5), Ok(LoadFault)); + assert_eq!(Exception::from_number(6), Ok(StoreMisaligned)); + assert_eq!(Exception::from_number(7), Ok(StoreFault)); + assert_eq!(Exception::from_number(8), Ok(UserEnvCall)); + assert_eq!(Exception::from_number(9), Ok(SupervisorEnvCall)); + assert_eq!(Exception::from_number(10), Err(Error::InvalidVariant(10))); + assert_eq!(Exception::from_number(11), Err(Error::InvalidVariant(11))); + assert_eq!(Exception::from_number(12), Ok(InstructionPageFault)); + assert_eq!(Exception::from_number(13), Ok(LoadPageFault)); + assert_eq!(Exception::from_number(14), Err(Error::InvalidVariant(14))); + assert_eq!(Exception::from_number(15), Ok(StorePageFault)); + assert_eq!(Exception::from_number(16), Err(Error::InvalidVariant(16))); + + assert_eq!(InstructionMisaligned.number(), 0); + assert_eq!(InstructionFault.number(), 1); + assert_eq!(IllegalInstruction.number(), 2); + assert_eq!(Breakpoint.number(), 3); + assert_eq!(LoadMisaligned.number(), 4); + assert_eq!(LoadFault.number(), 5); + assert_eq!(StoreMisaligned.number(), 6); + assert_eq!(StoreFault.number(), 7); + assert_eq!(UserEnvCall.number(), 8); + assert_eq!(SupervisorEnvCall.number(), 9); + assert_eq!(InstructionPageFault.number(), 12); + assert_eq!(LoadPageFault.number(), 13); + assert_eq!(StorePageFault.number(), 15); + + assert_eq!(StorePageFault.number(), Exception::MAX_EXCEPTION_NUMBER) + } +} diff --git a/riscv/src/lib.rs b/riscv/src/lib.rs index daa859f5..6290c18c 100644 --- a/riscv/src/lib.rs +++ b/riscv/src/lib.rs @@ -40,6 +40,10 @@ pub(crate) mod bits; pub mod delay; pub mod interrupt; pub mod register; + +// Re-export crates of the RISC-V ecosystem +#[cfg(feature = "riscv-macros")] +pub use riscv_macros::*; pub use riscv_pac::*; #[macro_use] diff --git a/riscv/src/register/mcause.rs b/riscv/src/register/mcause.rs index ff7f730b..fde22af4 100644 --- a/riscv/src/register/mcause.rs +++ b/riscv/src/register/mcause.rs @@ -1,5 +1,7 @@ //! mcause register +pub use crate::interrupt::Trap; + /// mcause register #[derive(Clone, Copy, Debug)] pub struct Mcause { @@ -13,109 +15,6 @@ impl From for Mcause { } } -/// Trap Cause -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub enum Trap { - Interrupt(Interrupt), - Exception(Exception), -} - -/// Interrupt -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -#[repr(usize)] -pub enum Interrupt { - SupervisorSoft = 1, - MachineSoft = 3, - SupervisorTimer = 5, - MachineTimer = 7, - SupervisorExternal = 9, - MachineExternal = 11, - Unknown, -} - -/// Exception -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -#[repr(usize)] -pub enum Exception { - InstructionMisaligned = 0, - InstructionFault = 1, - IllegalInstruction = 2, - Breakpoint = 3, - LoadMisaligned = 4, - LoadFault = 5, - StoreMisaligned = 6, - StoreFault = 7, - UserEnvCall = 8, - SupervisorEnvCall = 9, - MachineEnvCall = 11, - InstructionPageFault = 12, - LoadPageFault = 13, - StorePageFault = 15, - Unknown, -} - -impl From for Interrupt { - #[inline] - fn from(nr: usize) -> Self { - match nr { - 1 => Self::SupervisorSoft, - 3 => Self::MachineSoft, - 5 => Self::SupervisorTimer, - 7 => Self::MachineTimer, - 9 => Self::SupervisorExternal, - 11 => Self::MachineExternal, - _ => Self::Unknown, - } - } -} - -impl TryFrom for usize { - type Error = Interrupt; - - #[inline] - fn try_from(value: Interrupt) -> Result { - match value { - Interrupt::Unknown => Err(Self::Error::Unknown), - _ => Ok(value as Self), - } - } -} - -impl From for Exception { - #[inline] - fn from(nr: usize) -> Self { - match nr { - 0 => Self::InstructionMisaligned, - 1 => Self::InstructionFault, - 2 => Self::IllegalInstruction, - 3 => Self::Breakpoint, - 4 => Self::LoadMisaligned, - 5 => Self::LoadFault, - 6 => Self::StoreMisaligned, - 7 => Self::StoreFault, - 8 => Self::UserEnvCall, - 9 => Self::SupervisorEnvCall, - 11 => Self::MachineEnvCall, - 12 => Self::InstructionPageFault, - 13 => Self::LoadPageFault, - 15 => Self::StorePageFault, - _ => Self::Unknown, - } - } -} - -impl TryFrom for usize { - type Error = Exception; - - #[inline] - fn try_from(value: Exception) -> Result { - match value { - Exception::Unknown => Err(Self::Error::Unknown), - _ => Ok(value as Self), - } - } -} - impl Mcause { /// Returns the contents of the register as raw bits #[inline] @@ -129,13 +28,18 @@ impl Mcause { self.bits & !(1 << (usize::BITS as usize - 1)) } - /// Trap Cause + /// Returns the trap cause represented by this register. + /// + /// # Note + /// + /// This method returns a **raw trap cause**, which means that values are represented as `usize`. + /// To get a target-specific trap cause, use [`Trap::try_into`] with your target-specific M-Mode trap cause types. #[inline] - pub fn cause(&self) -> Trap { + pub fn cause(&self) -> Trap { if self.is_interrupt() { - Trap::Interrupt(Interrupt::from(self.code())) + Trap::Interrupt(self.code()) } else { - Trap::Exception(Exception::from(self.code())) + Trap::Exception(self.code()) } } diff --git a/riscv/src/register/scause.rs b/riscv/src/register/scause.rs index 79fa1b9e..ed6ada60 100644 --- a/riscv/src/register/scause.rs +++ b/riscv/src/register/scause.rs @@ -1,106 +1,14 @@ //! scause register +pub use crate::interrupt::Trap; +pub use riscv_pac::{CoreInterruptNumber, ExceptionNumber, InterruptNumber}; // re-export useful riscv-pac traits + /// scause register #[derive(Clone, Copy)] pub struct Scause { bits: usize, } -/// Trap Cause -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub enum Trap { - Interrupt(Interrupt), - Exception(Exception), -} - -/// Interrupt -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -#[repr(usize)] -pub enum Interrupt { - SupervisorSoft = 1, - SupervisorTimer = 5, - SupervisorExternal = 9, - Unknown, -} - -/// Exception -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -#[repr(usize)] -pub enum Exception { - InstructionMisaligned = 0, - InstructionFault = 1, - IllegalInstruction = 2, - Breakpoint = 3, - LoadMisaligned = 4, - LoadFault = 5, - StoreMisaligned = 6, - StoreFault = 7, - UserEnvCall = 8, - SupervisorEnvCall = 9, - InstructionPageFault = 12, - LoadPageFault = 13, - StorePageFault = 15, - Unknown, -} - -impl From for Interrupt { - #[inline] - fn from(nr: usize) -> Self { - match nr { - 1 => Self::SupervisorSoft, - 5 => Self::SupervisorTimer, - 9 => Self::SupervisorExternal, - _ => Self::Unknown, - } - } -} - -impl TryFrom for usize { - type Error = Interrupt; - - #[inline] - fn try_from(value: Interrupt) -> Result { - match value { - Interrupt::Unknown => Err(Self::Error::Unknown), - _ => Ok(value as Self), - } - } -} - -impl From for Exception { - #[inline] - fn from(nr: usize) -> Self { - match nr { - 0 => Self::InstructionMisaligned, - 1 => Self::InstructionFault, - 2 => Self::IllegalInstruction, - 3 => Self::Breakpoint, - 4 => Self::LoadMisaligned, - 5 => Self::LoadFault, - 6 => Self::StoreMisaligned, - 7 => Self::StoreFault, - 8 => Self::UserEnvCall, - 9 => Self::SupervisorEnvCall, - 12 => Self::InstructionPageFault, - 13 => Self::LoadPageFault, - 15 => Self::StorePageFault, - _ => Self::Unknown, - } - } -} - -impl TryFrom for usize { - type Error = Exception; - - #[inline] - fn try_from(value: Exception) -> Result { - match value { - Exception::Unknown => Err(Self::Error::Unknown), - _ => Ok(value as Self), - } - } -} - impl Scause { /// Returns the contents of the register as raw bits #[inline] @@ -114,13 +22,18 @@ impl Scause { self.bits & !(1 << (usize::BITS as usize - 1)) } - /// Trap Cause + /// Returns the trap cause represented by this register. + /// + /// # Note + /// + /// This method returns a **raw trap cause**, which means that values are represented as `usize`. + /// To get a target-specific trap cause, use [`Trap::try_into`] with your target-specific S-Mode trap cause types. #[inline] - pub fn cause(&self) -> Trap { + pub fn cause(&self) -> Trap { if self.is_interrupt() { - Trap::Interrupt(Interrupt::from(self.code())) + Trap::Interrupt(self.code()) } else { - Trap::Exception(Exception::from(self.code())) + Trap::Exception(self.code()) } } @@ -148,13 +61,12 @@ pub unsafe fn write(bits: usize) { /// Set supervisor cause register to corresponding cause. #[inline] -pub unsafe fn set(cause: Trap) { +pub unsafe fn set(cause: Trap) { let bits = match cause { Trap::Interrupt(i) => { - let i = usize::try_from(i).expect("unknown interrupt"); - i | (1 << (usize::BITS as usize - 1)) // interrupt bit is 1 + i.number() | (1 << (usize::BITS as usize - 1)) // interrupt bit is 1 } - Trap::Exception(e) => usize::try_from(e).expect("unknown exception"), + Trap::Exception(e) => e.number(), }; _write(bits); } diff --git a/riscv/tests/test.rs b/riscv/tests/test.rs new file mode 100644 index 00000000..c74b861b --- /dev/null +++ b/riscv/tests/test.rs @@ -0,0 +1,6 @@ +#[test] +fn ui() { + let t = trybuild::TestCases::new(); + t.compile_fail("tests/ui/fail_*.rs"); + t.pass("tests/ui/pass_*.rs"); +} diff --git a/riscv/tests/ui/fail_empty_macro.rs b/riscv/tests/ui/fail_empty_macro.rs new file mode 100644 index 00000000..8891da0a --- /dev/null +++ b/riscv/tests/ui/fail_empty_macro.rs @@ -0,0 +1,9 @@ +#[riscv::pac_enum] +#[derive(Clone, Copy, Debug, PartialEq)] +enum Interrupt { + I1 = 1, + I2 = 2, + I4 = 4, +} + +fn main() {} diff --git a/riscv/tests/ui/fail_empty_macro.stderr b/riscv/tests/ui/fail_empty_macro.stderr new file mode 100644 index 00000000..28c594b7 --- /dev/null +++ b/riscv/tests/ui/fail_empty_macro.stderr @@ -0,0 +1,7 @@ +error: unexpected end of input, expected `unsafe` + --> tests/ui/fail_empty_macro.rs:1:1 + | +1 | #[riscv::pac_enum] + | ^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in the attribute macro `riscv::pac_enum` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/riscv/tests/ui/fail_no_unsafe.rs b/riscv/tests/ui/fail_no_unsafe.rs new file mode 100644 index 00000000..a304fa9e --- /dev/null +++ b/riscv/tests/ui/fail_no_unsafe.rs @@ -0,0 +1,9 @@ +#[riscv::pac_enum(InterruptNumber)] +#[derive(Clone, Copy, Debug, PartialEq)] +enum Interrupt { + I1 = 1, + I2 = 2, + I4 = 4, +} + +fn main() {} diff --git a/riscv/tests/ui/fail_no_unsafe.stderr b/riscv/tests/ui/fail_no_unsafe.stderr new file mode 100644 index 00000000..68fec6f8 --- /dev/null +++ b/riscv/tests/ui/fail_no_unsafe.stderr @@ -0,0 +1,5 @@ +error: expected `unsafe` + --> tests/ui/fail_no_unsafe.rs:1:19 + | +1 | #[riscv::pac_enum(InterruptNumber)] + | ^^^^^^^^^^^^^^^ diff --git a/riscv/tests/ui/fail_unknown_trait.rs b/riscv/tests/ui/fail_unknown_trait.rs new file mode 100644 index 00000000..dc6c5d44 --- /dev/null +++ b/riscv/tests/ui/fail_unknown_trait.rs @@ -0,0 +1,9 @@ +#[riscv::pac_enum(unsafe InterruptNumber)] +#[derive(Clone, Copy, Debug, PartialEq)] +enum Interrupt { + I1 = 1, + I2 = 2, + I4 = 4, +} + +fn main() {} diff --git a/riscv/tests/ui/fail_unknown_trait.stderr b/riscv/tests/ui/fail_unknown_trait.stderr new file mode 100644 index 00000000..337f8548 --- /dev/null +++ b/riscv/tests/ui/fail_unknown_trait.stderr @@ -0,0 +1,5 @@ +error: Unknown trait name. Expected: 'ExceptionNumber', 'CoreInterruptNumber', 'ExternalInterruptNumber', 'PriorityNumber', or 'HartIdNumber' + --> tests/ui/fail_unknown_trait.rs:1:26 + | +1 | #[riscv::pac_enum(unsafe InterruptNumber)] + | ^^^^^^^^^^^^^^^ diff --git a/riscv/tests/ui/pass_test.rs b/riscv/tests/ui/pass_test.rs new file mode 100644 index 00000000..c80ead3e --- /dev/null +++ b/riscv/tests/ui/pass_test.rs @@ -0,0 +1,116 @@ +use riscv::result::Error; +use riscv::*; + +#[pac_enum(unsafe ExceptionNumber)] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +enum Exception { + E1 = 1, + E3 = 3, +} + +#[pac_enum(unsafe CoreInterruptNumber)] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +enum Interrupt { + I1 = 1, + I2 = 2, + I4 = 4, + I7 = 7, +} + +#[pac_enum(unsafe PriorityNumber)] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +enum Priority { + P0 = 0, + P1 = 1, + P2 = 2, + P3 = 3, +} + +#[pac_enum(unsafe HartIdNumber)] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +enum HartId { + H0 = 0, + H1 = 1, + H2 = 2, +} + +mod isr { + #[export_name = "DefaultHandler"] + fn default_handler() {} + + #[export_name = "I1"] + fn i1() {} + + #[export_name = "I2"] + fn i2() {} + + #[export_name = "I4"] + fn i4() {} + + #[export_name = "I7"] + fn i7() {} +} + +fn main() { + assert_eq!(Exception::E1.number(), 1); + assert_eq!(Exception::E3.number(), 3); + + assert_eq!(Exception::from_number(0), Err(Error::InvalidVariant(0))); + assert_eq!(Exception::from_number(1), Ok(Exception::E1)); + assert_eq!(Exception::from_number(2), Err(Error::InvalidVariant(2))); + assert_eq!(Exception::from_number(3), Ok(Exception::E3)); + assert_eq!(Exception::from_number(4), Err(Error::InvalidVariant(4))); + + assert_eq!(Exception::MAX_EXCEPTION_NUMBER, 3); + + assert_eq!(Interrupt::I1.number(), 1); + assert_eq!(Interrupt::I2.number(), 2); + assert_eq!(Interrupt::I4.number(), 4); + assert_eq!(Interrupt::I7.number(), 7); + + assert_eq!(Interrupt::from_number(0), Err(Error::InvalidVariant(0))); + assert_eq!(Interrupt::from_number(1), Ok(Interrupt::I1)); + assert_eq!(Interrupt::from_number(2), Ok(Interrupt::I2)); + assert_eq!(Interrupt::from_number(3), Err(Error::InvalidVariant(3))); + assert_eq!(Interrupt::from_number(4), Ok(Interrupt::I4)); + assert_eq!(Interrupt::from_number(5), Err(Error::InvalidVariant(5))); + assert_eq!(Interrupt::from_number(6), Err(Error::InvalidVariant(6))); + assert_eq!(Interrupt::from_number(7), Ok(Interrupt::I7)); + + assert_eq!(Interrupt::MAX_INTERRUPT_NUMBER, 7); + + assert_eq!(__CORE_INTERRUPTS.len(), Interrupt::MAX_INTERRUPT_NUMBER + 1); + + assert!(__CORE_INTERRUPTS[0].is_none()); + assert!(__CORE_INTERRUPTS[1].is_some()); + assert!(__CORE_INTERRUPTS[2].is_some()); + assert!(__CORE_INTERRUPTS[3].is_none()); + assert!(__CORE_INTERRUPTS[4].is_some()); + assert!(__CORE_INTERRUPTS[5].is_none()); + assert!(__CORE_INTERRUPTS[6].is_none()); + assert!(__CORE_INTERRUPTS[7].is_some()); + + assert_eq!(Priority::P0.number(), 0); + assert_eq!(Priority::P1.number(), 1); + assert_eq!(Priority::P2.number(), 2); + assert_eq!(Priority::P3.number(), 3); + + assert_eq!(Priority::from_number(0), Ok(Priority::P0)); + assert_eq!(Priority::from_number(1), Ok(Priority::P1)); + assert_eq!(Priority::from_number(2), Ok(Priority::P2)); + assert_eq!(Priority::from_number(3), Ok(Priority::P3)); + assert_eq!(Priority::from_number(4), Err(Error::InvalidVariant(4))); + + assert_eq!(Priority::MAX_PRIORITY_NUMBER, 3); + + assert_eq!(HartId::H0.number(), 0); + assert_eq!(HartId::H1.number(), 1); + assert_eq!(HartId::H2.number(), 2); + + assert_eq!(HartId::from_number(0), Ok(HartId::H0)); + assert_eq!(HartId::from_number(1), Ok(HartId::H1)); + assert_eq!(HartId::from_number(2), Ok(HartId::H2)); + assert_eq!(HartId::from_number(3), Err(Error::InvalidVariant(3))); + + assert_eq!(HartId::MAX_HART_ID_NUMBER, 2); +}