Skip to content

Commit 4f6564f

Browse files
Merge branch 'main' into feat/i2s-move
2 parents be30dda + c700b8b commit 4f6564f

File tree

5 files changed

+155
-50
lines changed

5 files changed

+155
-50
lines changed

esp-hal/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7676
- ESP32: Enable up to 4M of PSRAM (#3990)
7777
- I2C error recovery logic issues (#4000)
7878
- I2S: Fixed RX half-sample bits configuration bug causing microphone noise (#4109)
79+
- RISC-V: Direct interrupt vectoring (#4171)
7980

8081
### Removed
8182

esp-hal/MIGRATING-1.0.0-rc.0.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,3 +267,18 @@ All RTC clock enums/structs have been moved from `rtc_cntl` to the `clock` modul
267267
Imports will need to be updated accordingly.
268268

269269
Additionally, enum variant naming violations have been resolved, so the `RtcFastClock` and `RtcSlowClock` prefixes will need to be removed from any variants from these enums.
270+
271+
## Direct vectoring changes
272+
273+
`enable_direct` now requires user to pass handler function to it.
274+
275+
```diff
276+
interrupt::enable_direct(
277+
Interrupt::FROM_CPU_INTR0,
278+
Priority::Priority3,
279+
CpuInterrupt::Interrupt20,
280+
+ interrupt_handler,
281+
)
282+
.unwrap();
283+
284+
```

esp-hal/src/interrupt/riscv.rs

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,14 +210,32 @@ pub static RESERVED_INTERRUPTS: &[u32] = PRIORITY_TO_INTERRUPT;
210210

211211
/// Enable an interrupt by directly binding it to a available CPU interrupt
212212
///
213-
/// Unless you are sure, you most likely want to use [`enable`] instead.
213+
/// ⚠️ This installs a *raw trap handler*, the `handler` user provides is written directly into the
214+
/// CPU interrupt vector table. That means:
215+
///
216+
/// - Provided handler will be used as an actual trap-handler
217+
/// - It is user's responsibility to:
218+
/// - Save and restore all registers they use.
219+
/// - Clear the interrupt source if necessary.
220+
/// - Return using the `mret` instruction.
221+
/// - The handler should be declared as naked function. The compiler will not insert a function
222+
/// prologue/epilogue for the user, normal Rust `fn` will result in an error.
223+
///
224+
/// Unless you are sure that you need such low-level control to achieve the lowest possible latency,
225+
/// you most likely want to use [`enable`] instead.
214226
///
215227
/// Trying using a reserved interrupt from [`RESERVED_INTERRUPTS`] will return
216228
/// an error.
229+
///
230+
/// ## Example
231+
/// Visit the [interrupt] test to see a proper example of how to use direct vectoring.
232+
///
233+
/// [interrupt]: https://github.com/esp-rs/esp-hal/blob/main/hil-test/src/bin/interrupt.rs
217234
pub fn enable_direct(
218235
interrupt: Interrupt,
219236
level: Priority,
220237
cpu_interrupt: CpuInterrupt,
238+
handler: unsafe extern "C" fn(),
221239
) -> Result<(), Error> {
222240
if RESERVED_INTERRUPTS.contains(&(cpu_interrupt as _)) {
223241
return Err(Error::CpuInterruptReserved);
@@ -228,11 +246,53 @@ pub fn enable_direct(
228246
unsafe {
229247
map(Cpu::current(), interrupt, cpu_interrupt);
230248
set_priority(Cpu::current(), cpu_interrupt, level);
249+
250+
let mt = mtvec::read();
251+
252+
assert_eq!(
253+
mt.trap_mode().into_usize(),
254+
mtvec::TrapMode::Vectored.into_usize()
255+
);
256+
257+
let base_addr = mt.address() as usize;
258+
259+
let int_slot = base_addr.wrapping_add((cpu_interrupt as usize) * 4);
260+
261+
let instr = encode_jal_x0(handler as usize, int_slot)?;
262+
263+
core::ptr::write_volatile(int_slot as *mut u32, instr);
264+
core::arch::asm!("fence.i");
265+
231266
enable_cpu_interrupt(cpu_interrupt);
232267
}
233268
Ok(())
234269
}
235270

271+
// helper: returns correctly encoded RISC-V `jal` instruction
272+
fn encode_jal_x0(target: usize, pc: usize) -> Result<u32, Error> {
273+
let offset = (target as isize) - (pc as isize);
274+
275+
const MIN: isize = -(1isize << 20);
276+
const MAX: isize = (1isize << 20) - 1;
277+
278+
assert!(offset % 2 == 0 && (MIN..=MAX).contains(&offset));
279+
280+
let imm = offset as u32;
281+
let imm20 = (imm >> 20) & 0x1;
282+
let imm10_1 = (imm >> 1) & 0x3ff;
283+
let imm11 = (imm >> 11) & 0x1;
284+
let imm19_12 = (imm >> 12) & 0xff;
285+
286+
let instr = (imm20 << 31)
287+
| (imm19_12 << 12)
288+
| (imm11 << 20)
289+
| (imm10_1 << 21)
290+
// https://lhtin.github.io/01world/app/riscv-isa/?xlen=32&insn_name=jal
291+
| 0b1101111u32;
292+
293+
Ok(instr)
294+
}
295+
236296
/// Disable the given peripheral interrupt.
237297
pub fn disable(core: Cpu, interrupt: Interrupt) {
238298
map_raw(core, interrupt, DISABLED_CPU_INTERRUPT)

hil-test/src/bin/interrupt.rs

Lines changed: 77 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -25,36 +25,45 @@ use hil_test as _;
2525

2626
static SWINT0: Mutex<RefCell<Option<SoftwareInterrupt<0>>>> = Mutex::new(RefCell::new(None));
2727

28-
#[allow(unused)] // TODO: Remove attribute when interrupt latency test re-enabled
28+
#[unsafe(no_mangle)]
29+
static mut LAST_PERF: u32 = 0;
30+
31+
#[unsafe(no_mangle)]
32+
static mut SW_TRIGGER_ADDR: *mut u32 = core::ptr::null_mut();
33+
2934
struct Context {
3035
sw0_trigger_addr: u32,
3136
}
3237

38+
// We need to place this close to the trap handler for the jump to be resolved properly
39+
#[unsafe(link_section = ".trap.rust")]
3340
#[unsafe(no_mangle)]
34-
fn interrupt20() {
35-
unsafe { asm!("csrrwi x0, 0x7e1, 0 #disable timer") }
36-
critical_section::with(|cs| {
37-
SWINT0.borrow_ref(cs).as_ref().unwrap().reset();
38-
});
39-
40-
let mut perf_counter: u32 = 0;
41-
unsafe {
42-
asm!(
43-
"
44-
csrr {x}, 0x7e2
45-
",
46-
options(nostack),
47-
x = inout(reg) perf_counter,
48-
)
49-
};
50-
defmt::info!("Performance counter:{}", perf_counter);
51-
// TODO these values should be adjusted to catch smaller regressions
52-
cfg_if::cfg_if! {
53-
if #[cfg(any(feature = "esp32c3", feature = "esp32c2"))] {
54-
assert!(perf_counter < 1100);
55-
} else {
56-
assert!(perf_counter < 750);
57-
}
41+
#[unsafe(naked)]
42+
#[rustfmt::skip]
43+
unsafe extern "C" fn interrupt_handler() {
44+
core::arch::naked_asm! {"
45+
# save affected registers
46+
addi sp, sp, -16*4 # allocate 16 words for saving regs (will work with just 2, but RISC-V wants it to be aligned by 16)
47+
sw t0, 1*4(sp)
48+
sw t1, 2*4(sp)
49+
50+
csrrwi x0, 0x7e1, 0 #disable timer
51+
52+
lw t0, {sw} # t0 <- &SW_TRIGGER_ADDR
53+
sw x0, 0(t0) # clear (deassert) *SW_TRIGGER_ADDR
54+
55+
csrr t1, 0x7e2 # read performance counter
56+
la t0, {x}
57+
sw t1, 0(t0) # store word to LAST_PERF
58+
59+
# restore affected registers
60+
lw t0, 1*4(sp)
61+
lw t1, 2*4(sp)
62+
addi sp, sp, 16*4
63+
mret
64+
",
65+
x = sym LAST_PERF,
66+
sw = sym SW_TRIGGER_ADDR,
5867
}
5968
}
6069

@@ -77,47 +86,66 @@ mod tests {
7786
}
7887

7988
let sw0_trigger_addr = cpu_intr.register_block().cpu_intr_from_cpu(0) as *const _ as u32;
89+
unsafe {
90+
SW_TRIGGER_ADDR = sw0_trigger_addr as *mut u32;
91+
}
8092

8193
critical_section::with(|cs| {
8294
SWINT0
8395
.borrow_ref_mut(cs)
8496
.replace(sw_ints.software_interrupt0)
8597
});
98+
8699
interrupt::enable_direct(
87100
Interrupt::FROM_CPU_INTR0,
88101
Priority::Priority3,
89102
CpuInterrupt::Interrupt20,
103+
interrupt_handler,
90104
)
91105
.unwrap();
92106

93107
Context { sw0_trigger_addr }
94108
}
95109

110+
// Handler function (assigned above) reads cycles count from CSR and stores it into `LAST_PERF`
111+
// static, which this test then reads and asserts its value to detect regressions
96112
#[test]
97113
#[rustfmt::skip]
98-
fn interrupt_latency(_ctx: Context) {
99-
// unsafe {
100-
// asm!(
101-
// "
102-
// csrrwi x0, 0x7e0, 1 #what to count, for cycles write 1 for instructions write 2
103-
// csrrwi x0, 0x7e1, 0 #disable counter
104-
// csrrwi x0, 0x7e2, 0 #reset counter
105-
// "
106-
// );
107-
// }
108-
109-
// // interrupt is raised from assembly for max timer granularity.
110-
// unsafe {
111-
// asm!(
112-
// "
113-
// li {bit}, 1 # Flip flag (bit 0)
114-
// csrrwi x0, 0x7e1, 1 # enable timer
115-
// sw {bit}, 0({addr}) # trigger FROM_CPU_INTR0
116-
// ",
117-
// options(nostack),
118-
// addr = in(reg) ctx.sw0_trigger_addr,
119-
// bit = out(reg) _,
120-
// )
121-
// }
114+
fn interrupt_latency(ctx: Context) {
115+
unsafe {
116+
asm!(
117+
"
118+
csrrwi x0, 0x7e0, 1 #what to count, for cycles write 1 for instructions write 2
119+
csrrwi x0, 0x7e1, 0 #disable counter
120+
csrrwi x0, 0x7e2, 0 #reset counter
121+
"
122+
);
123+
}
124+
125+
// interrupt is raised from assembly for max timer granularity.
126+
unsafe {
127+
asm!(
128+
"
129+
li {bit}, 1 # Flip flag (bit 0)
130+
csrrwi x0, 0x7e1, 1 # enable timer
131+
sw {bit}, 0({addr}) # trigger FROM_CPU_INTR0
132+
",
133+
options(nostack),
134+
addr = in(reg) ctx.sw0_trigger_addr,
135+
bit = out(reg) _,
136+
)
137+
}
138+
139+
let perf_counter = unsafe { LAST_PERF };
140+
defmt::info!("Performance counter: {}", perf_counter);
141+
142+
// TODO c3/c2 values should be adjusted to catch smaller regressions
143+
cfg_if::cfg_if! {
144+
if #[cfg(any(feature = "esp32c3", feature = "esp32c2"))] {
145+
assert!(perf_counter < 400);
146+
} else {
147+
assert!(perf_counter < 155);
148+
}
149+
}
122150
}
123151
}

qa-test/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ esp-backtrace = { path = "../esp-backtrace", features = [
2121
"panic-handler",
2222
"println",
2323
] }
24+
critical-section = "1.1.3"
2425
esp-bootloader-esp-idf = { path = "../esp-bootloader-esp-idf" }
2526
esp-hal = { path = "../esp-hal", features = ["log-04", "unstable"] }
2627
esp-hal-embassy = { path = "../esp-hal-embassy" }

0 commit comments

Comments
 (0)