esp32_c3/
chip.rs

1// Licensed under the Apache License, Version 2.0 or the MIT License.
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3// Copyright Tock Contributors 2022.
4
5//! High-level setup and interrupt mapping for the chip.
6
7use core::fmt::Write;
8use core::ptr::addr_of;
9
10use kernel::platform::chip::{Chip, InterruptService};
11use kernel::utilities::registers::interfaces::{ReadWriteable, Readable, Writeable};
12use kernel::utilities::StaticRef;
13
14use rv32i::csr::{self, mcause, mtvec::mtvec, CSR};
15use rv32i::pmp::{simple::SimplePMP, PMPUserMPU};
16use rv32i::syscall::SysCall;
17
18use crate::intc::{Intc, IntcRegisters};
19use crate::interrupts;
20use crate::rng;
21use crate::sysreg;
22use crate::timg;
23
24pub const INTC_BASE: StaticRef<IntcRegisters> =
25    unsafe { StaticRef::new(0x600C_2000 as *const IntcRegisters) };
26
27pub static mut INTC: Intc = Intc::new(INTC_BASE);
28
29pub struct Esp32C3<'a, I: InterruptService + 'a> {
30    userspace_kernel_boundary: SysCall,
31    pub pmp: PMPUserMPU<8, SimplePMP<16>>,
32    intc: &'a Intc,
33    pic_interrupt_service: &'a I,
34}
35
36pub struct Esp32C3DefaultPeripherals<'a> {
37    pub uart0: esp32::uart::Uart<'a>,
38    pub timg0: timg::TimG<'a>,
39    pub timg1: timg::TimG<'a>,
40    pub gpio: esp32::gpio::Port<'a>,
41    pub rtc_cntl: esp32::rtc_cntl::RtcCntl,
42    pub sysreg: sysreg::SysReg,
43    pub rng: rng::Rng<'a>,
44}
45
46impl Esp32C3DefaultPeripherals<'_> {
47    pub fn new() -> Self {
48        Self {
49            uart0: esp32::uart::Uart::new(esp32::uart::UART0_BASE),
50            timg0: timg::TimG::new(timg::TIMG0_BASE, timg::ClockSource::Pll),
51            timg1: timg::TimG::new(timg::TIMG1_BASE, timg::ClockSource::Pll),
52            gpio: esp32::gpio::Port::new(),
53            rtc_cntl: esp32::rtc_cntl::RtcCntl::new(esp32::rtc_cntl::RTC_CNTL_BASE),
54            sysreg: sysreg::SysReg::new(),
55            rng: rng::Rng::new(),
56        }
57    }
58
59    pub fn init(&'static self) {
60        kernel::deferred_call::DeferredCallClient::register(&self.rng);
61    }
62}
63
64impl InterruptService for Esp32C3DefaultPeripherals<'_> {
65    unsafe fn service_interrupt(&self, interrupt: u32) -> bool {
66        match interrupt {
67            interrupts::IRQ_UART0 => self.uart0.handle_interrupt(),
68
69            interrupts::IRQ_TIMER1 => self.timg0.handle_interrupt(),
70            interrupts::IRQ_TIMER2 => self.timg1.handle_interrupt(),
71
72            interrupts::IRQ_GPIO | interrupts::IRQ_GPIO_NMI => self.gpio.handle_interrupt(),
73
74            _ => return false,
75        }
76        true
77    }
78}
79
80impl<'a, I: InterruptService + 'a> Esp32C3<'a, I> {
81    pub unsafe fn new(pic_interrupt_service: &'a I) -> Self {
82        Self {
83            userspace_kernel_boundary: SysCall::new(),
84            pmp: PMPUserMPU::new(SimplePMP::new().unwrap()),
85            intc: &*addr_of!(INTC),
86            pic_interrupt_service,
87        }
88    }
89
90    pub fn map_pic_interrupts(&self) {
91        self.intc.map_interrupts();
92    }
93
94    pub unsafe fn enable_pic_interrupts(&self) {
95        self.intc.enable_all();
96    }
97
98    unsafe fn handle_pic_interrupts(&self) {
99        while let Some(interrupt) = self.intc.get_saved_interrupts() {
100            if !self.pic_interrupt_service.service_interrupt(interrupt) {
101                panic!("Unhandled interrupt {}", interrupt);
102            }
103            self.with_interrupts_disabled(|| {
104                // Safe as interrupts are disabled
105                self.intc.complete(interrupt);
106            });
107        }
108    }
109}
110
111impl<'a, I: InterruptService + 'a> Chip for Esp32C3<'a, I> {
112    type MPU = PMPUserMPU<8, SimplePMP<16>>;
113    type UserspaceKernelBoundary = SysCall;
114    type ThreadIdProvider = rv32i::thread_id::RiscvThreadIdProvider;
115
116    fn service_pending_interrupts(&self) {
117        loop {
118            if self.intc.get_saved_interrupts().is_some() {
119                unsafe {
120                    self.handle_pic_interrupts();
121                }
122            }
123
124            if self.intc.get_saved_interrupts().is_none() {
125                break;
126            }
127        }
128
129        self.intc.enable_all();
130    }
131
132    fn has_pending_interrupts(&self) -> bool {
133        self.intc.get_saved_interrupts().is_some()
134    }
135
136    fn mpu(&self) -> &Self::MPU {
137        &self.pmp
138    }
139
140    fn userspace_kernel_boundary(&self) -> &SysCall {
141        &self.userspace_kernel_boundary
142    }
143
144    fn sleep(&self) {
145        unsafe {
146            rv32i::support::wfi();
147        }
148    }
149
150    unsafe fn with_interrupts_disabled<F, R>(&self, f: F) -> R
151    where
152        F: FnOnce() -> R,
153    {
154        rv32i::support::with_interrupts_disabled(f)
155    }
156
157    unsafe fn print_state(&self, writer: &mut dyn Write) {
158        let mcval: csr::mcause::Trap = core::convert::From::from(csr::CSR.mcause.extract());
159        let _ = writer.write_fmt(format_args!("\r\n---| RISC-V Machine State |---\r\n"));
160        let _ = writer.write_fmt(format_args!("Last cause (mcause): "));
161        rv32i::print_mcause(mcval, writer);
162        let interrupt = csr::CSR.mcause.read(csr::mcause::mcause::is_interrupt);
163        let code = csr::CSR.mcause.read(csr::mcause::mcause::reason);
164        let _ = writer.write_fmt(format_args!(
165            " (interrupt={}, exception code={:#010X})",
166            interrupt, code
167        ));
168        let _ = writer.write_fmt(format_args!(
169            "\r\nLast value (mtval):  {:#010X}\
170         \r\n\
171         \r\nSystem register dump:\
172         \r\n mepc:    {:#010X}    mstatus:     {:#010X}\
173         \r\n mtvec:   {:#010X}",
174            csr::CSR.mtval.get(),
175            csr::CSR.mepc.get(),
176            csr::CSR.mstatus.get(),
177            csr::CSR.mtvec.get()
178        ));
179        let mstatus = csr::CSR.mstatus.extract();
180        let uie = mstatus.is_set(csr::mstatus::mstatus::uie);
181        let sie = mstatus.is_set(csr::mstatus::mstatus::sie);
182        let mie = mstatus.is_set(csr::mstatus::mstatus::mie);
183        let upie = mstatus.is_set(csr::mstatus::mstatus::upie);
184        let spie = mstatus.is_set(csr::mstatus::mstatus::spie);
185        let mpie = mstatus.is_set(csr::mstatus::mstatus::mpie);
186        let spp = mstatus.is_set(csr::mstatus::mstatus::spp);
187        let _ = writer.write_fmt(format_args!(
188            "\r\n mstatus: {:#010X}\
189         \r\n  uie:    {:5}  upie:   {}\
190         \r\n  sie:    {:5}  spie:   {}\
191         \r\n  mie:    {:5}  mpie:   {}\
192         \r\n  spp:    {}",
193            mstatus.get(),
194            uie,
195            upie,
196            sie,
197            spie,
198            mie,
199            mpie,
200            spp
201        ));
202    }
203}
204
205fn handle_exception(exception: mcause::Exception) {
206    match exception {
207        mcause::Exception::UserEnvCall | mcause::Exception::SupervisorEnvCall => (),
208
209        mcause::Exception::InstructionMisaligned
210        | mcause::Exception::InstructionFault
211        | mcause::Exception::IllegalInstruction
212        | mcause::Exception::Breakpoint
213        | mcause::Exception::LoadMisaligned
214        | mcause::Exception::LoadFault
215        | mcause::Exception::StoreMisaligned
216        | mcause::Exception::StoreFault
217        | mcause::Exception::MachineEnvCall
218        | mcause::Exception::InstructionPageFault
219        | mcause::Exception::LoadPageFault
220        | mcause::Exception::StorePageFault
221        | mcause::Exception::Unknown => {
222            panic!("fatal exception: {:?}: {:#x}", exception, CSR.mtval.get());
223        }
224    }
225}
226
227unsafe fn handle_interrupt(_intr: mcause::Interrupt) {
228    CSR.mstatus.modify(csr::mstatus::mstatus::mie::CLEAR);
229
230    // Claim the interrupt, unwrap() as we know an interrupt exists
231    // Once claimed this interrupt won't fire until it's completed
232    // NOTE: The interrupt is no longer pending in the PLIC
233    loop {
234        let interrupt = (*addr_of!(INTC)).next_pending();
235
236        match interrupt {
237            Some(irq) => {
238                // Safe as interrupts are disabled
239                (*addr_of!(INTC)).save_interrupt(irq);
240                (*addr_of!(INTC)).disable(irq);
241            }
242            None => {
243                // Enable generic interrupts
244                CSR.mstatus.modify(csr::mstatus::mstatus::mie::SET);
245                break;
246            }
247        }
248    }
249}
250
251/// Trap handler for board/chip specific code.
252///
253/// This gets called when an interrupt occurs while the chip is
254/// in kernel mode.
255#[export_name = "_start_trap_rust_from_kernel"]
256pub unsafe extern "C" fn start_trap_rust() {
257    match mcause::Trap::from(CSR.mcause.extract()) {
258        mcause::Trap::Interrupt(interrupt) => {
259            handle_interrupt(interrupt);
260        }
261        mcause::Trap::Exception(exception) => {
262            handle_exception(exception);
263        }
264    }
265}
266
267/// Function that gets called if an interrupt occurs while an app was running.
268///
269/// mcause is passed in, and this function should correctly handle disabling the
270/// interrupt that fired so that it does not trigger again.
271#[export_name = "_disable_interrupt_trap_rust_from_app"]
272pub unsafe extern "C" fn disable_interrupt_trap_handler(mcause_val: u32) {
273    match mcause::Trap::from(mcause_val as usize) {
274        mcause::Trap::Interrupt(interrupt) => {
275            handle_interrupt(interrupt);
276        }
277        _ => {
278            panic!("unexpected non-interrupt\n");
279        }
280    }
281}
282
283/// The ESP32C3 should support non-vectored and vectored interrupts, but
284/// vectored interrupts seem more reliable so let's use that.
285pub unsafe fn configure_trap_handler() {
286    CSR.mtvec
287        .write(mtvec::trap_addr.val(_start_trap_vectored as usize >> 2) + mtvec::mode::Vectored)
288}
289
290// Mock implementation for crate tests that does not include the section
291// specifier, as the test will not use our linker script, and the host
292// compilation environment may not allow the section name.
293#[cfg(not(any(doc, all(target_arch = "riscv32", target_os = "none"))))]
294pub extern "C" fn _start_trap_vectored() {
295    use core::hint::unreachable_unchecked;
296    unsafe {
297        unreachable_unchecked();
298    }
299}
300
301#[cfg(any(doc, all(target_arch = "riscv32", target_os = "none")))]
302#[link_section = ".riscv.trap_vectored"]
303#[unsafe(naked)]
304pub extern "C" fn _start_trap_vectored() -> ! {
305    use core::arch::naked_asm;
306    // Below are 32 (non-compressed) jumps to cover the entire possible
307    // range of vectored traps.
308    naked_asm!(
309        "
310        j {start_trap}
311        j {start_trap}
312        j {start_trap}
313        j {start_trap}
314        j {start_trap}
315        j {start_trap}
316        j {start_trap}
317        j {start_trap}
318        j {start_trap}
319        j {start_trap}
320        j {start_trap}
321        j {start_trap}
322        j {start_trap}
323        j {start_trap}
324        j {start_trap}
325        j {start_trap}
326        j {start_trap}
327        j {start_trap}
328        j {start_trap}
329        j {start_trap}
330        j {start_trap}
331        j {start_trap}
332        j {start_trap}
333        j {start_trap}
334        j {start_trap}
335        j {start_trap}
336        j {start_trap}
337        j {start_trap}
338        j {start_trap}
339        j {start_trap}
340        j {start_trap}
341        j {start_trap}
342        ",
343        start_trap = sym rv32i::_start_trap,
344    );
345}
346
347/// Array used to track the "trap handler active" state per hart.
348///
349/// The `riscv` crate requires chip crates to allocate an array to
350/// track whether any given hart is currently in a trap handler. The
351/// array must be zero-initialized.
352#[export_name = "_trap_handler_active"]
353static mut TRAP_HANDLER_ACTIVE: [usize; 1] = [0; 1];