As usual, I needed to post the question on StackOverflow to find the issue myself one minute later.
The issue was in the linker script: when I removed these two lines I've got the correct first-level handler in place, and my hardware jumped to it upon an IRQ:
_vector_table = ORIGIN(REGION_TEXT) + 0x12340;
_start_trap = ORIGIN(REGION_TEXT) + 0x12340;
Obviously, the drawback is that now I have to rely on linker to locate the handlers, but at least it works somehow. Initially I wanted it to be always at location 0x12340, so that I could put a breakpoint there without doing any math.
It turned out that if I do not define these symbols explicitly in the linker script, I can still define them as extern
in my code and it works fine. Below is an example of my overloaded _setup_interrupts
:
use riscv::register;
#[unsafe(no_mangle)]
pub extern "Rust" fn _setup_interrupts() {
unsafe {
let vectored = false;
let mtvec = if vectored {
unsafe extern "C" {
fn _vector_table();
}
let mut mtvec = register::mtvec::Mtvec::from_bits(_vector_table as usize);
mtvec.set_trap_mode(register::stvec::TrapMode::Vectored);
mtvec
} else {
unsafe extern "C" {
fn _start_trap();
}
let mut mtvec = register::mtvec::Mtvec::from_bits(_start_trap as usize);
mtvec.set_trap_mode(register::stvec::TrapMode::Direct);
mtvec
};
register::mtvec::write(mtvec);
...
}
}