diff --git a/.github/workflows/riscv-rt.yaml b/.github/workflows/riscv-rt.yaml index be49bbce..a1727162 100644 --- a/.github/workflows/riscv-rt.yaml +++ b/.github/workflows/riscv-rt.yaml @@ -7,7 +7,7 @@ on: name: Build check (riscv-rt) jobs: - build: + build-riscv: strategy: matrix: # All generated code should be running on stable now, MRSV is 1.61.0 @@ -45,11 +45,27 @@ jobs: run: RUSTFLAGS="-C link-arg=-Triscv-rt/examples/device.x" cargo build --package riscv-rt --target ${{ matrix.target }} --example ${{ matrix.example }} --features=s-mode,single-hart,v-trap - name : Build (u-boot) run: RUSTFLAGS="-C link-arg=-Triscv-rt/examples/device.x" cargo build --package riscv-rt --target ${{ matrix.target }} --example empty --features=u-boot - + + build-others: + strategy: + matrix: + os: [ macos-latest, ubuntu-latest, windows-latest ] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + - name: Build (no features) + run: cargo build --package riscv-rt + - name: Build (all features but u-boot) + run: cargo build --package riscv-rt --features=s-mode,single-hart,v-trap + - name: Build (u-boot) + run: cargo build --package riscv-rt --features=u-boot + # Job to check that all the builds succeeded build-check: needs: - - build + - build-riscv + - build-others runs-on: ubuntu-latest if: always() steps: diff --git a/.github/workflows/riscv-semihosting.yaml b/.github/workflows/riscv-semihosting.yaml index 4f6da8b0..3ba711ec 100644 --- a/.github/workflows/riscv-semihosting.yaml +++ b/.github/workflows/riscv-semihosting.yaml @@ -11,8 +11,8 @@ jobs: build-riscv: strategy: matrix: - # All generated code should be running on stable now, MRSV is 1.60.0 - toolchain: [ stable, nightly, 1.60.0 ] + # All generated code should be running on stable now, MRSV is 1.61.0 + toolchain: [ stable, nightly, 1.61.0 ] target: - riscv32i-unknown-none-elf - riscv32imc-unknown-none-elf diff --git a/.github/workflows/riscv.yaml b/.github/workflows/riscv.yaml index cd03b872..21aac5f9 100644 --- a/.github/workflows/riscv.yaml +++ b/.github/workflows/riscv.yaml @@ -11,8 +11,8 @@ jobs: build-riscv: strategy: matrix: - # All generated code should be running on stable now, MRSV is 1.60.0 - toolchain: [ stable, nightly, 1.60.0 ] + # All generated code should be running on stable now, MRSV is 1.61.0 + toolchain: [ stable, nightly, 1.61.0 ] target: - riscv32i-unknown-none-elf - riscv32imc-unknown-none-elf diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml new file mode 100644 index 00000000..a7ab62c7 --- /dev/null +++ b/.github/workflows/tests.yaml @@ -0,0 +1,28 @@ +on: + push: + branches: [ master ] + pull_request: + merge_group: + +name: Run macro tests (tests) + +jobs: + run-tests: + strategy: + matrix: + os: [ macos-latest, ubuntu-latest ] # windows shows weird linking errors + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@nightly + - name: Run tests + run: cargo test --package tests + + # Job to check that all the builds succeeded + tests-check: + needs: + - run-tests + runs-on: ubuntu-latest + if: always() + steps: + - run: jq --exit-status 'all(.result == "success")' <<< '${{ toJson(needs) }}' diff --git a/Cargo.toml b/Cargo.toml index 5e73fed7..ba28eae6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,4 +6,5 @@ members = [ "riscv-peripheral", "riscv-rt", "riscv-semihosting", + "tests", ] diff --git a/riscv-pac/CHANGELOG.md b/riscv-pac/CHANGELOG.md index 0303047f..969e9b32 100644 --- a/riscv-pac/CHANGELOG.md +++ b/riscv-pac/CHANGELOG.md @@ -10,6 +10,13 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Added - Add `result` module for `Error` and `Result` types +- Add `ExceptionNumber` trait. +- Classify interrupt numbers in `CoreInterruptNumber` and `ExternalInterruptNumber`. +- Added simple tests to illustrate how to implement all the provided traits. + +### Changed + +- All traits now work with `usize` data type. ## [v0.1.1] - 2024-02-15 diff --git a/riscv-pac/Cargo.toml b/riscv-pac/Cargo.toml index b1cb2ae4..b5ba86b2 100644 --- a/riscv-pac/Cargo.toml +++ b/riscv-pac/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "riscv-pac" -version = "0.1.1" +version = "0.2.0" edition = "2021" rust-version = "1.60" repository = "https://github.com/rust-embedded/riscv" diff --git a/riscv-pac/src/lib.rs b/riscv-pac/src/lib.rs index 4da1170a..f0929b3d 100644 --- a/riscv-pac/src/lib.rs +++ b/riscv-pac/src/lib.rs @@ -4,37 +4,84 @@ 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. + 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; + /// Tries to convert a number to a valid interrupt. + 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 `usize` of its priority level. /// /// # Safety /// @@ -46,21 +93,19 @@ pub unsafe trait InterruptNumber: Copy { /// * `MAX_PRIORITY_NUMBER` must coincide with the highest allowed priority number. pub unsafe trait PriorityNumber: Copy { /// Number assigned to the highest priority level. - const MAX_PRIORITY_NUMBER: u8; + const MAX_PRIORITY_NUMBER: usize; /// Converts a priority level to its corresponding number. - fn number(self) -> u8; + fn number(self) -> usize; /// Tries to convert a number to a valid priority level. - /// If the conversion fails, it returns an error with the number back. - fn from_number(value: u8) -> Result; + fn from_number(value: usize) -> Result; } /// Trait for enums of HART identifiers. /// -/// This trait should be implemented by a peripheral access crate (PAC) -/// on its enum of available HARTs for a specific device. -/// Each variant must convert to a `u16` of its HART ID number. +/// 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 `usize` of its HART ID number. /// /// # Safety /// @@ -72,12 +117,173 @@ pub unsafe trait PriorityNumber: Copy { /// * `MAX_HART_ID_NUMBER` must coincide with the highest allowed HART ID number. pub unsafe trait HartIdNumber: Copy { /// Highest number assigned to a context. - const MAX_HART_ID_NUMBER: u16; + const MAX_HART_ID_NUMBER: usize; /// Converts a HART ID to its corresponding number. - fn number(self) -> u16; + fn number(self) -> usize; /// Tries to convert a number to a valid HART ID. - /// If the conversion fails, it returns an error with the number back. - fn from_number(value: u16) -> Result; + fn from_number(value: usize) -> Result; +} + +#[cfg(test)] +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: usize = Self::P3 as usize; + + #[inline] + fn number(self) -> usize { + self as _ + } + + #[inline] + fn from_number(number: usize) -> Result { + match number { + 0 => Ok(Priority::P0), + 1 => Ok(Priority::P1), + 2 => Ok(Priority::P2), + 3 => Ok(Priority::P3), + _ => Err(Error::InvalidVariant(number)), + } + } + } + + unsafe impl HartIdNumber for HartId { + const MAX_HART_ID_NUMBER: usize = Self::H2 as usize; + + #[inline] + fn number(self) -> usize { + self as _ + } + + #[inline] + fn from_number(number: usize) -> Result { + match number { + 0 => Ok(HartId::H0), + 1 => Ok(HartId::H1), + 2 => Ok(HartId::H2), + _ => Err(Error::InvalidVariant(number)), + } + } + } + + #[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..e4703980 100644 --- a/riscv-peripheral/CHANGELOG.md +++ b/riscv-peripheral/CHANGELOG.md @@ -11,6 +11,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - use `riscv-pac` result types for trait implementations +### Changed + +- Adapt to new version of `riscv-pac` traits. +- `PLIC` now expects interrupt enums to implement the `riscv_pac::ExternalInterruptNumber` trait. + ### Fixed - `clippy` fixes diff --git a/riscv-peripheral/Cargo.toml b/riscv-peripheral/Cargo.toml index e54efa9f..8922e21f 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.12.0" } +riscv-pac = { path = "../riscv-pac", version = "0.2.0" } [dev-dependencies] heapless = "0.8.0" diff --git a/riscv-peripheral/examples/e310x.rs b/riscv-peripheral/examples/e310x.rs index 6f608b6c..60e28c61 100644 --- a/riscv-peripheral/examples/e310x.rs +++ b/riscv-peripheral/examples/e310x.rs @@ -2,36 +2,35 @@ //! 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: usize = Self::H0 as usize; #[inline] - fn number(self) -> u16 { + fn number(self) -> usize { self as _ } #[inline] - fn from_number(number: u16) -> Result { - if number > Self::MAX_HART_ID_NUMBER { - Err(Error::InvalidVariant(number as usize)) - } else { - // SAFETY: valid context number - Ok(unsafe { core::mem::transmute(number) }) + fn from_number(number: usize) -> Result { + match number { + 0 => Ok(Self::H0), + _ => Err(Error::InvalidVariant(number)), } } } #[derive(Copy, Clone, Debug, PartialEq, Eq)] -#[repr(u16)] +#[repr(usize)] pub enum Interrupt { WATCHDOG = 1, RTC = 2, @@ -88,26 +87,28 @@ 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)) + Err(Error::InvalidVariant(number)) } else { // SAFETY: valid interrupt number - Ok(unsafe { core::mem::transmute(number) }) + Ok(unsafe { core::mem::transmute::(number) }) } } } +unsafe impl ExternalInterruptNumber for Interrupt {} + #[derive(Copy, Clone, Debug, PartialEq, Eq)] -#[repr(u8)] +#[repr(usize)] pub enum Priority { P0 = 0, P1 = 1, @@ -120,20 +121,20 @@ pub enum Priority { } unsafe impl PriorityNumber for Priority { - const MAX_PRIORITY_NUMBER: u8 = 7; + const MAX_PRIORITY_NUMBER: usize = Self::P7 as usize; #[inline] - fn number(self) -> u8 { + fn number(self) -> usize { self as _ } #[inline] - fn from_number(number: u8) -> Result { + fn from_number(number: usize) -> Result { if number > Self::MAX_PRIORITY_NUMBER { - Err(Error::InvalidVariant(number as usize)) + Err(Error::InvalidVariant(number)) } else { // SAFETY: valid priority number - Ok(unsafe { core::mem::transmute(number) }) + Ok(unsafe { core::mem::transmute::(number) }) } } } diff --git a/riscv-peripheral/src/aclint.rs b/riscv-peripheral/src/aclint.rs index 873daeda..096aa4f3 100644 --- a/riscv-peripheral/src/aclint.rs +++ b/riscv-peripheral/src/aclint.rs @@ -66,7 +66,7 @@ pub(crate) mod test { use riscv_pac::result::{Error, Result}; #[derive(Clone, Copy, Debug, Eq, PartialEq)] - #[repr(u16)] + #[repr(usize)] pub(crate) enum HartId { H0 = 0, H1 = 1, @@ -74,20 +74,20 @@ pub(crate) mod test { } unsafe impl HartIdNumber for HartId { - const MAX_HART_ID_NUMBER: u16 = 2; + const MAX_HART_ID_NUMBER: usize = Self::H2 as usize; #[inline] - fn number(self) -> u16 { + fn number(self) -> usize { self as _ } #[inline] - fn from_number(number: u16) -> Result { + fn from_number(number: usize) -> Result { if number > Self::MAX_HART_ID_NUMBER { - Err(Error::InvalidVariant(number as usize)) + Err(Error::InvalidVariant(number)) } else { // SAFETY: valid context number - Ok(unsafe { core::mem::transmute(number) }) + Ok(unsafe { core::mem::transmute::(number) }) } } } diff --git a/riscv-peripheral/src/aclint/mswi.rs b/riscv-peripheral/src/aclint/mswi.rs index 4e85121a..d24d7b07 100644 --- a/riscv-peripheral/src/aclint/mswi.rs +++ b/riscv-peripheral/src/aclint/mswi.rs @@ -33,7 +33,7 @@ impl MSWI { #[inline] pub fn msip(&self, hart_id: H) -> MSIP { // SAFETY: `hart_id` is valid for the target - unsafe { MSIP::new(self.msip0.get_ptr().offset(hart_id.number() as _) as _) } + unsafe { MSIP::new(self.msip0.get_ptr().add(hart_id.number()) as _) } } /// Returns the `MSIP` register for the current HART. diff --git a/riscv-peripheral/src/aclint/mtimer.rs b/riscv-peripheral/src/aclint/mtimer.rs index 5e5e1c1c..707c2de9 100644 --- a/riscv-peripheral/src/aclint/mtimer.rs +++ b/riscv-peripheral/src/aclint/mtimer.rs @@ -35,7 +35,7 @@ impl MTIMER { #[inline] pub fn mtimecmp(&self, hart_id: H) -> MTIMECMP { // SAFETY: `hart_id` is valid for the target - unsafe { MTIMECMP::new(self.mtimecmp0.get_ptr().offset(hart_id.number() as _) as _) } + unsafe { MTIMECMP::new(self.mtimecmp0.get_ptr().add(hart_id.number()) as _) } } /// Returns the `MTIMECMP` register for the current HART. diff --git a/riscv-peripheral/src/aclint/sswi.rs b/riscv-peripheral/src/aclint/sswi.rs index 51072d66..048bd300 100644 --- a/riscv-peripheral/src/aclint/sswi.rs +++ b/riscv-peripheral/src/aclint/sswi.rs @@ -64,7 +64,7 @@ impl SSWI { #[inline] pub fn setssip(&self, hart_id: H) -> SETSSIP { // SAFETY: `hart_id` is valid for the target - unsafe { SETSSIP::new(self.setssip0.get_ptr().offset(hart_id.number() as _) as _) } + unsafe { SETSSIP::new(self.setssip0.get_ptr().add(hart_id.number()) as _) } } } diff --git a/riscv-peripheral/src/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..a6272fe8 100644 --- a/riscv-peripheral/src/macros.rs +++ b/riscv-peripheral/src/macros.rs @@ -3,10 +3,12 @@ /// Macro to create interfaces to CLINT peripherals in PACs. /// The resulting struct will be named `CLINT`, and will provide safe access to the CLINT registers. /// -/// This macro expects 4 different argument types: +/// This macro expects 5 different argument types: /// /// - Base address (**MANDATORY**): base address of the CLINT peripheral of the target. /// - Frequency (**OPTIONAL**): clock frequency (in Hz) of the `MTIME` register. It enables the `delay` method of the `CLINT` struct. +/// - Async flag (**OPTIONAL**): It enables the `async_delay` method of the `CLINT struct`. +/// You must activate the `embedded-hal-async` feature to use this flag. /// - Per-HART mtimecmp registers (**OPTIONAL**): a list of `mtimecmp` registers for easing access to per-HART mtimecmp regs. /// - Per-HART msip registers (**OPTIONAL**): a list of `msip` registers for easing access to per-HART msip regs. /// @@ -17,41 +19,37 @@ /// ## 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` /// unsafe impl riscv_peripheral::aclint::HartIdNumber for HartId { -/// const MAX_HART_ID_NUMBER: u16 = 2; -/// fn number(self) -> u16 { self as _ } -/// fn from_number(number: u16) -> Result { -/// if number > Self::MAX_HART_ID_NUMBER { -/// Err(Error::InvalidVariant(number as usize)) -/// } else { -/// // SAFETY: valid context number -/// Ok(unsafe { core::mem::transmute(number) }) +/// const MAX_HART_ID_NUMBER: usize = Self::H2 as usize; +/// fn number(self) -> usize { self as _ } +/// fn from_number(number: usize) -> Result { +/// match number { +/// 0 => Ok(HartId::H0), +/// 1 => Ok(HartId::H1), +/// 2 => Ok(HartId::H2), +/// _ => Err(Error::InvalidVariant(number)), /// } /// } /// } /// -/// clint_codegen!( +/// riscv_peripheral::clint_codegen!( /// base 0x0200_0000, /// mtimecmps [mtimecmp0 = (HartId::H0, "`H0`"), mtimecmp1 = (HartId::H1, "`H1`"), mtimecmp2 = (HartId::H2, "`H2`")], /// msips [msip0=(HartId::H0,"`H0`"), msip1=(HartId::H1,"`H1`"), msip2=(HartId::H2,"`H2`")], // do not forget the ending comma! @@ -206,7 +204,7 @@ macro_rules! clint_codegen { /// /// # Note /// - /// You must export the `riscv_peripheral::hal::delay::DelayNs` trait in order to use delay methods. + /// You must export the [`embedded_hal::delay::DelayNs`] trait in order to use delay methods. #[inline] pub const fn delay() -> $crate::hal::aclint::Delay { $crate::hal::aclint::Delay::new(Self::mtime(), Self::freq()) @@ -220,7 +218,7 @@ macro_rules! clint_codegen { /// /// # Note /// - /// You must export the `riscv_peripheral::hal_async::delay::DelayNs` trait in order to use delay methods. + /// You must export the [`embedded_hal_async::delay::DelayNs`] trait in order to use delay methods. /// /// This implementation relies on the machine-level timer interrupts to wake futures. /// Therefore, it needs to schedule the machine-level timer interrupts via the `MTIMECMP` register assigned to the current HART. @@ -264,6 +262,27 @@ macro_rules! clint_codegen { } /// Macro to create interfaces to PLIC peripherals in PACs. +/// The resulting struct will be named `PLIC`, and will provide safe access to the PLIC registers. +/// +/// This macro expects 2 different argument types: +/// +/// - Base address (**MANDATORY**): base address of the PLIC peripheral of the target. +/// - Per-HART contexts (**OPTIONAL**): a list of `ctx` contexts for easing access to per-HART PLIC contexts. +/// +/// Check the examples below for more details about the usage and syntax of this macro. +/// +/// # Example +/// +/// ## Base address only +/// +/// ``` +/// use riscv_peripheral::clint_codegen; +/// +/// riscv_peripheral::plic_codegen!(base 0x0C00_0000,); // do not forget the ending comma! +/// +/// let priorities = PLIC::priorities(); // Priorities registers +/// let pendings = PLIC::pendings(); // Pendings registers +/// ``` #[macro_export] macro_rules! plic_codegen { () => { @@ -335,7 +354,7 @@ macro_rules! plic_codegen { /// This function determines the current HART ID by reading the [`riscv::register::mhartid`] CSR. /// Thus, it can only be used in M-mode. For S-mode, use [`PLIC::ctx`] instead. #[inline] - pub fn ctx_mhartid(&self) -> $crate::plic::CTX { + pub fn ctx_mhartid() -> $crate::plic::CTX { $crate::plic::PLIC::::ctx_mhartid() } } diff --git a/riscv-peripheral/src/plic.rs b/riscv-peripheral/src/plic.rs index 116689a6..596a8795 100644 --- a/riscv-peripheral/src/plic.rs +++ b/riscv-peripheral/src/plic.rs @@ -64,7 +64,7 @@ impl PLIC

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

{ // SAFETY: valid context number - unsafe { CTX::new(hart_id.number()) } + unsafe { CTX::new(hart_id.number() as _) } } /// Returns the PLIC HART context for the current HART. @@ -145,11 +145,10 @@ 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)] pub(crate) enum Interrupt { I1 = 1, I2 = 2, @@ -158,7 +157,6 @@ pub(crate) mod test { } #[derive(Clone, Copy, Debug, Eq, PartialEq)] - #[repr(u8)] pub(crate) enum Priority { P0 = 0, P1 = 1, @@ -167,7 +165,6 @@ pub(crate) mod test { } #[derive(Clone, Copy, Debug, Eq, PartialEq)] - #[repr(u16)] pub(crate) enum Context { C0 = 0, C1 = 1, @@ -175,58 +172,62 @@ pub(crate) mod test { } unsafe impl InterruptNumber for Interrupt { - const MAX_INTERRUPT_NUMBER: u16 = 4; + const MAX_INTERRUPT_NUMBER: usize = Self::I4 as usize; #[inline] - fn number(self) -> u16 { + fn number(self) -> usize { self as _ } #[inline] - fn from_number(number: u16) -> Result { - if number > Self::MAX_INTERRUPT_NUMBER || number == 0 { - Err(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; + const MAX_PRIORITY_NUMBER: usize = Self::P3 as usize; #[inline] - fn number(self) -> u8 { + fn number(self) -> usize { self as _ } #[inline] - fn from_number(number: u8) -> Result { - if number > Self::MAX_PRIORITY_NUMBER { - Err(Error::InvalidVariant(number as usize)) - } else { - // SAFETY: valid priority number - Ok(unsafe { core::mem::transmute(number) }) + fn from_number(number: usize) -> Result { + match number { + 0 => Ok(Priority::P0), + 1 => Ok(Priority::P1), + 2 => Ok(Priority::P2), + 3 => Ok(Priority::P3), + _ => Err(Error::InvalidVariant(number)), } } } unsafe impl HartIdNumber for Context { - const MAX_HART_ID_NUMBER: u16 = 2; + const MAX_HART_ID_NUMBER: usize = Self::C2 as usize; #[inline] - fn number(self) -> u16 { + fn number(self) -> usize { self as _ } #[inline] - fn from_number(number: u16) -> Result { - if number > Self::MAX_HART_ID_NUMBER { - Err(Error::InvalidVariant(number as usize)) - } else { - // SAFETY: valid context number - Ok(unsafe { core::mem::transmute(number) }) + fn from_number(number: usize) -> Result { + match number { + 0 => Ok(Context::C0), + 1 => Ok(Context::C1), + 2 => Ok(Context::C2), + _ => Err(Error::InvalidVariant(number)), } } } 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-rt/CHANGELOG.md b/riscv-rt/CHANGELOG.md index d224e5cd..659ded45 100644 --- a/riscv-rt/CHANGELOG.md +++ b/riscv-rt/CHANGELOG.md @@ -9,21 +9,28 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Added +- Add integration tests to check that macros work as expected. +- Add `no-exceptions` feature to opt-out the default implementation of `_dispatch_exception` +- Add `no-interrupts` feature to opt-out the default implementation of `_dispatch_core_interrupt` - Add `pre_init_trap` to detect early errors during the boot process. - Add `v-trap` feature to enable interrupt handling in vectored mode. -- Add `interrupt` proc macro to help defining interrupt handlers. -If `v-trap` feature is enabled, this macro also generates its corresponding trap. +- Add `core_interrupt` proc macro to help defining core interrupt handlers. + If `v-trap` feature is enabled, this macro also generates its corresponding trap. +- Add `external_interrupt` proc macro to help defining external interrupt handlers. +- Add `exception` proc macro to help defining exception handlers. + If `v-trap` feature is enabled, this macro also generates its corresponding trap. - Add `u-boot` feature, so that you can start your elf binary with u-boot and work with passed arguments. ### Changed +- Use `cfg_attr` in `start_trap_rust` to allow compilation in non-riscv targets. - Moved all the assembly code to `asm.rs` - Use `weak` symbols for functions such as `_mp_hook` or `_start_trap` - `abort` is now `weak`, so it is possible to link third-party libraries including this symbol. - Made `cfg` variable selection more robust for custom targets -- `_start_trap_rust` now only deals with exceptions. When an interrupt is detected, it now calls -to `_dispatch_interrupt`. +- `_start_trap_rust` now relies on `_dispatch_exception` and `_dispatch_core_interrupt`. + This change allows more flexibility for targets with non-standard exceptions and interrupts. - Upgrade rust-version to 1.61 - Update `syn` to version 2.0 diff --git a/riscv-rt/Cargo.toml b/riscv-rt/Cargo.toml index eb2e212f..245aa565 100644 --- a/riscv-rt/Cargo.toml +++ b/riscv-rt/Cargo.toml @@ -12,8 +12,16 @@ license = "ISC" edition = "2021" links = "riscv-rt" # Prevent multiple versions of riscv-rt being linked +[package.metadata.docs.rs] +default-target = "riscv64imac-unknown-none-elf" +targets = [ + "riscv32i-unknown-none-elf", "riscv32imc-unknown-none-elf", "riscv32imac-unknown-none-elf", + "riscv64imac-unknown-none-elf", "riscv64gc-unknown-none-elf", +] + [dependencies] -riscv = {path = "../riscv", version = "0.11.1"} +riscv = { path = "../riscv", version = "0.12.0" } +riscv-pac = { path = "../riscv-pac", version = "0.2.0" } riscv-rt-macros = { path = "macros", version = "0.2.1" } [dev-dependencies] @@ -24,3 +32,5 @@ s-mode = ["riscv-rt-macros/s-mode"] single-hart = [] v-trap = ["riscv-rt-macros/v-trap"] u-boot = ["riscv-rt-macros/u-boot", "single-hart"] +no-interrupts = [] +no-exceptions = [] diff --git a/riscv-rt/examples/empty.rs b/riscv-rt/examples/empty.rs index b770cba0..cac3488b 100644 --- a/riscv-rt/examples/empty.rs +++ b/riscv-rt/examples/empty.rs @@ -2,9 +2,39 @@ #![no_main] extern crate panic_halt; -extern crate riscv_rt; -use riscv_rt::{entry, interrupt}; +use riscv_rt::{core_interrupt, entry, exception, external_interrupt}; + +use riscv::{ + interrupt::{Exception, Interrupt}, + result::*, +}; + +/// Just a dummy type to test the `ExternalInterrupt` trait. +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum ExternalInterrupt { + GPIO, + UART, +} + +unsafe impl riscv::InterruptNumber for ExternalInterrupt { + const MAX_INTERRUPT_NUMBER: usize = 1; + + #[inline] + fn number(self) -> usize { + self as usize + } + + #[inline] + fn from_number(value: usize) -> Result { + match value { + 0 => Ok(Self::GPIO), + 1 => Ok(Self::UART), + _ => Err(Error::InvalidVariant(value)), + } + } +} +unsafe impl riscv::ExternalInterruptNumber for ExternalInterrupt {} #[entry] fn main() -> ! { @@ -12,8 +42,58 @@ fn main() -> ! { loop {} } -#[interrupt] -fn MachineSoft() { +/* EXAMPLES OF USING THE core_interrupt MACRO FOR CORE INTERRUPT HANDLERS. +IF v-trap ENABLED, THE MACRO ALSO DEFINES _start_COREINTERRUPT_trap routines */ + +/// Handler with the simplest signature. +#[core_interrupt(Interrupt::SupervisorSoft)] +fn supervisor_soft() { + // do something here + loop {} +} + +/// Handler with the most complete signature. +#[core_interrupt(Interrupt::SupervisorTimer)] +unsafe fn supervisor_timer() -> ! { + // do something here + loop {} +} + +/* EXAMPLES OF USING THE external_interrupt MACRO FOR EXTERNAL INTERRUPT HANDLERS. */ + +/// Handler with the simplest signature. +#[external_interrupt(ExternalInterrupt::GPIO)] +fn external_gpio() { + // do something here + loop {} +} + +/// Handler with the most complete signature. +#[external_interrupt(ExternalInterrupt::UART)] +unsafe fn external_uart() -> ! { + // do something here + loop {} +} + +/* EXAMPLES OF USING THE exception MACRO FOR EXCEPTION HANDLERS. */ + +/// Handler with the simplest signature. +#[exception(Exception::InstructionMisaligned)] +fn instruction_misaligned() { + // do something here + loop {} +} + +/// Handler with the most complete signature. +#[exception(Exception::IllegalInstruction)] +unsafe fn illegal_instruction(_trap: &riscv_rt::TrapFrame) -> ! { + // do something here + loop {} +} + +// The reference to TrapFrame can be mutable if the handler needs to modify it. +#[exception(Exception::Breakpoint)] +unsafe fn breakpoint(_trap: &mut riscv_rt::TrapFrame) -> ! { // do something here loop {} } diff --git a/riscv-rt/examples/multi_core.rs b/riscv-rt/examples/multi_core.rs index a3cbab79..0ccfc97a 100644 --- a/riscv-rt/examples/multi_core.rs +++ b/riscv-rt/examples/multi_core.rs @@ -2,8 +2,6 @@ #![no_main] extern crate panic_halt; -extern crate riscv; -extern crate riscv_rt; use riscv::asm::wfi; use riscv::register::{mie, mip}; diff --git a/riscv-rt/macros/Cargo.toml b/riscv-rt/macros/Cargo.toml index b7dba6fb..c842c4d6 100644 --- a/riscv-rt/macros/Cargo.toml +++ b/riscv-rt/macros/Cargo.toml @@ -11,6 +11,7 @@ license = "MIT OR Apache-2.0" name = "riscv-rt-macros" repository = "https://github.com/rust-embedded/riscv" version = "0.2.1" +edition = "2021" [lib] proc-macro = true diff --git a/riscv-rt/macros/src/lib.rs b/riscv-rt/macros/src/lib.rs index df7d5a01..3cd75e1a 100644 --- a/riscv-rt/macros/src/lib.rs +++ b/riscv-rt/macros/src/lib.rs @@ -1,19 +1,19 @@ #![deny(warnings)] -extern crate proc_macro; -#[macro_use] -extern crate quote; extern crate core; +extern crate proc_macro; extern crate proc_macro2; -#[macro_use] +extern crate quote; extern crate syn; -use proc_macro2::Span; +use proc_macro2::{Span, TokenStream as TokenStream2}; +use quote::quote; use syn::{ parse::{self, Parse}, + parse_macro_input, parse_quote, punctuated::Punctuated, spanned::Spanned, - FnArg, ItemFn, LitInt, LitStr, PatType, ReturnType, Type, Visibility, + FnArg, ItemFn, LitInt, LitStr, PatType, Path, ReturnType, Token, Type, Visibility, }; use proc_macro::TokenStream; @@ -357,12 +357,35 @@ pub fn loop_global_asm(input: TokenStream) -> TokenStream { res.parse().unwrap() } -#[derive(Clone, Copy)] +#[derive(Clone, Copy, Debug)] enum RiscvArch { Rv32, Rv64, } +impl RiscvArch { + fn width(&self) -> usize { + match self { + Self::Rv32 => 4, + Self::Rv64 => 8, + } + } + + fn store(&self) -> &str { + match self { + Self::Rv32 => "sw", + Self::Rv64 => "sd", + } + } + + fn load(&self) -> &str { + match self { + Self::Rv32 => "lw", + Self::Rv64 => "ld", + } + } +} + /// Size of the trap frame (in number of registers) const TRAP_SIZE: usize = 16; @@ -396,11 +419,8 @@ const TRAP_FRAME: [&str; TRAP_SIZE] = [ /// frame storage in two parts: the first part saves space in the stack and stores only the `a0` register, /// while the second part stores the remaining registers. fn store_trap bool>(arch: RiscvArch, mut filter: T) -> String { - let (width, store) = match arch { - RiscvArch::Rv32 => (4, "sw"), - RiscvArch::Rv64 => (8, "sd"), - }; - + let width = arch.width(); + let store = arch.store(); TRAP_FRAME .iter() .enumerate() @@ -413,10 +433,8 @@ fn store_trap bool>(arch: RiscvArch, mut filter: T) -> String /// Generate the assembly instructions to load the trap frame. /// The `arch` parameter is used to determine the width of the registers. fn load_trap(arch: RiscvArch) -> String { - let (width, load) = match arch { - RiscvArch::Rv32 => (4, "lw"), - RiscvArch::Rv64 => (8, "ld"), - }; + let width = arch.width(); + let load = arch.load(); TRAP_FRAME .iter() .enumerate() @@ -451,10 +469,7 @@ pub fn weak_start_trap_riscv64(_input: TokenStream) -> TokenStream { /// The `arch` parameter is used to determine the width of the registers. /// The macro also ensures that the trap frame size is 16-byte aligned. fn weak_start_trap(arch: RiscvArch) -> TokenStream { - let width = match arch { - RiscvArch::Rv32 => 4, - RiscvArch::Rv64 => 8, - }; + let width = arch.width(); // ensure we do not break that sp is 16-byte aligned if (TRAP_SIZE * width) % 16 != 0 { return parse::Error::new(Span::call_site(), "Trap frame size must be 16-byte aligned") @@ -509,10 +524,7 @@ pub fn vectored_interrupt_trap_riscv64(_input: TokenStream) -> TokenStream { /// jumps to the interrupt handler. The '_continue_interrupt_trap' function stores the trap frame /// partially (all registers except a0), jumps to the interrupt handler, and restores the trap frame. fn vectored_interrupt_trap(arch: RiscvArch) -> TokenStream { - let width = match arch { - RiscvArch::Rv32 => 4, - RiscvArch::Rv64 => 8, - }; + let width = arch.width(); let store_start = store_trap(arch, |reg| reg == "a0"); let store_continue = store_trap(arch, |reg| reg != "a0"); let load = load_trap(arch); @@ -548,91 +560,230 @@ _continue_interrupt_trap: instructions.parse().unwrap() } +#[derive(Clone, Copy, Debug)] +enum RiscvPacItem { + Exception, + ExternalInterrupt, + CoreInterrupt, +} + +impl RiscvPacItem { + fn macro_id(&self) -> &str { + match self { + Self::Exception => "exception", + Self::ExternalInterrupt => "external_interrupt", + Self::CoreInterrupt => "core_interrupt", + } + } + + fn valid_signature(&self) -> &str { + match self { + Self::Exception => "`[unsafe] fn([&[mut] riscv_rt::TrapFrame]) [-> !]`", + _ => "`[unsafe] fn() [-> !]`", + } + } + + fn check_signature(&self, f: &ItemFn) -> bool { + let valid_args = match self { + Self::Exception => { + if f.sig.inputs.len() > 1 { + return false; + } + match f.sig.inputs.first() { + Some(FnArg::Typed(t)) => { + let first_param_type = *t.ty.clone(); + let expected_types: Vec = vec![ + parse_quote!(&riscv_rt::TrapFrame), + parse_quote!(&mut riscv_rt::TrapFrame), + ]; + expected_types.iter().any(|t| first_param_type == *t) + } + Some(_) => false, + None => true, + } + } + _ => f.sig.inputs.is_empty(), + }; + + valid_args + && f.sig.constness.is_none() + && f.sig.asyncness.is_none() + && f.vis == Visibility::Inherited + && f.sig.abi.is_none() + && f.sig.generics.params.is_empty() + && f.sig.generics.where_clause.is_none() + && f.sig.variadic.is_none() + && match f.sig.output { + ReturnType::Default => true, + ReturnType::Type(_, ref ty) => matches!(**ty, Type::Never(_)), + } + } + + fn impl_trait(&self) -> TokenStream2 { + match self { + Self::Exception => quote! { riscv_rt::ExceptionNumber }, + Self::ExternalInterrupt => quote! { riscv_rt::ExternalInterruptNumber }, + Self::CoreInterrupt => quote! { riscv_rt::CoreInterruptNumber }, + } + } +} + #[proc_macro_attribute] -/// Attribute to declare an interrupt handler. +/// Attribute to declare an exception handler. /// -/// The function must have the signature `[unsafe] fn() [-> !]`. -/// If the `v-trap` feature is enabled, this macro generates the -/// interrupt trap handler in assembly for RISCV-32 targets. -pub fn interrupt_riscv32(args: TokenStream, input: TokenStream) -> TokenStream { - interrupt(args, input, RiscvArch::Rv32) +/// The function must have the signature `[unsafe] fn([&[mut] riscv_rt::TrapFrame]) [-> !]`. +/// +/// The argument of the macro must be a path to a variant of an enum that implements the `riscv_rt::ExceptionNumber` trait. +/// +/// # Example +/// +/// ``` ignore,no_run +/// #[riscv_rt::exception(riscv::interrupt::Exception::LoadMisaligned)] +/// fn load_misaligned(trap_frame: &mut riscv_rt::TrapFrame) -> ! { +/// loop{}; +/// } +/// ``` +pub fn exception(args: TokenStream, input: TokenStream) -> TokenStream { + trap(args, input, RiscvPacItem::Exception, None) } #[proc_macro_attribute] -/// Attribute to declare an interrupt handler. +/// Attribute to declare a core interrupt handler. /// /// The function must have the signature `[unsafe] fn() [-> !]`. -/// If the `v-trap` feature is enabled, this macro generates the -/// interrupt trap handler in assembly for RISCV-64 targets. -pub fn interrupt_riscv64(args: TokenStream, input: TokenStream) -> TokenStream { - interrupt(args, input, RiscvArch::Rv64) +/// +/// The argument of the macro must be a path to a variant of an enum that implements the `riscv_rt::CoreInterruptNumber` trait. +/// +/// If the `v-trap` feature is enabled, this macro generates the corresponding interrupt trap handler in assembly. +/// +/// # Example +/// +/// ``` ignore,no_run +/// #[riscv_rt::core_interrupt(riscv::interrupt::Interrupt::SupervisorSoft)] +/// fn supervisor_soft() -> ! { +/// loop{}; +/// } +/// ``` +pub fn core_interrupt_riscv32(args: TokenStream, input: TokenStream) -> TokenStream { + let arch = match () { + #[cfg(feature = "v-trap")] + () => Some(RiscvArch::Rv32), + #[cfg(not(feature = "v-trap"))] + () => None, + }; + trap(args, input, RiscvPacItem::CoreInterrupt, arch) } -fn interrupt(args: TokenStream, input: TokenStream, _arch: RiscvArch) -> TokenStream { - let f = parse_macro_input!(input as ItemFn); +#[proc_macro_attribute] +/// Attribute to declare a core interrupt handler. +/// +/// The function must have the signature `[unsafe] fn() [-> !]`. +/// +/// The argument of the macro must be a path to a variant of an enum that implements the `riscv_rt::CoreInterruptNumber` trait. +/// +/// If the `v-trap` feature is enabled, this macro generates the corresponding interrupt trap handler in assembly. +/// +/// # Example +/// +/// ``` ignore,no_run +/// #[riscv_rt::core_interrupt(riscv::interrupt::Interrupt::SupervisorSoft)] +/// fn supervisor_soft() -> ! { +/// loop{}; +/// } +/// ``` +pub fn core_interrupt_riscv64(args: TokenStream, input: TokenStream) -> TokenStream { + let arch = match () { + #[cfg(feature = "v-trap")] + () => Some(RiscvArch::Rv64), + #[cfg(not(feature = "v-trap"))] + () => None, + }; + trap(args, input, RiscvPacItem::CoreInterrupt, arch) +} - // check the function arguments - if !f.sig.inputs.is_empty() { - return parse::Error::new( - f.sig.inputs.first().unwrap().span(), - "`#[interrupt]` function should not have arguments", - ) - .to_compile_error() - .into(); - } +#[proc_macro_attribute] +/// Attribute to declare an external interrupt handler. +/// +/// The function must have the signature `[unsafe] fn() [-> !]`. +/// +/// The argument of the macro must be a path to a variant of an enum that implements the `riscv_rt::ExternalInterruptNumber` trait. +/// +/// # Example +/// +/// ``` ignore,no_run +/// #[riscv_rt::external_interrupt(e310x::interrupt::Interrupt::GPIO0)] +/// fn gpio0() -> ! { +/// loop{}; +/// } +/// ``` +pub fn external_interrupt(args: TokenStream, input: TokenStream) -> TokenStream { + trap(args, input, RiscvPacItem::ExternalInterrupt, None) +} - // check the function signature - let valid_signature = f.sig.constness.is_none() - && f.sig.asyncness.is_none() - && f.vis == Visibility::Inherited - && f.sig.abi.is_none() - && f.sig.generics.params.is_empty() - && f.sig.generics.where_clause.is_none() - && f.sig.variadic.is_none() - && match f.sig.output { - ReturnType::Default => true, - ReturnType::Type(_, ref ty) => matches!(**ty, Type::Never(_)), - }; +fn trap( + args: TokenStream, + input: TokenStream, + pac_item: RiscvPacItem, + arch: Option, +) -> TokenStream { + let f = parse_macro_input!(input as ItemFn); - if !valid_signature { - return parse::Error::new( - f.span(), - "`#[interrupt]` function must have signature `[unsafe] fn() [-> !]`", - ) - .to_compile_error() - .into(); + if !pac_item.check_signature(&f) { + let msg = format!( + "`#[{}]` function must have signature {}", + pac_item.macro_id(), + pac_item.valid_signature() + ); + return parse::Error::new(f.sig.span(), msg) + .to_compile_error() + .into(); } - - if !args.is_empty() { - return parse::Error::new(Span::call_site(), "This attribute accepts no arguments") + if args.is_empty() { + let msg = format!( + "`#[{}]` attribute expects a path to a variant of an enum that implements the {} trait.", + pac_item.macro_id(), + pac_item.impl_trait() + ); + return parse::Error::new(Span::call_site(), msg) .to_compile_error() .into(); } - // XXX should we blacklist other attributes? - let ident = &f.sig.ident; - let export_name = format!("{:#}", ident); + let int_path = parse_macro_input!(args as Path); + let int_ident = &int_path.segments.last().unwrap().ident; + let export_name = format!("{:#}", int_ident); + + let start_trap = match arch { + Some(arch) => { + let trap = start_interrupt_trap(int_ident, arch); + quote! { + #[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))] + #trap + } + } + None => proc_macro2::TokenStream::new(), + }; - #[cfg(not(feature = "v-trap"))] - let start_trap = proc_macro2::TokenStream::new(); - #[cfg(feature = "v-trap")] - let start_trap = start_interrupt_trap(ident, _arch); + let pac_trait = pac_item.impl_trait(); quote!( + // Compile-time check to ensure the trap path implements the trap trait + const _: fn() = || { + fn assert_impl(_arg: T) {} + assert_impl(#int_path); + }; + #start_trap + #[export_name = #export_name] #f ) .into() } -#[cfg(feature = "v-trap")] fn start_interrupt_trap(ident: &syn::Ident, arch: RiscvArch) -> proc_macro2::TokenStream { let interrupt = ident.to_string(); - let width = match arch { - RiscvArch::Rv32 => 4, - RiscvArch::Rv64 => 8, - }; + let width = arch.width(); let store = store_trap(arch, |r| r == "a0"); let instructions = format!( diff --git a/riscv-rt/src/asm.rs b/riscv-rt/src/asm.rs index 7883c446..bc177e7e 100644 --- a/riscv-rt/src/asm.rs +++ b/riscv-rt/src/asm.rs @@ -287,38 +287,6 @@ riscv_rt_macros::vectored_interrupt_trap_riscv32!(); #[cfg(all(riscv64, feature = "v-trap"))] riscv_rt_macros::vectored_interrupt_trap_riscv64!(); -#[cfg(feature = "v-trap")] -cfg_global_asm!( - // Set the vector mode to vectored. - r#".section .trap, "ax" - .weak _vector_table - .type _vector_table, @function - - .option push - .balign 0x4 // TODO check if this is the correct alignment - .option norelax - .option norvc - - _vector_table: - j _start_trap // Interrupt 0 is used for exceptions - j _start_SupervisorSoft_trap - j _start_DefaultHandler_trap // Interrupt 2 is reserved - j _start_MachineSoft_trap - j _start_DefaultHandler_trap // Interrupt 4 is reserved - j _start_SupervisorTimer_trap - j _start_DefaultHandler_trap // Interrupt 6 is reserved - j _start_MachineTimer_trap - j _start_DefaultHandler_trap // Interrupt 8 is reserved - j _start_SupervisorExternal_trap - j _start_DefaultHandler_trap // Interrupt 10 is reserved - j _start_MachineExternal_trap - - // default table does not include the remaining interrupts. - // Targets with extra interrupts should override this table. - - .option pop"#, -); - #[rustfmt::skip] global_asm!( ".section .text.abort diff --git a/riscv-rt/src/exceptions.rs b/riscv-rt/src/exceptions.rs new file mode 100644 index 00000000..2fd80dcb --- /dev/null +++ b/riscv-rt/src/exceptions.rs @@ -0,0 +1,70 @@ +//! Exception handling for targets that comply with the RISC-V exception handling standard. +//! +//! Exception dispatching is performed by the [`_dispatch_exception`] function. +//! This function is called by the [crate::start_trap_rust] whenever an exception is triggered. +//! This approach relies on the [`__EXCEPTIONS`] array, which sorts all the exception handlers +//! depending on their corresponding exception source code. +//! +//! # Note +//! +//! If your target has custom exception sources, the target PAC might provide equivalent +//! code to adapt for the target needs. In this case, you may need to opt out this module. +//! To do so, activate the `no-exceptions` feature of the `riscv-rt` crate. + +use crate::TrapFrame; + +extern "C" { + fn InstructionMisaligned(trap_frame: &TrapFrame); + fn InstructionFault(trap_frame: &TrapFrame); + fn IllegalInstruction(trap_frame: &TrapFrame); + fn Breakpoint(trap_frame: &TrapFrame); + fn LoadMisaligned(trap_frame: &TrapFrame); + fn LoadFault(trap_frame: &TrapFrame); + fn StoreMisaligned(trap_frame: &TrapFrame); + fn StoreFault(trap_frame: &TrapFrame); + fn UserEnvCall(trap_frame: &TrapFrame); + fn SupervisorEnvCall(trap_frame: &TrapFrame); + fn MachineEnvCall(trap_frame: &TrapFrame); + fn InstructionPageFault(trap_frame: &TrapFrame); + fn LoadPageFault(trap_frame: &TrapFrame); + fn StorePageFault(trap_frame: &TrapFrame); +} + +/// Array with all the exception handlers sorted according to their exception source code. +#[no_mangle] +pub static __EXCEPTIONS: [Option; 16] = [ + Some(InstructionMisaligned), + Some(InstructionFault), + Some(IllegalInstruction), + Some(Breakpoint), + Some(LoadMisaligned), + Some(LoadFault), + Some(StoreMisaligned), + Some(StoreFault), + Some(UserEnvCall), + Some(SupervisorEnvCall), + None, + Some(MachineEnvCall), + Some(InstructionPageFault), + Some(LoadPageFault), + None, + Some(StorePageFault), +]; + +/// It calls the corresponding exception handler depending on the exception source code. +/// +/// # Safety +/// +/// This function must be called only from the [`crate::start_trap_rust`] function. +/// Do **NOT** call this function directly. +#[inline] +#[no_mangle] +pub unsafe extern "C" fn _dispatch_exception(trap_frame: &TrapFrame, code: usize) { + extern "C" { + fn ExceptionHandler(trap_frame: &TrapFrame); + } + match __EXCEPTIONS.get(code) { + Some(Some(handler)) => handler(trap_frame), + _ => ExceptionHandler(trap_frame), + } +} diff --git a/riscv-rt/src/interrupts.rs b/riscv-rt/src/interrupts.rs new file mode 100644 index 00000000..6fe900e5 --- /dev/null +++ b/riscv-rt/src/interrupts.rs @@ -0,0 +1,100 @@ +//! Interrupt handling for targets that comply with the RISC-V interrupt handling standard. +//! +//! In direct mode (i.e., `v-trap` feature disabled), interrupt dispatching is performed by the +//! [`_dispatch_core_interrupt`] function. This function is called by the [crate::start_trap_rust] +//! whenever an interrupt is triggered. This approach relies on the [`__CORE_INTERRUPTS`] array, +//! which sorts all the interrupt handlers depending on their corresponding interrupt source code. +//! +//! In vectored mode (i.e., `v-trap` feature enabled), interrupt dispatching is handled by hardware. +//! To support this mode, we provide inline assembly code that defines the interrupt vector table. +//! +//! # Note +//! +//! If your target has custom core interrupt sources, the target PAC might provide equivalent +//! code to adapt for the target needs. In this case, you may need to opt out this module. +//! To do so, activate the `no-interrupts` feature of the `riscv-rt` crate. + +#[cfg(not(feature = "v-trap"))] +extern "C" { + fn SupervisorSoft(); + fn MachineSoft(); + fn SupervisorTimer(); + fn MachineTimer(); + fn SupervisorExternal(); + fn MachineExternal(); +} + +/// Array with all the core interrupt handlers sorted according to their interrupt source code. +/// +/// # Note +/// +/// This array is necessary only in direct mode (i.e., `v-trap` feature disabled). +#[cfg(not(feature = "v-trap"))] +#[no_mangle] +pub static __CORE_INTERRUPTS: [Option; 12] = [ + None, + Some(SupervisorSoft), + None, + Some(MachineSoft), + None, + Some(SupervisorTimer), + None, + Some(MachineTimer), + None, + Some(SupervisorExternal), + None, + Some(MachineExternal), +]; + +/// It calls the corresponding interrupt handler depending on the interrupt source code. +/// +/// # Note +/// +/// This function is only required in direct mode (i.e., `v-trap` feature disabled). +/// In vectored mode, interrupt handler dispatching is performed directly by hardware. +/// +/// # Safety +/// +/// This function must be called only from the [`crate::start_trap_rust`] function. +/// Do **NOT** call this function directly. +#[cfg(not(feature = "v-trap"))] +#[inline] +#[no_mangle] +pub unsafe extern "C" fn _dispatch_core_interrupt(code: usize) { + extern "C" { + fn DefaultHandler(); + } + match __CORE_INTERRUPTS.get(code) { + Some(Some(handler)) => handler(), + _ => DefaultHandler(), + } +} + +// In vectored mode, we also must provide a vector table +#[cfg(all(riscv, feature = "v-trap"))] +core::arch::global_asm!( + r#" .section .trap, "ax" + .weak _vector_table + .type _vector_table, @function + + .option push + .balign 0x4 // TODO check if this is the correct alignment + .option norelax + .option norvc + + _vector_table: + j _start_trap // Interrupt 0 is used for exceptions + j _start_SupervisorSoft_trap + j _start_DefaultHandler_trap // Interrupt 2 is reserved + j _start_MachineSoft_trap + j _start_DefaultHandler_trap // Interrupt 4 is reserved + j _start_SupervisorTimer_trap + j _start_DefaultHandler_trap // Interrupt 6 is reserved + j _start_MachineTimer_trap + j _start_DefaultHandler_trap // Interrupt 8 is reserved + j _start_SupervisorExternal_trap + j _start_DefaultHandler_trap // Interrupt 10 is reserved + j _start_MachineExternal_trap + + .option pop"# +); diff --git a/riscv-rt/src/lib.rs b/riscv-rt/src/lib.rs index c4572dc4..9705ad28 100644 --- a/riscv-rt/src/lib.rs +++ b/riscv-rt/src/lib.rs @@ -270,34 +270,24 @@ //! ### Core exception handlers //! //! This functions are called when corresponding exception occurs. -//! You can define an exception handler with one of the following names: -//! * `InstructionMisaligned` -//! * `InstructionFault` -//! * `IllegalInstruction` -//! * `Breakpoint` -//! * `LoadMisaligned` -//! * `LoadFault` -//! * `StoreMisaligned` -//! * `StoreFault` -//! * `UserEnvCall` -//! * `SupervisorEnvCall` -//! * `MachineEnvCall` -//! * `InstructionPageFault` -//! * `LoadPageFault` -//! * `StorePageFault` +//! You can define an exception handler with the [`exception`] attribute. +//! The attribute expects the path to the exception source as an argument. +//! +//! The [`exception`] attribute ensures at compile time that there is a valid +//! exception source for the given handler. //! //! For example: //! ``` no_run -//! #[export_name = "MachineEnvCall"] -//! fn custom_menv_call_handler(trap_frame: &riscv_rt::TrapFrame) { -//! // ... +//! use riscv::interrupt::Exception; // or a target-specific exception enum +//! +//! #[riscv_rt::exception(Exception::MachineEnvCall)] +//! fn custom_menv_call_handler(trap_frame: &mut riscv_rt::TrapFrame) { +//! todo!() //! } -//! ``` -//! or -//! ``` no_run -//! #[no_mangle] -//! fn MachineEnvCall(trap_frame: &riscv_rt::TrapFrame) -> ! { -//! // ... +//! +//! #[riscv_rt::exception(Exception::LoadFault)] +//! fn custom_load_fault_handler() -> ! { +//! loop {} //! } //! ``` //! @@ -320,51 +310,50 @@ //! or //! ``` no_run //! #[no_mangle] -//! fn ExceptionHandler(trap_frame: &riscv_rt::TrapFrame) -> ! { +//! fn ExceptionHandler(trap_frame: &mut riscv_rt::TrapFrame) { //! // ... //! } //! ``` //! //! Default implementation of this function stucks in a busy-loop. //! -//! //! ### Core interrupt handlers //! //! This functions are called when corresponding interrupt is occured. -//! You can define an interrupt handler with one of the following names: -//! * `SupervisorSoft` -//! * `MachineSoft` -//! * `SupervisorTimer` -//! * `MachineTimer` -//! * `SupervisorExternal` -//! * `MachineExternal` +//! You can define a core interrupt handler with the [`core_interrupt`] attribute. +//! The attribute expects the path to the interrupt source as an argument. +//! +//! The [`core_interrupt`] attribute ensures at compile time that there is a valid +//! core interrupt source for the given handler. //! //! For example: //! ``` no_run -//! #[export_name = "MachineTimer"] -//! fn custom_timer_handler() { -//! // ... +//! use riscv::interrupt::Interrupt; // or a target-specific core interrupt enum +//! +//! #[riscv_rt::core_interrupt(Interrupt::MachineSoft)] +//! unsafe fn custom_machine_soft_handler() { +//! todo!() //! } -//! ``` -//! or -//! ``` no_run -//! #[no_mangle] -//! fn MachineTimer() { -//! // ... +//! +//! #[riscv_rt::core_interrupt(Interrupt::MachineTimer)] +//! fn custom_machine_timer_handler() -> ! { +//! loop {} //! } //! ``` //! -//! You can also use the `#[interrupt]` macro to define interrupt handlers: +//! In vectored mode, this macro will also generate a proper trap handler for the interrupt. //! -//! ``` no_run -//! #[riscv_rt::interrupt] -//! fn MachineTimer() { -//! // ... -//! } -//! ``` +//! If interrupt handler is not explicitly defined, `DefaultHandler` is called. +//! +//! ### External interrupt handlers +//! +//! This functions are called when corresponding interrupt is occured. +//! You can define an external interrupt handler with the [`external_interrupt`] attribute. +//! The attribute expects the path to the interrupt source as an argument. //! -//! In direct mode, this macro is equivalent to defining a function with the same name. -//! However, in vectored mode, this macro will generate a proper trap handler for the interrupt. +//! The [`external_interrupt`] attribute ensures at compile time that there is a valid +//! external interrupt source for the given handler. +//! Note that external interrupts are target-specific and may not be available on all platforms. //! //! If interrupt handler is not explicitly defined, `DefaultHandler` is called. //! @@ -372,20 +361,22 @@ //! //! This function is called when interrupt without defined interrupt handler is occured. //! The interrupt reason can be decoded from the `mcause`/`scause` register. +//! If it is an external interrupt, the interrupt reason can be decoded from a +//! target-specific peripheral interrupt controller. //! //! This function can be redefined in the following way: //! //! ``` no_run //! #[export_name = "DefaultHandler"] -//! fn custom_interrupt_handler() { +//! unsafe fn custom_interrupt_handler() { //! // ... //! } //! ``` //! or //! ``` no_run //! #[no_mangle] -//! fn DefaultHandler() { -//! // ... +//! fn DefaultHandler() -> ! { +//! loop {} //! } //! ``` //! @@ -436,22 +427,7 @@ //! riscv-rt = {features=["v-trap"]} //! ``` //! When the vectored trap feature is enabled, the trap vector is set to `_vector_table` in vectored mode. -//! This table is a list of `j _start_INTERRUPT_trap` instructions, where `INTERRUPT` is the name of the interrupt. -//! -//! ### Defining interrupt handlers in vectored mode -//! -//! In vectored mode, each interrupt must also have a corresponding trap handler. -//! Therefore, using `export_name` or `no_mangle` is not enough to define an interrupt handler. -//! The [`interrupt`] macro will generate the trap handler for the interrupt: -//! -//! ``` no_run -//! #[riscv_rt::interrupt] -//! fn MachineTimer() { -//! // ... -//! } -//! ``` -//! -//! This will generate a function named `_start_MachineTimer_trap` that calls the interrupt handler `MachineTimer`. +//! This table is a list of `j _start_INTERRUPT_trap` instructions, where `INTERRUPT` is the name of the core interrupt. //! //! ## `u-boot` //! @@ -473,19 +449,28 @@ #[cfg(riscv)] mod asm; +#[cfg(not(feature = "no-exceptions"))] +pub mod exceptions; + +#[cfg(not(feature = "no-interrupts"))] +pub mod interrupts; + #[cfg(feature = "s-mode")] use riscv::register::scause as xcause; #[cfg(not(feature = "s-mode"))] use riscv::register::mcause as xcause; -pub use riscv_rt_macros::{entry, pre_init}; +pub use riscv_rt_macros::{entry, exception, external_interrupt, pre_init}; -#[cfg(riscv32)] -pub use riscv_rt_macros::interrupt_riscv32 as interrupt; +pub use riscv_pac::*; +#[cfg(riscv32)] +pub use riscv_rt_macros::core_interrupt_riscv32 as core_interrupt; #[cfg(riscv64)] -pub use riscv_rt_macros::interrupt_riscv64 as interrupt; +pub use riscv_rt_macros::core_interrupt_riscv64 as core_interrupt; +#[cfg(not(riscv))] +pub use riscv_rt_macros::core_interrupt_riscv64 as core_interrupt; // just for docs, tests, etc. /// We export this static with an informative name so that if an application attempts to link /// two copies of riscv-rt together, linking will fail. We also declare a links key in @@ -521,119 +506,49 @@ pub struct TrapFrame { /// Trap entry point rust (_start_trap_rust) /// /// `scause`/`mcause` is read to determine the cause of the trap. XLEN-1 bit indicates -/// if it's an interrupt or an exception. The result is examined and ExceptionHandler -/// or one of the core interrupt handlers is called. +/// if it's an interrupt or an exception. The result is examined and one of the +/// exception handlers or one of the core interrupt handlers is called. +/// +/// # Note +/// +/// Exception dispatching is performed by an extern `_dispatch_exception` function. +/// Targets that comply with the RISC-V standard can use the implementation provided +/// by this crate in the [`exceptions`] module. Targets with special exception sources +/// may provide their custom implementation of the `_dispatch_exception` function. You may +/// also need to enable the `no-exceptions` feature to op-out the default implementation. +/// +/// In direct mode (i.e., `v-trap` feature disabled), interrupt dispatching is performed +/// by an extern `_dispatch_core_interrupt` function. Targets that comply with the RISC-V +/// standard can use the implementation provided by this crate in the [`interrupts`] module. +/// Targets with special interrupt sources may provide their custom implementation of the +/// `_dispatch_core_interrupt` function. You may also need to enable the `no-interrupts` +/// feature to op-out the default implementation. +/// +/// In vectored mode (i.e., `v-trap` feature enabled), interrupt dispatching is performed +/// directly by hardware, and thus this function should **not** be triggered due to an +/// interrupt. If this abnormal situation happens, this function will directly call the +/// `DefaultHandler` function. /// /// # Safety /// /// This function must be called only from assembly `_start_trap` function. /// Do **NOT** call this function directly. -#[link_section = ".trap.rust"] +#[cfg_attr(riscv, link_section = ".trap.rust")] #[export_name = "_start_trap_rust"] pub unsafe extern "C" fn start_trap_rust(trap_frame: *const TrapFrame) { extern "C" { - fn ExceptionHandler(trap_frame: &TrapFrame); - fn _dispatch_interrupt(code: usize); - } - - let cause = xcause::read(); - let code = cause.code(); - - if cause.is_exception() { - let trap_frame = &*trap_frame; - if code < __EXCEPTIONS.len() { - let h = &__EXCEPTIONS[code]; - if let Some(handler) = h { - handler(trap_frame); - } else { - ExceptionHandler(trap_frame); - } - } else { - ExceptionHandler(trap_frame); - } - } else { - _dispatch_interrupt(code); - } -} - -extern "C" { - fn InstructionMisaligned(trap_frame: &TrapFrame); - fn InstructionFault(trap_frame: &TrapFrame); - fn IllegalInstruction(trap_frame: &TrapFrame); - fn Breakpoint(trap_frame: &TrapFrame); - fn LoadMisaligned(trap_frame: &TrapFrame); - fn LoadFault(trap_frame: &TrapFrame); - fn StoreMisaligned(trap_frame: &TrapFrame); - fn StoreFault(trap_frame: &TrapFrame); - fn UserEnvCall(trap_frame: &TrapFrame); - fn SupervisorEnvCall(trap_frame: &TrapFrame); - fn MachineEnvCall(trap_frame: &TrapFrame); - fn InstructionPageFault(trap_frame: &TrapFrame); - fn LoadPageFault(trap_frame: &TrapFrame); - fn StorePageFault(trap_frame: &TrapFrame); -} - -#[doc(hidden)] -#[no_mangle] -pub static __EXCEPTIONS: [Option; 16] = [ - Some(InstructionMisaligned), - Some(InstructionFault), - Some(IllegalInstruction), - Some(Breakpoint), - Some(LoadMisaligned), - Some(LoadFault), - Some(StoreMisaligned), - Some(StoreFault), - Some(UserEnvCall), - Some(SupervisorEnvCall), - None, - Some(MachineEnvCall), - Some(InstructionPageFault), - Some(LoadPageFault), - None, - Some(StorePageFault), -]; - -#[export_name = "_dispatch_interrupt"] -unsafe extern "C" fn dispatch_interrupt(code: usize) { - extern "C" { + #[cfg(not(feature = "v-trap"))] + fn _dispatch_core_interrupt(code: usize); + #[cfg(feature = "v-trap")] fn DefaultHandler(); + fn _dispatch_exception(trap_frame: &TrapFrame, code: usize); } - if code < __INTERRUPTS.len() { - let h = &__INTERRUPTS[code]; - if let Some(handler) = h { - handler(); - } else { - DefaultHandler(); - } - } else { - DefaultHandler(); + match xcause::read().cause() { + #[cfg(not(feature = "v-trap"))] + xcause::Trap::Interrupt(code) => _dispatch_core_interrupt(code), + #[cfg(feature = "v-trap")] + xcause::Trap::Interrupt(_) => DefaultHandler(), + xcause::Trap::Exception(code) => _dispatch_exception(&*trap_frame, code), } } - -extern "C" { - fn SupervisorSoft(); - fn MachineSoft(); - fn SupervisorTimer(); - fn MachineTimer(); - fn SupervisorExternal(); - fn MachineExternal(); -} - -#[doc(hidden)] -#[no_mangle] -pub static __INTERRUPTS: [Option; 12] = [ - None, - Some(SupervisorSoft), - None, - Some(MachineSoft), - None, - Some(SupervisorTimer), - None, - Some(MachineTimer), - None, - Some(SupervisorExternal), - None, - Some(MachineExternal), -]; diff --git a/riscv-semihosting/CHANGELOG.md b/riscv-semihosting/CHANGELOG.md index a4ef0e74..80e008a3 100644 --- a/riscv-semihosting/CHANGELOG.md +++ b/riscv-semihosting/CHANGELOG.md @@ -7,6 +7,8 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Changed +- Apply clippy changes +- Bump riscv dependency version - Made `cfg` variable selection more robust for custom targets - Fixed debug::exit() on riscv64 QEMU simulation - Fixed an ambiguous link in the generated crate documentation. diff --git a/riscv-semihosting/Cargo.toml b/riscv-semihosting/Cargo.toml index 8b715436..4163186e 100644 --- a/riscv-semihosting/Cargo.toml +++ b/riscv-semihosting/Cargo.toml @@ -24,4 +24,4 @@ default = ["jlink-quirks"] [dependencies] critical-section = "1.0.0" -riscv = { path = "../riscv", version = "0.11.0" } +riscv = { path = "../riscv", version = "0.12.0" } diff --git a/riscv-semihosting/README.md b/riscv-semihosting/README.md index 0768aeba..56e89eb6 100644 --- a/riscv-semihosting/README.md +++ b/riscv-semihosting/README.md @@ -17,7 +17,7 @@ Given this, the generally sufficient for using this library. A major difference between this library and `cortex-m-semihosting` is that there -are mandatory features to choose the privilege level at which the semihosting +are features to choose the privilege level at which the semihosting calls are executed. The *machine-mode (M-mode)* feature will cause the macros in `export` to execute the semihosting operation in an interrupt-free context, while *user-mode (U-mode)* causes them to just execute the operation. diff --git a/riscv-semihosting/src/export.rs b/riscv-semihosting/src/export.rs index c56d6d48..3aefa178 100644 --- a/riscv-semihosting/src/export.rs +++ b/riscv-semihosting/src/export.rs @@ -1,7 +1,9 @@ //! IMPLEMENTATION DETAILS USED BY MACROS - use crate::hio::{self, HostStream}; -use core::fmt::{self, Write}; +use core::{ + fmt::{self, Write}, + ptr::addr_of_mut, +}; static mut HSTDOUT: Option = None; static mut HSTDERR: Option = None; @@ -11,42 +13,46 @@ mod machine { use super::*; pub fn hstdout_str(s: &str) { - let _result = critical_section::with(|_| unsafe { - if HSTDOUT.is_none() { - HSTDOUT = Some(hio::hstdout()?); + let _result = critical_section::with(|_| { + let hstdout = unsafe { &mut *addr_of_mut!(HSTDOUT) }; + if hstdout.is_none() { + *hstdout = Some(hio::hstdout()?); } - HSTDOUT.as_mut().unwrap().write_str(s).map_err(drop) + hstdout.as_mut().unwrap().write_str(s).map_err(drop) }); } pub fn hstdout_fmt(args: fmt::Arguments) { - let _result = critical_section::with(|_| unsafe { - if HSTDOUT.is_none() { - HSTDOUT = Some(hio::hstdout()?); + let _result = critical_section::with(|_| { + let hstdout = unsafe { &mut *addr_of_mut!(HSTDOUT) }; + if hstdout.is_none() { + *hstdout = Some(hio::hstdout()?); } - HSTDOUT.as_mut().unwrap().write_fmt(args).map_err(drop) + hstdout.as_mut().unwrap().write_fmt(args).map_err(drop) }); } pub fn hstderr_str(s: &str) { - let _result = critical_section::with(|_| unsafe { - if HSTDERR.is_none() { - HSTDERR = Some(hio::hstderr()?); + let _result = critical_section::with(|_| { + let hstderr = unsafe { &mut *addr_of_mut!(HSTDERR) }; + if hstderr.is_none() { + *hstderr = Some(hio::hstderr()?); } - HSTDERR.as_mut().unwrap().write_str(s).map_err(drop) + hstderr.as_mut().unwrap().write_str(s).map_err(drop) }); } pub fn hstderr_fmt(args: fmt::Arguments) { - let _result = critical_section::with(|_| unsafe { - if HSTDERR.is_none() { - HSTDERR = Some(hio::hstderr()?); + let _result = critical_section::with(|_| { + let hstderr = unsafe { &mut *addr_of_mut!(HSTDERR) }; + if hstderr.is_none() { + *hstderr = Some(hio::hstderr()?); } - HSTDERR.as_mut().unwrap().write_fmt(args).map_err(drop) + hstderr.as_mut().unwrap().write_fmt(args).map_err(drop) }); } } @@ -59,41 +65,45 @@ mod user { pub fn hstdout_str(s: &str) { let _result = unsafe { - if HSTDOUT.is_none() { - HSTDOUT = Some(hio::hstdout().unwrap()); + let hstdout = &mut *addr_of_mut!(HSTDOUT); + if hstdout.is_none() { + *hstdout = Some(hio::hstdout().unwrap()); } - HSTDOUT.as_mut().unwrap().write_str(s).map_err(drop) + hstdout.as_mut().unwrap().write_str(s).map_err(drop) }; } pub fn hstdout_fmt(args: fmt::Arguments) { let _result = unsafe { - if HSTDOUT.is_none() { - HSTDOUT = Some(hio::hstdout().unwrap()); + let hstdout = &mut *addr_of_mut!(HSTDOUT); + if hstdout.is_none() { + *hstdout = Some(hio::hstdout().unwrap()); } - HSTDOUT.as_mut().unwrap().write_fmt(args).map_err(drop) + hstdout.as_mut().unwrap().write_fmt(args).map_err(drop) }; } pub fn hstderr_str(s: &str) { let _result = unsafe { - if HSTDERR.is_none() { - HSTDERR = Some(hio::hstderr().unwrap()); + let hstderr = &mut *addr_of_mut!(HSTDERR); + if hstderr.is_none() { + *hstderr = Some(hio::hstderr().unwrap()); } - HSTDERR.as_mut().unwrap().write_str(s).map_err(drop) + hstderr.as_mut().unwrap().write_str(s).map_err(drop) }; } pub fn hstderr_fmt(args: fmt::Arguments) { let _result = unsafe { - if HSTDERR.is_none() { - HSTDERR = Some(hio::hstderr().unwrap()); + let hstderr = &mut *addr_of_mut!(HSTDERR); + if hstderr.is_none() { + *hstderr = Some(hio::hstderr().unwrap()); } - HSTDERR.as_mut().unwrap().write_fmt(args).map_err(drop) + hstderr.as_mut().unwrap().write_fmt(args).map_err(drop) }; } } diff --git a/riscv/CHANGELOG.md b/riscv/CHANGELOG.md index 7b2aa2fb..98d1b7b4 100644 --- a/riscv/CHANGELOG.md +++ b/riscv/CHANGELOG.md @@ -9,6 +9,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Added +- `riscv-macros` crate for `riscv-pac` enums. +- Bump MSRV to 1.61. +- Implementation of `riscv-pac` traits for `Interrupt` and `Exception` enums. +- Tests for the `riscv-pac` trait implementations of `Interrupt` and `Exception` enums. - Add `Mcause::from(usize)` for use in unit tests - Add `Mstatus::from(usize)` for use in unit tests - Add `Mstatus.bits()` @@ -20,11 +24,13 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Add `Mcounteren` in-memory update functions - Add `Mstatus` vector extension support - Add fallible counterparts to all functions that `panic` +- Add `riscv-pac` as a dependency ### Fixed - Fixed `sip::set_ssoft` and `sip::clear_ssoft` using wrong address - Fixed assignment in `mstatus` unit tests. +- delay implementation does not use binary labels in inline assembly. ## [v0.11.1] - 2024-02-15 diff --git a/riscv/Cargo.toml b/riscv/Cargo.toml index a783bb1c..35bc7fd8 100644 --- a/riscv/Cargo.toml +++ b/riscv/Cargo.toml @@ -1,8 +1,8 @@ [package] name = "riscv" -version = "0.11.1" +version = "0.12.0" edition = "2021" -rust-version = "1.60" +rust-version = "1.61" repository = "https://github.com/rust-embedded/riscv" authors = ["The RISC-V Team "] categories = ["embedded", "hardware-support", "no-std"] @@ -20,10 +20,12 @@ 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.2.0" } +riscv-macros = { path = "macros", version = "0.1.0", optional = true } 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..fae9b4be --- /dev/null +++ b/riscv/macros/src/lib.rs @@ -0,0 +1,438 @@ +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, +}; + +/// Struct to represent a function parameter. +struct FunctionParam { + /// Name of the parameter. + param_name: TokenStream2, + /// Data type of the parameter. + param_type: TokenStream2, +} + +/// Configuration parameters of a trap. It is useful to abstract the +/// differences between exception handlers and core interrupt handlers. +struct TrapConfig { + /// Name of the default handler (e.g., `DefaultHandler` for core interrupts). + default_handler: TokenStream2, + /// Vector describing all the function parameters of these kind of trap handlers. + handler_params: Vec, + /// Dispatch function name (e.g., `_dispatch_exception` or `_dispatch_core_interrupt`). + dispatch_fn_name: TokenStream2, + /// Name of the array that sorts all the trap handlers (e.g., `__CORE_INTERRUPTS`). + handlers_array_name: TokenStream2, +} + +impl TrapConfig { + /// Vector with all the input parameters expected when declaring extern handler functions + fn extern_signature(&self) -> Vec { + let mut res = Vec::new(); + for param in self.handler_params.iter() { + let param_name = ¶m.param_name; + let param_type = ¶m.param_type; + res.push(quote! { #param_name: #param_type }); + } + res + } + + /// Similar to [`Self::extern_signature`], but skipping the parameter names. + fn array_signature(&self) -> Vec { + let mut res = Vec::new(); + for param in self.handler_params.iter() { + res.push(param.param_type.clone()) + } + res + } + + /// Similar to [`Self::extern_signature`], but skipping the parameter data types. + fn handler_input(&self) -> Vec { + let mut res = Vec::new(); + for param in self.handler_params.iter() { + res.push(param.param_name.clone()) + } + res + } + + /// Similar to [`Self::extern_signature`], but pushing the trap `code` to the vector. + fn dispatch_fn_signature(&self) -> Vec { + let mut res = self.extern_signature(); + res.push(quote! {code: usize}); + res + } +} + +/// Traits that can be implemented using the `pac_enum` macro +enum PacTrait { + Exception, + 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 an additional marker trait, if any. + fn marker_trait_name(&self) -> Option { + match self { + Self::Interrupt(interrupt_type) => Some(interrupt_type.marker_trait_name()), + _ => None, + } + } + + /// Returns a token stream representing the 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), + } + } + + /// For Exception or an Interrupt enums, it returns the trap configuration details. + fn trap_config(&self) -> Option { + match self { + Self::Exception => Some(TrapConfig { + default_handler: quote! { ExceptionHandler }, + handler_params: vec![FunctionParam { + param_name: quote! { trap_frame }, + param_type: quote! { &riscv_rt::TrapFrame }, + }], + dispatch_fn_name: quote! { _dispatch_exception }, + handlers_array_name: quote! { __EXCEPTIONS }, + }), + Self::Interrupt(interrupt_type) => Some(TrapConfig { + default_handler: quote! { DefaultHandler }, + handler_params: Vec::new(), + dispatch_fn_name: interrupt_type.dispatch_fn_name(), + handlers_array_name: interrupt_type.isr_array_name(), + }), + _ => None, + } + } +} + +impl Parse for PacTrait { + 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.as_ref() { + Some((_, syn::Expr::Lit(expr_lit))) => match &expr_lit.lit { + syn::Lit::Int(lit_int) => { + lit_int.base10_parse::().unwrap_or_else(|_| { + panic!("All variant discriminants must be unsigned integers") + }) + } + _ => panic!("All variant discriminants must be unsigned integers"), + }, + None => panic!("Variant must have a discriminant"), + _ => panic!("All variant discriminants must be literal expressions"), + }; + + 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 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 handlers(&self, trap_config: &TrapConfig) -> Vec { + let signature = trap_config.extern_signature(); + self.numbers + .values() + .map(|ident| { + quote! { fn #ident (#(#signature),*) } + }) + .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 handlers_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 const_name = attr.const_name(); + + let max_discriminant = self.max_number; + let valid_matches = self.valid_matches(); + + // Push the trait implementation + res.push(quote! { + unsafe impl riscv::#trait_name for #name { + const #const_name: usize = #max_discriminant; + + #[inline] + fn number(self) -> usize { + self as _ + } + + #[inline] + fn from_number(number: usize) -> riscv::result::Result { + match number { + #(#valid_matches,)* + _ => Err(riscv::result::Error::InvalidVariant(number)), + } + } + } + }); + + if let Some(marker_trait_name) = attr.marker_trait_name() { + res.push(quote! { unsafe impl riscv::#marker_trait_name for #name {} }); + } + + if let Some(trap_config) = attr.trap_config() { + let default_handler = &trap_config.default_handler; + let extern_signature = trap_config.extern_signature(); + let handler_input = trap_config.handler_input(); + let array_signature = trap_config.array_signature(); + let dispatch_fn_name = &trap_config.dispatch_fn_name; + let dispatch_fn_args = &trap_config.dispatch_fn_signature(); + let vector_table = &trap_config.handlers_array_name; + + let handlers = self.handlers(&trap_config); + let interrupt_array = self.handlers_array(); + + // Push the interrupt handler functions and the interrupt array + res.push(quote! { + extern "C" { + #(#handlers;)* + } + + #[doc(hidden)] + #[no_mangle] + pub static #vector_table: [Option; #max_discriminant + 1] = [ + #(#interrupt_array),* + ]; + + #[inline] + #[no_mangle] + unsafe extern "C" fn #dispatch_fn_name(#(#dispatch_fn_args),*) { + extern "C" { + fn #default_handler(#(#extern_signature),*); + } + + match #vector_table.get(code) { + Some(Some(handler)) => handler(#(#handler_input),*), + _ => #default_handler(#(#handler_input),*), + } + } + }); + } + + if let PacTrait::Interrupt(InterruptType::Core) = attr { + 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 b6fcae4a..93fda11c 100644 --- a/riscv/src/interrupt.rs +++ b/riscv/src/interrupt.rs @@ -2,188 +2,97 @@ // 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. +/// +/// This enum represents the cause of a trap. It can be either an interrupt or an exception. +/// The [`mcause`](crate::register::mcause::Mcause::cause) and +/// [`scause`](crate::register::scause::Scause::cause) registers return a value of this type. +/// However, the trap cause is represented as raw numbers. To get a target-specific trap cause, +/// use [`Trap::try_into`] with your target-specific M-Mode or S-Mode trap cause types. +/// +/// # Example +/// +/// In targets that comply with the RISC-V standard, you can use the standard +/// [`Interrupt`] and [`Exception`] enums to represent the trap cause: +/// +/// ```no_run +/// use riscv::interrupt::{Trap, Interrupt, Exception}; +/// use riscv::register::mcause; +/// +/// let raw_trap: Trap = mcause::read().cause(); +/// let standard_trap: Trap = raw_trap.try_into().unwrap(); +/// ``` +/// +/// Targets that do not comply with the RISC-V standard usually have their own interrupt and exceptions. +/// You can find these types in the target-specific PAC. If it has been generated with `svd2rust`, +/// you can use the `pac::interrupt::CoreInterrupt` and `pac::interrupt::Exception` enums: +/// +/// ```ignore,no_run +/// use riscv::interrupt::Trap; +/// use pac::interrupt::{CoreInterrupt, Exception}; // pac is the target-specific PAC +/// +/// let standard_trap: Trap = pac::interrupt::cause(); +/// ``` +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum Trap { + Interrupt(I), + 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..92163a2e --- /dev/null +++ b/riscv/src/interrupt/machine.rs @@ -0,0 +1,274 @@ +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 + let mut after_mstatus = mstatus::read(); + if mstatus.mpie() { + after_mstatus.set_mpie(mstatus.mpie()); + } + after_mstatus.set_mpp(mstatus.mpp()); + mstatus::write(after_mstatus); + 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..ddb913c2 --- /dev/null +++ b/riscv/src/interrupt/supervisor.rs @@ -0,0 +1,260 @@ +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/tests/Cargo.toml b/tests/Cargo.toml new file mode 100644 index 00000000..d787b18b --- /dev/null +++ b/tests/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "tests" +version = "0.1.0" +edition = "2021" + +[dependencies] +riscv = { path = "../riscv", version = "0.12.0" } +riscv-rt = { path = "../riscv-rt", version = "0.13.0", features = ["no-exceptions", "no-interrupts"]} +trybuild = "1.0" diff --git a/tests/tests/riscv-rt/core_interrupt/fail_empty_macro.rs b/tests/tests/riscv-rt/core_interrupt/fail_empty_macro.rs new file mode 100644 index 00000000..0cec9d6c --- /dev/null +++ b/tests/tests/riscv-rt/core_interrupt/fail_empty_macro.rs @@ -0,0 +1,4 @@ +#[riscv_rt::core_interrupt] +fn my_interrupt() {} + +fn main() {} diff --git a/tests/tests/riscv-rt/core_interrupt/fail_empty_macro.stderr b/tests/tests/riscv-rt/core_interrupt/fail_empty_macro.stderr new file mode 100644 index 00000000..99d53458 --- /dev/null +++ b/tests/tests/riscv-rt/core_interrupt/fail_empty_macro.stderr @@ -0,0 +1,7 @@ +error: `#[core_interrupt]` attribute expects a path to a variant of an enum that implements the riscv_rt :: CoreInterruptNumber trait. + --> tests/riscv-rt/core_interrupt/fail_empty_macro.rs:1:1 + | +1 | #[riscv_rt::core_interrupt] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in the attribute macro `riscv_rt::core_interrupt` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tests/tests/riscv-rt/core_interrupt/fail_impl_interrupt_number.rs b/tests/tests/riscv-rt/core_interrupt/fail_impl_interrupt_number.rs new file mode 100644 index 00000000..752f62d7 --- /dev/null +++ b/tests/tests/riscv-rt/core_interrupt/fail_impl_interrupt_number.rs @@ -0,0 +1,4 @@ +#[riscv_rt::core_interrupt(riscv::interrupt::Exception::LoadMisaligned)] +fn my_interrupt() {} + +fn main() {} diff --git a/tests/tests/riscv-rt/core_interrupt/fail_impl_interrupt_number.stderr b/tests/tests/riscv-rt/core_interrupt/fail_impl_interrupt_number.stderr new file mode 100644 index 00000000..96e6fba5 --- /dev/null +++ b/tests/tests/riscv-rt/core_interrupt/fail_impl_interrupt_number.stderr @@ -0,0 +1,18 @@ +error[E0277]: the trait bound `riscv::interrupt::Exception: CoreInterruptNumber` is not satisfied + --> tests/riscv-rt/core_interrupt/fail_impl_interrupt_number.rs:1:28 + | +1 | #[riscv_rt::core_interrupt(riscv::interrupt::Exception::LoadMisaligned)] + | ---------------------------^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^-- + | | | + | | the trait `CoreInterruptNumber` is not implemented for `riscv::interrupt::Exception` + | required by a bound introduced by this call + | + = help: the following other types implement trait `CoreInterruptNumber`: + riscv::interrupt::Interrupt + riscv::interrupt::supervisor::Interrupt +note: required by a bound in `assert_impl` + --> tests/riscv-rt/core_interrupt/fail_impl_interrupt_number.rs:1:1 + | +1 | #[riscv_rt::core_interrupt(riscv::interrupt::Exception::LoadMisaligned)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `assert_impl` + = note: this error originates in the attribute macro `riscv_rt::core_interrupt` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tests/tests/riscv-rt/core_interrupt/fail_signatures.rs b/tests/tests/riscv-rt/core_interrupt/fail_signatures.rs new file mode 100644 index 00000000..0beab5b9 --- /dev/null +++ b/tests/tests/riscv-rt/core_interrupt/fail_signatures.rs @@ -0,0 +1,10 @@ +#[riscv_rt::core_interrupt(riscv::interrupt::Interrupt::SupervisorSoft)] +fn my_interrupt(code: usize) {} + +#[riscv_rt::core_interrupt(riscv::interrupt::Interrupt::SupervisorTimer)] +fn my_other_interrupt() -> usize {} + +#[riscv_rt::core_interrupt(riscv::interrupt::Interrupt::SupervisorExternal)] +async fn my_async_interrupt() {} + +fn main() {} diff --git a/tests/tests/riscv-rt/core_interrupt/fail_signatures.stderr b/tests/tests/riscv-rt/core_interrupt/fail_signatures.stderr new file mode 100644 index 00000000..03c1eec7 --- /dev/null +++ b/tests/tests/riscv-rt/core_interrupt/fail_signatures.stderr @@ -0,0 +1,17 @@ +error: `#[core_interrupt]` function must have signature `[unsafe] fn() [-> !]` + --> tests/riscv-rt/core_interrupt/fail_signatures.rs:2:1 + | +2 | fn my_interrupt(code: usize) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: `#[core_interrupt]` function must have signature `[unsafe] fn() [-> !]` + --> tests/riscv-rt/core_interrupt/fail_signatures.rs:5:1 + | +5 | fn my_other_interrupt() -> usize {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: `#[core_interrupt]` function must have signature `[unsafe] fn() [-> !]` + --> tests/riscv-rt/core_interrupt/fail_signatures.rs:8:1 + | +8 | async fn my_async_interrupt() {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/tests/riscv-rt/core_interrupt/pass_core_interrupt.rs b/tests/tests/riscv-rt/core_interrupt/pass_core_interrupt.rs new file mode 100644 index 00000000..1ed3a404 --- /dev/null +++ b/tests/tests/riscv-rt/core_interrupt/pass_core_interrupt.rs @@ -0,0 +1,11 @@ +use riscv::interrupt::Interrupt::*; + +#[riscv_rt::core_interrupt(SupervisorSoft)] +fn simple_interrupt() {} + +#[riscv_rt::core_interrupt(SupervisorTimer)] +unsafe fn no_return_interrupt() -> ! { + loop {} +} + +fn main() {} diff --git a/tests/tests/riscv-rt/exception/fail_empty_macro.rs b/tests/tests/riscv-rt/exception/fail_empty_macro.rs new file mode 100644 index 00000000..48b8af93 --- /dev/null +++ b/tests/tests/riscv-rt/exception/fail_empty_macro.rs @@ -0,0 +1,4 @@ +#[riscv_rt::exception] +fn my_exception() {} + +fn main() {} diff --git a/tests/tests/riscv-rt/exception/fail_empty_macro.stderr b/tests/tests/riscv-rt/exception/fail_empty_macro.stderr new file mode 100644 index 00000000..7489bd94 --- /dev/null +++ b/tests/tests/riscv-rt/exception/fail_empty_macro.stderr @@ -0,0 +1,7 @@ +error: `#[exception]` attribute expects a path to a variant of an enum that implements the riscv_rt :: ExceptionNumber trait. + --> tests/riscv-rt/exception/fail_empty_macro.rs:1:1 + | +1 | #[riscv_rt::exception] + | ^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in the attribute macro `riscv_rt::exception` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tests/tests/riscv-rt/exception/fail_impl_exception_number.rs b/tests/tests/riscv-rt/exception/fail_impl_exception_number.rs new file mode 100644 index 00000000..42e44659 --- /dev/null +++ b/tests/tests/riscv-rt/exception/fail_impl_exception_number.rs @@ -0,0 +1,4 @@ +#[riscv_rt::exception(riscv::interrupt::Interrupt::SupervisorSoft)] +fn my_exception() {} + +fn main() {} diff --git a/tests/tests/riscv-rt/exception/fail_impl_exception_number.stderr b/tests/tests/riscv-rt/exception/fail_impl_exception_number.stderr new file mode 100644 index 00000000..ce4c506b --- /dev/null +++ b/tests/tests/riscv-rt/exception/fail_impl_exception_number.stderr @@ -0,0 +1,18 @@ +error[E0277]: the trait bound `riscv::interrupt::Interrupt: ExceptionNumber` is not satisfied + --> tests/riscv-rt/exception/fail_impl_exception_number.rs:1:23 + | +1 | #[riscv_rt::exception(riscv::interrupt::Interrupt::SupervisorSoft)] + | ----------------------^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^-- + | | | + | | the trait `ExceptionNumber` is not implemented for `riscv::interrupt::Interrupt` + | required by a bound introduced by this call + | + = help: the following other types implement trait `ExceptionNumber`: + riscv::interrupt::Exception + riscv::interrupt::supervisor::Exception +note: required by a bound in `assert_impl` + --> tests/riscv-rt/exception/fail_impl_exception_number.rs:1:1 + | +1 | #[riscv_rt::exception(riscv::interrupt::Interrupt::SupervisorSoft)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `assert_impl` + = note: this error originates in the attribute macro `riscv_rt::exception` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tests/tests/riscv-rt/exception/fail_signatures.rs b/tests/tests/riscv-rt/exception/fail_signatures.rs new file mode 100644 index 00000000..6970d9be --- /dev/null +++ b/tests/tests/riscv-rt/exception/fail_signatures.rs @@ -0,0 +1,10 @@ +#[riscv_rt::exception(riscv::interrupt::Exception::LoadMisaligned)] +fn my_exception(code: usize) {} + +#[riscv_rt::exception(riscv::interrupt::Exception::StoreMisaligned)] +fn my_other_exception(trap_frame: &riscv_rt::TrapFrame, code: usize) {} + +#[riscv_rt::exception(riscv::interrupt::Exception::LoadFault)] +async fn my_async_exception(trap_frame: &riscv_rt::TrapFrame, code: usize) {} + +fn main() {} diff --git a/tests/tests/riscv-rt/exception/fail_signatures.stderr b/tests/tests/riscv-rt/exception/fail_signatures.stderr new file mode 100644 index 00000000..6f0c9107 --- /dev/null +++ b/tests/tests/riscv-rt/exception/fail_signatures.stderr @@ -0,0 +1,17 @@ +error: `#[exception]` function must have signature `[unsafe] fn([&[mut] riscv_rt::TrapFrame]) [-> !]` + --> tests/riscv-rt/exception/fail_signatures.rs:2:1 + | +2 | fn my_exception(code: usize) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: `#[exception]` function must have signature `[unsafe] fn([&[mut] riscv_rt::TrapFrame]) [-> !]` + --> tests/riscv-rt/exception/fail_signatures.rs:5:1 + | +5 | fn my_other_exception(trap_frame: &riscv_rt::TrapFrame, code: usize) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: `#[exception]` function must have signature `[unsafe] fn([&[mut] riscv_rt::TrapFrame]) [-> !]` + --> tests/riscv-rt/exception/fail_signatures.rs:8:1 + | +8 | async fn my_async_exception(trap_frame: &riscv_rt::TrapFrame, code: usize) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/tests/riscv-rt/exception/pass_exception.rs b/tests/tests/riscv-rt/exception/pass_exception.rs new file mode 100644 index 00000000..e0cdab5b --- /dev/null +++ b/tests/tests/riscv-rt/exception/pass_exception.rs @@ -0,0 +1,17 @@ +use riscv::interrupt::Exception::*; + +#[riscv_rt::exception(LoadMisaligned)] +fn simple_exception() {} + +#[riscv_rt::exception(LoadFault)] +fn unmutable_exception(_trap_frame: &riscv_rt::TrapFrame) {} + +#[riscv_rt::exception(StoreMisaligned)] +fn mutable_exception(_trap_frame: &mut riscv_rt::TrapFrame) {} + +#[riscv_rt::exception(StoreFault)] +fn no_return_exception(_trap_frame: &mut riscv_rt::TrapFrame) -> ! { + loop {} +} + +fn main() {} diff --git a/tests/tests/riscv-rt/external_interrupt/fail_empty_macro.rs b/tests/tests/riscv-rt/external_interrupt/fail_empty_macro.rs new file mode 100644 index 00000000..7767ed24 --- /dev/null +++ b/tests/tests/riscv-rt/external_interrupt/fail_empty_macro.rs @@ -0,0 +1,4 @@ +#[riscv_rt::external_interrupt] +fn my_interrupt() {} + +fn main() {} diff --git a/tests/tests/riscv-rt/external_interrupt/fail_empty_macro.stderr b/tests/tests/riscv-rt/external_interrupt/fail_empty_macro.stderr new file mode 100644 index 00000000..5ef53507 --- /dev/null +++ b/tests/tests/riscv-rt/external_interrupt/fail_empty_macro.stderr @@ -0,0 +1,7 @@ +error: `#[external_interrupt]` attribute expects a path to a variant of an enum that implements the riscv_rt :: ExternalInterruptNumber trait. + --> tests/riscv-rt/external_interrupt/fail_empty_macro.rs:1:1 + | +1 | #[riscv_rt::external_interrupt] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in the attribute macro `riscv_rt::external_interrupt` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tests/tests/riscv-rt/external_interrupt/fail_impl_interrupt_number.rs b/tests/tests/riscv-rt/external_interrupt/fail_impl_interrupt_number.rs new file mode 100644 index 00000000..48ab92b6 --- /dev/null +++ b/tests/tests/riscv-rt/external_interrupt/fail_impl_interrupt_number.rs @@ -0,0 +1,4 @@ +#[riscv_rt::external_interrupt(riscv::interrupt::Interrupt::SupervisorSoft)] +fn my_interrupt() {} + +fn main() {} diff --git a/tests/tests/riscv-rt/external_interrupt/fail_impl_interrupt_number.stderr b/tests/tests/riscv-rt/external_interrupt/fail_impl_interrupt_number.stderr new file mode 100644 index 00000000..03dace2c --- /dev/null +++ b/tests/tests/riscv-rt/external_interrupt/fail_impl_interrupt_number.stderr @@ -0,0 +1,15 @@ +error[E0277]: the trait bound `riscv::interrupt::Interrupt: ExternalInterruptNumber` is not satisfied + --> tests/riscv-rt/external_interrupt/fail_impl_interrupt_number.rs:1:32 + | +1 | #[riscv_rt::external_interrupt(riscv::interrupt::Interrupt::SupervisorSoft)] + | -------------------------------^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^-- + | | | + | | the trait `ExternalInterruptNumber` is not implemented for `riscv::interrupt::Interrupt` + | required by a bound introduced by this call + | +note: required by a bound in `assert_impl` + --> tests/riscv-rt/external_interrupt/fail_impl_interrupt_number.rs:1:1 + | +1 | #[riscv_rt::external_interrupt(riscv::interrupt::Interrupt::SupervisorSoft)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `assert_impl` + = note: this error originates in the attribute macro `riscv_rt::external_interrupt` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tests/tests/riscv-rt/external_interrupt/fail_signatures.rs b/tests/tests/riscv-rt/external_interrupt/fail_signatures.rs new file mode 100644 index 00000000..29757bae --- /dev/null +++ b/tests/tests/riscv-rt/external_interrupt/fail_signatures.rs @@ -0,0 +1,39 @@ +use riscv_rt::result::{Error, Result}; + +/// Just a dummy type to test the `ExternalInterrupt` trait. +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum ExternalInterrupt { + GPIO, + UART, + PWM, +} +unsafe impl riscv_rt::InterruptNumber for ExternalInterrupt { + const MAX_INTERRUPT_NUMBER: usize = 2; + + #[inline] + fn number(self) -> usize { + self as usize + } + + #[inline] + fn from_number(value: usize) -> Result { + match value { + 0 => Ok(Self::GPIO), + 1 => Ok(Self::UART), + 2 => Ok(Self::PWM), + _ => Err(Error::InvalidVariant(value)), + } + } +} +unsafe impl riscv::ExternalInterruptNumber for ExternalInterrupt {} + +#[riscv_rt::external_interrupt(ExternalInterrupt::GPIO)] +fn my_interrupt() -> usize {} + +#[riscv_rt::external_interrupt(ExternalInterrupt::UART)] +fn my_other_interrupt(code: usize) -> usize {} + +#[riscv_rt::external_interrupt(ExternalInterrupt::PWM)] +async fn my_async_interrupt(code: usize) -> usize {} + +fn main() {} diff --git a/tests/tests/riscv-rt/external_interrupt/fail_signatures.stderr b/tests/tests/riscv-rt/external_interrupt/fail_signatures.stderr new file mode 100644 index 00000000..3ea34689 --- /dev/null +++ b/tests/tests/riscv-rt/external_interrupt/fail_signatures.stderr @@ -0,0 +1,17 @@ +error: `#[external_interrupt]` function must have signature `[unsafe] fn() [-> !]` + --> tests/riscv-rt/external_interrupt/fail_signatures.rs:31:1 + | +31 | fn my_interrupt() -> usize {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: `#[external_interrupt]` function must have signature `[unsafe] fn() [-> !]` + --> tests/riscv-rt/external_interrupt/fail_signatures.rs:34:1 + | +34 | fn my_other_interrupt(code: usize) -> usize {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: `#[external_interrupt]` function must have signature `[unsafe] fn() [-> !]` + --> tests/riscv-rt/external_interrupt/fail_signatures.rs:37:1 + | +37 | async fn my_async_interrupt(code: usize) -> usize {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/tests/riscv-rt/external_interrupt/pass_external_interrupt.rs b/tests/tests/riscv-rt/external_interrupt/pass_external_interrupt.rs new file mode 100644 index 00000000..57aff904 --- /dev/null +++ b/tests/tests/riscv-rt/external_interrupt/pass_external_interrupt.rs @@ -0,0 +1,36 @@ +use riscv_rt::result::{Error, Result}; + +/// Just a dummy type to test the `ExternalInterrupt` trait. +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum ExternalInterrupt { + GPIO, + UART, +} +unsafe impl riscv_rt::InterruptNumber for ExternalInterrupt { + const MAX_INTERRUPT_NUMBER: usize = 1; + + #[inline] + fn number(self) -> usize { + self as usize + } + + #[inline] + fn from_number(value: usize) -> Result { + match value { + 0 => Ok(Self::GPIO), + 1 => Ok(Self::UART), + _ => Err(Error::InvalidVariant(value)), + } + } +} +unsafe impl riscv::ExternalInterruptNumber for ExternalInterrupt {} + +#[riscv_rt::external_interrupt(ExternalInterrupt::GPIO)] +fn simple_interrupt() {} + +#[riscv_rt::external_interrupt(ExternalInterrupt::UART)] +unsafe fn no_return_interrupt() -> ! { + loop {} +} + +fn main() {} diff --git a/tests/tests/riscv/fail_empty_macro.rs b/tests/tests/riscv/fail_empty_macro.rs new file mode 100644 index 00000000..8891da0a --- /dev/null +++ b/tests/tests/riscv/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/tests/tests/riscv/fail_empty_macro.stderr b/tests/tests/riscv/fail_empty_macro.stderr new file mode 100644 index 00000000..d163ec43 --- /dev/null +++ b/tests/tests/riscv/fail_empty_macro.stderr @@ -0,0 +1,7 @@ +error: unexpected end of input, expected `unsafe` + --> tests/riscv/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/tests/tests/riscv/fail_no_unsafe.rs b/tests/tests/riscv/fail_no_unsafe.rs new file mode 100644 index 00000000..a304fa9e --- /dev/null +++ b/tests/tests/riscv/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/tests/tests/riscv/fail_no_unsafe.stderr b/tests/tests/riscv/fail_no_unsafe.stderr new file mode 100644 index 00000000..cdb3789e --- /dev/null +++ b/tests/tests/riscv/fail_no_unsafe.stderr @@ -0,0 +1,5 @@ +error: expected `unsafe` + --> tests/riscv/fail_no_unsafe.rs:1:19 + | +1 | #[riscv::pac_enum(InterruptNumber)] + | ^^^^^^^^^^^^^^^ diff --git a/tests/tests/riscv/fail_unknown_trait.rs b/tests/tests/riscv/fail_unknown_trait.rs new file mode 100644 index 00000000..dc6c5d44 --- /dev/null +++ b/tests/tests/riscv/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/tests/tests/riscv/fail_unknown_trait.stderr b/tests/tests/riscv/fail_unknown_trait.stderr new file mode 100644 index 00000000..03fcad8b --- /dev/null +++ b/tests/tests/riscv/fail_unknown_trait.stderr @@ -0,0 +1,5 @@ +error: Unknown trait name. Expected: 'ExceptionNumber', 'CoreInterruptNumber', 'ExternalInterruptNumber', 'PriorityNumber', or 'HartIdNumber' + --> tests/riscv/fail_unknown_trait.rs:1:26 + | +1 | #[riscv::pac_enum(unsafe InterruptNumber)] + | ^^^^^^^^^^^^^^^ diff --git a/tests/tests/riscv/pass_test.rs b/tests/tests/riscv/pass_test.rs new file mode 100644 index 00000000..c80ead3e --- /dev/null +++ b/tests/tests/riscv/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); +} diff --git a/tests/tests/test.rs b/tests/tests/test.rs new file mode 100644 index 00000000..b89b72cc --- /dev/null +++ b/tests/tests/test.rs @@ -0,0 +1,15 @@ +#[test] +fn riscv() { + let t = trybuild::TestCases::new(); + + t.compile_fail("tests/riscv/fail_*.rs"); + t.pass("tests/riscv/pass_*.rs"); +} + +#[test] +fn riscv_rt() { + let t = trybuild::TestCases::new(); + + t.compile_fail("tests/riscv-rt/*/fail_*.rs"); + t.pass("tests/riscv-rt/*/pass_*.rs"); +}