Skip to content

Commit cc7e6e9

Browse files
authored
feat(d1): add a simple exception handler (#237)
1 parent e0b3d8a commit cc7e6e9

File tree

6 files changed

+272
-2
lines changed

6 files changed

+272
-2
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

platforms/allwinner-d1/boards/src/bin/lichee-rv.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,6 @@ fn main() -> ! {
5050
});
5151

5252
d1.initialize_sharp_display();
53-
5453
// Initialize LED loop
5554
d1.kernel
5655
.initialize(async move {

platforms/allwinner-d1/boards/src/lib.rs

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@ extern crate alloc;
44

55
use core::{panic::PanicInfo, ptr::NonNull};
66
use kernel::mnemos_alloc::heap::{MnemosAlloc, SingleThreadedLinkedListAllocator};
7-
use mnemos_d1_core::{Ram, D1};
7+
use mnemos_d1_core::{
8+
trap::{self, Trap},
9+
Ram, D1,
10+
};
811

912
#[global_allocator]
1013
static AHEAP: MnemosAlloc<SingleThreadedLinkedListAllocator> = MnemosAlloc::new();
@@ -22,3 +25,19 @@ pub unsafe fn initialize_heap<const HEAP_SIZE: usize>(buf: &'static Ram<HEAP_SIZ
2225
fn handler(info: &PanicInfo) -> ! {
2326
D1::handle_panic(info)
2427
}
28+
29+
#[export_name = "ExceptionHandler"]
30+
fn exception_handler(trap_frame: &riscv_rt::TrapFrame) -> ! {
31+
match Trap::from_mcause().expect("mcause should never be invalid") {
32+
Trap::Interrupt(int) => {
33+
unreachable!("the exception handler should only recieve exception traps, but got {int}")
34+
}
35+
Trap::Exception(exn) => {
36+
let mepc = riscv::register::mepc::read();
37+
panic!(
38+
"CPU exception: {exn} ({exn:#X}) at {mepc:#X}\n\n{:#X}",
39+
trap::PrettyTrapFrame(trap_frame)
40+
);
41+
}
42+
}
43+
}

platforms/allwinner-d1/core/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ license = "MIT OR Apache-2.0"
1717
[dependencies]
1818
d1-pac = "0.0.31"
1919
critical-section = "1.1.1"
20+
riscv-rt = "0.11.0"
2021

2122
# kernel
2223
[dependencies.mnemos]

platforms/allwinner-d1/core/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ pub mod drivers;
99
pub mod plic;
1010
mod ram;
1111
pub mod timer;
12+
pub mod trap;
1213

1314
use core::{
1415
fmt::Write,
Lines changed: 249 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,249 @@
1+
//! RISC-V trap/exception handling
2+
#![warn(missing_docs)]
3+
// TODO(eliza): none of this is really D1-specific, and it should all work on
4+
// any RISC-V target. If/when we add a generic `mnemos-riscv` crate, we may want
5+
// to move this stuff there...
6+
7+
use core::fmt;
8+
9+
/// Represents the cause of a trap, based on the value of the `mcause` register.
10+
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
11+
pub enum Trap {
12+
/// The trap was an interrupt.
13+
Interrupt(Interrupt),
14+
/// The trap was an exception.
15+
Exception(Exception),
16+
}
17+
18+
/// Error returned by [`Trap::from_mcause`].
19+
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
20+
pub struct InvalidMcause {
21+
bits: usize,
22+
err: &'static str,
23+
}
24+
25+
/// Pretty-prints a [`riscv_rt::TrapFrame`].
26+
#[derive(Copy, Clone, Debug)]
27+
pub struct PrettyTrapFrame<'a>(pub &'a riscv_rt::TrapFrame);
28+
29+
macro_rules! cause_enum {
30+
(
31+
$(#[$meta:meta])* $vis:vis enum $Enum:ident {
32+
$(
33+
$Variant:ident = $val:literal => $pretty:literal
34+
),+
35+
$(,)?
36+
}
37+
) => {
38+
$(#[$meta])*
39+
#[repr(usize)]
40+
$vis enum $Enum {
41+
$(
42+
#[doc = $pretty]
43+
$Variant = $val,
44+
)+
45+
}
46+
47+
impl fmt::Display for $Enum {
48+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
49+
match self {
50+
$(
51+
$Enum::$Variant => f.pad($pretty),
52+
)+
53+
}
54+
}
55+
}
56+
57+
impl fmt::UpperHex for $Enum {
58+
#[inline]
59+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
60+
fmt::UpperHex::fmt(&(*self as usize), f)
61+
}
62+
}
63+
64+
impl fmt::LowerHex for $Enum {
65+
#[inline]
66+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
67+
fmt::LowerHex::fmt(&(*self as usize), f)
68+
}
69+
}
70+
71+
impl TryFrom<usize> for $Enum {
72+
type Error = &'static str;
73+
74+
fn try_from(value: usize) -> Result<Self, Self::Error> {
75+
match value {
76+
$(
77+
$val => Ok($Enum::$Variant),
78+
)+
79+
_ => Err(cause_enum!(@error: $Enum, $($val),+)),
80+
}
81+
}
82+
}
83+
};
84+
85+
(@error: $Enum:ident, $firstval:literal, $($val:literal),*) => {
86+
concat!(
87+
"invalid value for ",
88+
stringify!($Enum),
89+
", expected one of [",
90+
stringify!($firstval),
91+
$(
92+
", ",
93+
stringify!($val),
94+
)+
95+
"]",
96+
)
97+
};
98+
}
99+
100+
cause_enum! {
101+
/// RISC-V exception causes.
102+
///
103+
/// If the interrupt bit (the highest bit) in the `mcause` register is 0, the
104+
/// rest of the `mcause` register is interpreted as an exception cause.
105+
///
106+
/// See "3.1.20 Machine Cause Register (`mcause`)" in [_RISC-V rivileged
107+
/// Architectures_, v1.10][manual].
108+
///
109+
/// [manual]:
110+
/// https://riscv.org/wp-content/uploads/2017/05/riscv-privileged-v1.10.pdf
111+
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
112+
pub enum Exception {
113+
InstAddrMisaligned = 0 => "Instruction address misaligned",
114+
InstAccessFault = 1 => "Instruction access fault",
115+
IllegalInst = 2 => "Illegal instruction",
116+
Breakpoint = 3 => "Breakpoint",
117+
LoadAddrMisaligned = 4 => "Load address misaligned",
118+
LoadAccessFault = 5 => "Load access fault",
119+
StoreAddrMisaligned = 6 => "Store/AMO address misaligned",
120+
StoreAccessFault = 7 => "Store/AMO access fault",
121+
UModeEnvCall = 8 => "Environment call from U-mode",
122+
SModeEnvCall = 9 => "Environment call from S-mode",
123+
MModeEnvCall = 11 => "Environment call from M-mode",
124+
InstPageFault = 12 => "Instruction page fault",
125+
LoadPageFault = 13 => "Load page fault",
126+
StorePageFault = 15 => "Store/AMO page fault",
127+
}
128+
}
129+
130+
cause_enum! {
131+
/// RISC-V interrupt causes.
132+
///
133+
/// If the interrupt bit (the highest bit) in the `mcause` register is 1, the
134+
/// rest of the `mcause` register is interpreted as an interrupt cause.
135+
///
136+
/// See "3.1.20 Machine Cause Register (`mcause`)" in [_RISC-V Privileged
137+
/// Architectures_, v1.10][manual].
138+
///
139+
/// [manual]:
140+
/// https://riscv.org/wp-content/uploads/2017/05/riscv-privileged-v1.10.pdf
141+
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
142+
pub enum Interrupt {
143+
UserSw = 0 => "User software interrupt",
144+
SupervisorSw = 1 => "Supervisor software interrupt",
145+
MachineSw = 3 => "Machine software interrupt",
146+
UserTimer = 4 => "User timer interrupt",
147+
SupervisorTimer = 5 => "Supervisor timer interrupt",
148+
MachineTimer = 7 => "Machine timer interrupt",
149+
UserExt = 8 => "User external interrupt",
150+
SupervisorExt = 9 => "Supervisor external interrupt",
151+
MachineExt = 11 => "Machine external interrupt",
152+
}
153+
}
154+
155+
// === impl Trap ===
156+
157+
impl Trap {
158+
/// Reads the value of the `mcause` register and interprets it as a `Trap`.
159+
///
160+
/// # Returns
161+
///
162+
/// - [`Ok`]`(`[`Trap`]`)` if the value of the `mcause` register is a valid
163+
/// trap code
164+
/// - [`Err`]`(`[`InvalidMcause`]`)` if the value of the `mcause` register
165+
/// is not a valid trap code. Note that this Should Not Happen if the
166+
/// hardware is functioning correctly.
167+
#[cfg(any(target_arch = "riscv64", target_arch = "riscv32"))]
168+
pub fn from_mcause() -> Result<Self, InvalidMcause> {
169+
let mut bits: usize;
170+
unsafe {
171+
core::arch::asm!("csrrs {}, mcause, x0", out(reg) bits, options(nomem, nostack, preserves_flags));
172+
}
173+
Self::from_bits(bits)
174+
}
175+
176+
/// Reads the value of the `mcause` register and interprets it as a `Trap`.
177+
///
178+
/// # Returns
179+
///
180+
/// - [`Ok`]`(`[`Trap`]`)` if the value of the `mcause` register is a valid
181+
/// trap code
182+
/// - [`Err`]`(`[`InvalidMcause`]`)` if the value of the `mcause` register
183+
/// is not a valid trap code. Note that this Should Not Happen if the
184+
/// hardware is functioning correctly.
185+
#[cfg(not(any(target_arch = "riscv64", target_arch = "riscv32")))]
186+
pub fn from_mcause() -> Result<Self, InvalidMcause> {
187+
unimplemented!("cannot access mcause on a non-RISC-V platform!")
188+
}
189+
190+
#[cfg_attr(
191+
not(any(target_arch = "riscv64", target_arch = "riscv32")),
192+
allow(dead_code)
193+
)]
194+
fn from_bits(bits: usize) -> Result<Self, InvalidMcause> {
195+
const INTERRUPT_BIT: usize = 1 << (usize::BITS - 1);
196+
197+
let res = if bits & INTERRUPT_BIT == INTERRUPT_BIT {
198+
Interrupt::try_from(bits & !INTERRUPT_BIT).map(Self::Interrupt)
199+
} else {
200+
Exception::try_from(bits & !INTERRUPT_BIT).map(Self::Exception)
201+
};
202+
res.map_err(|err| InvalidMcause { err, bits })
203+
}
204+
}
205+
206+
impl fmt::Display for Trap {
207+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
208+
match self {
209+
Trap::Interrupt(t) => fmt::Display::fmt(t, f),
210+
Trap::Exception(t) => fmt::Display::fmt(t, f),
211+
}
212+
}
213+
}
214+
215+
// === impl InvalidMcause ===
216+
217+
impl fmt::Display for InvalidMcause {
218+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
219+
let Self { bits, err } = self;
220+
write!(f, "invalid mcause ({bits:#b}): {err}")
221+
}
222+
}
223+
224+
const CHARS: usize = (usize::BITS / 4) as usize;
225+
226+
macro_rules! pretty_trap_frame {
227+
(fmt: $f:ident, frame: $frame:expr, $spec:literal => $($reg:ident),+ $(,)?) => {
228+
229+
let nl = if $f.alternate() { "\n" } else { ", " };
230+
$(
231+
$f.pad(stringify!($reg))?;
232+
write!($f, concat!(": {:0width$", $spec,"}{}"), $frame.$reg, nl, width = CHARS)?;
233+
)+
234+
}
235+
}
236+
237+
impl fmt::LowerHex for PrettyTrapFrame<'_> {
238+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
239+
pretty_trap_frame!(fmt: f, frame: self.0, "x" => ra, t0, t1, t2, t3, t4, t5, t6, a0, a1, a2, a3, a4, a5, a6, a7);
240+
Ok(())
241+
}
242+
}
243+
244+
impl fmt::UpperHex for PrettyTrapFrame<'_> {
245+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
246+
pretty_trap_frame!(fmt: f, frame: self.0, "X" => ra, t0, t1, t2, t3, t4, t5, t6, a0, a1, a2, a3, a4, a5, a6, a7);
247+
Ok(())
248+
}
249+
}

0 commit comments

Comments
 (0)