Skip to content

Commit b3e8290

Browse files
Merge #129
129: PLIC peripheral r=romancardenas a=romancardenas Same as #125 (closing right now) but with a clean commit history. I've been playing around with this implementation and detected nice-to-have features and boring patterns that could be implemented using macros. Pointers to repos that are already working with it: - [`e310x` PAC](https://github.com/greenlsi/e310x) - [`e310x-hal` HAL](https://github.com/greenlsi/e310x-hal) - [`hifive1` BSP](https://github.com/romancardenas/hifive1) with an illustrative [example](https://github.com/romancardenas/hifive1/blob/master/examples/interrupt.rs) Co-authored-by: Román Cárdenas <[email protected]>
2 parents 864bce2 + 11cc875 commit b3e8290

File tree

6 files changed

+488
-0
lines changed

6 files changed

+488
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
99

1010
### Added
1111

12+
- Add generic implementation of a PLIC peripheral
1213
- Add `asm::fence()`, a wrapper for implementing a `fence` instruction
1314
- Add `asm::fence_i()`, a wrapper for implementing a `fence.i` instruction
1415

Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ keywords = ["riscv", "register", "peripheral"]
1111
license = "ISC"
1212

1313
[package.metadata.docs.rs]
14+
all-features = true
1415
default-target = "riscv64imac-unknown-none-elf"
1516
targets = [
1617
"riscv32i-unknown-none-elf", "riscv32imc-unknown-none-elf", "riscv32imac-unknown-none-elf",
@@ -19,8 +20,10 @@ targets = [
1920

2021
[features]
2122
critical-section-single-hart = ["critical-section/restore-state-bool"]
23+
plic = ["volatile-register"]
2224

2325
[dependencies]
2426
bit_field = "0.10.0"
2527
critical-section = "1.1.0"
2628
embedded-hal = "0.2.6"
29+
volatile-register = {version = "0.2.1", optional = true}

src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@
3131
pub mod asm;
3232
pub mod delay;
3333
pub mod interrupt;
34+
#[cfg(any(feature = "plic"))]
35+
pub mod peripheral;
3436
pub mod register;
3537

3638
#[macro_use]

src/macros.rs

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,3 +55,161 @@ macro_rules! singleton {
5555
})
5656
};
5757
}
58+
59+
/// Macro to create interfaces to PLIC contexts in PACs.
60+
///
61+
/// This macro expects 5 arguments:
62+
///
63+
/// - `PLIC`: name of the PLIC context interface structure to be created.
64+
/// We recommend to leave `PLIC` for context 0 and `PLICx` for the remaining contexts.
65+
///
66+
/// - `BASE`: base address of the PLIC peripheral of the target.
67+
///
68+
/// - `CONTEXT`: context number assigned to the PLIC interface.
69+
///
70+
/// - `INTERRUPT`: enum type of the external interruptions of the target.
71+
/// This type must implement the [`crate::peripheral::plic::InterruptNumber`] trait.
72+
///
73+
/// - `PRIORITY`: enum type of the priority levels supported by the target.
74+
/// This type must implement the [`crate::peripheral::plic::PriorityNumber`] trait.
75+
///
76+
/// # Note
77+
///
78+
/// This macro requires the `plic` feature to be active.
79+
#[cfg(feature = "plic")]
80+
#[macro_export]
81+
macro_rules! plic_context {
82+
($PLIC:ident, $BASE:literal, $CONTEXT:literal, $INTERRUPT:ident, $PRIORITY:ident) => {
83+
/// Platform-Level Interrupt Controller (PLIC) context.
84+
#[repr(transparent)]
85+
pub struct $PLIC {
86+
context: $crate::peripheral::PLIC<$BASE, $CONTEXT>,
87+
}
88+
89+
impl $PLIC {
90+
/// Creates a new PLIC context interface.
91+
pub const fn new() -> Self {
92+
Self {
93+
context: $crate::peripheral::PLIC::new(),
94+
}
95+
}
96+
97+
/// Enables machine external interrupts.
98+
#[inline(always)]
99+
pub fn enable() {
100+
$crate::peripheral::PLIC::<$BASE, $CONTEXT>::enable();
101+
}
102+
103+
/// Disables machine external interrupts.
104+
#[inline(always)]
105+
pub fn disable() {
106+
$crate::peripheral::PLIC::<$BASE, $CONTEXT>::disable();
107+
}
108+
109+
/// Returns the priority level associated to a given interrupt source.
110+
#[inline(always)]
111+
pub fn priority(source: $INTERRUPT) -> $PRIORITY {
112+
$crate::peripheral::PLIC::<$BASE, $CONTEXT>::priority(source)
113+
}
114+
115+
/// Getter method for the priority level associated to a given interrupt source.
116+
#[inline(always)]
117+
pub fn get_priority(&self, source: $INTERRUPT) -> $PRIORITY {
118+
Self::priority(source)
119+
}
120+
121+
/// Sets the priority level of a given interrupt source.
122+
///
123+
/// # Note
124+
///
125+
/// Interrupt source priorities are shared among all the contexts of the PLIC.
126+
/// Thus, changing the priority of sources may affect other PLIC contexts.
127+
///
128+
/// # Safety
129+
///
130+
/// Changing priority levels can break priority-based critical sections and compromise memory safety.
131+
#[inline(always)]
132+
pub unsafe fn set_priority(&mut self, source: $INTERRUPT, priority: $PRIORITY) {
133+
self.context.set_priority(source, priority);
134+
}
135+
136+
/// Checks if an interrupt triggered by a given source is pending.
137+
#[inline(always)]
138+
pub fn is_interrupt_pending(source: $INTERRUPT) -> bool {
139+
$crate::peripheral::PLIC::<$BASE, $CONTEXT>::is_interrupt_pending(source)
140+
}
141+
142+
/// Checks if an interrupt source is enabled for the PLIC context.
143+
#[inline(always)]
144+
pub fn is_interrupt_enabled(source: $INTERRUPT) -> bool {
145+
$crate::peripheral::PLIC::<$BASE, $CONTEXT>::is_interrupt_enabled(source)
146+
}
147+
148+
/// Enables an interrupt source for the PLIC context.
149+
///
150+
/// # Safety
151+
///
152+
/// It performs non-atomic read-modify-write operations, which may lead to undefined behavior.
153+
/// Additionally, Enabling an interrupt source can break mask-based critical sections.
154+
#[inline(always)]
155+
pub unsafe fn enable_interrupt(&mut self, source: $INTERRUPT) {
156+
self.context.enable_interrupt(source);
157+
}
158+
159+
/// Disables an interrupt source for the PLIC context.
160+
///
161+
/// # Safety
162+
///
163+
/// It performs non-atomic read-modify-write operations, which may lead to undefined behavior.
164+
#[inline(always)]
165+
pub unsafe fn disable_interrupt(&mut self, source: $INTERRUPT) {
166+
self.context.disable_interrupt(source);
167+
}
168+
169+
/// Returns the priority threshold of the PLIC context.
170+
#[inline(always)]
171+
pub fn threshold() -> $PRIORITY {
172+
$crate::peripheral::PLIC::<$BASE, $CONTEXT>::threshold()
173+
}
174+
175+
/// Getter method for the priority threshold of the PLIC context.
176+
#[inline(always)]
177+
pub fn get_threshold(&self) -> $PRIORITY {
178+
Self::threshold()
179+
}
180+
181+
/// Sets the priority threshold for for the PLIC context.
182+
///
183+
/// # Safety
184+
///
185+
/// Unmasking an interrupt source can break mask-based critical sections.
186+
#[inline(always)]
187+
pub unsafe fn set_threshold(&mut self, priority: $PRIORITY) {
188+
self.context.set_threshold(priority);
189+
}
190+
191+
/// Claims the number of a pending interrupt for for the PLIC context.
192+
/// If no interrupt is pending for this context, it returns [`None`].
193+
#[inline(always)]
194+
pub fn claim() -> Option<$INTERRUPT> {
195+
$crate::peripheral::PLIC::<$BASE, $CONTEXT>::claim()
196+
}
197+
198+
/// Marks a pending interrupt as complete from for the PLIC context.
199+
#[inline(always)]
200+
pub fn complete(source: $INTERRUPT) {
201+
$crate::peripheral::PLIC::<$BASE, $CONTEXT>::complete(source);
202+
}
203+
204+
/// Resets the PLIC peripherals.
205+
///
206+
/// # Safety
207+
///
208+
/// It performs non-atomic read-modify-write operations, which may lead to undefined behavior.
209+
#[inline(always)]
210+
pub unsafe fn reset(&mut self) {
211+
self.context.reset::<$INTERRUPT, $PRIORITY>();
212+
}
213+
}
214+
};
215+
}

src/peripheral.rs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
//! RISC-V peripherals
2+
use core::marker::PhantomData;
3+
4+
// Platform-Level Interrupt Controller
5+
#[cfg(feature = "plic")]
6+
pub mod plic;
7+
8+
/// Interface for a context of the PLIC peripheral.
9+
///
10+
/// # Note
11+
///
12+
/// This structure requires the `plic` feature.
13+
///
14+
/// The RISC-V standard does not specify a fixed location for the PLIC.
15+
/// Thus, we use const generics to map a PLIC to the desired memory location.
16+
/// Each platform must specify the base address of the PLIC on the platform.
17+
///
18+
/// The PLIC standard allows up to 15_872 different contexts for interfacing the PLIC.
19+
/// Usually, each HART uses a dedicated context. In this way, they do not interfere
20+
/// with each other when attending to external interruptions.
21+
///
22+
/// You can use the [`crate::plic_context`] macro to generate a specific structure
23+
/// for interfacing every PLIC context of your platform. The resulting structure
24+
/// replaces generic types with the specific types of your target.
25+
#[allow(clippy::upper_case_acronyms)]
26+
#[cfg(feature = "plic")]
27+
#[derive(Default)]
28+
pub struct PLIC<const BASE: usize, const CONTEXT: usize> {
29+
_marker: PhantomData<*const ()>,
30+
}
31+
32+
#[cfg(feature = "plic")]
33+
impl<const BASE: usize, const CONTEXT: usize> PLIC<BASE, CONTEXT> {
34+
/// Pointer to the register block
35+
pub const PTR: *const self::plic::RegisterBlock = BASE as *const _;
36+
37+
/// Creates a new interface for the PLIC peripheral. PACs can use this
38+
/// function to add a PLIC interface to their `Peripherals` struct.
39+
pub const fn new() -> Self {
40+
Self {
41+
_marker: PhantomData,
42+
}
43+
}
44+
}

0 commit comments

Comments
 (0)