qemu_rv32_virt_chip/
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::debug;
11use kernel::hil::time::Freq10MHz;
12use kernel::platform::chip::{Chip, InterruptService};
13
14use kernel::utilities::registers::interfaces::{ReadWriteable, Readable};
15
16use rv32i::csr::{mcause, mie::mie, mip::mip, CSR};
17
18use crate::plic::PLIC;
19use sifive::plic::Plic;
20
21use crate::interrupts;
22
23use virtio::transports::mmio::VirtIOMMIODevice;
24
25type QemuRv32VirtPMP = rv32i::pmp::PMPUserMPU<
26    5,
27    rv32i::pmp::kernel_protection_mml_epmp::KernelProtectionMMLEPMP<16, 5>,
28>;
29
30pub type QemuRv32VirtClint<'a> = sifive::clint::Clint<'a, Freq10MHz>;
31
32pub struct QemuRv32VirtChip<'a, I: InterruptService + 'a> {
33    userspace_kernel_boundary: rv32i::syscall::SysCall,
34    pmp: QemuRv32VirtPMP,
35    plic: &'a Plic,
36    timer: &'a QemuRv32VirtClint<'a>,
37    plic_interrupt_service: &'a I,
38}
39
40pub struct QemuRv32VirtDefaultPeripherals<'a> {
41    pub uart0: crate::uart::Uart16550<'a>,
42    pub virtio_mmio: [VirtIOMMIODevice; 8],
43}
44
45impl QemuRv32VirtDefaultPeripherals<'_> {
46    pub fn new() -> Self {
47        Self {
48            uart0: crate::uart::Uart16550::new(crate::uart::UART0_BASE),
49            virtio_mmio: [
50                VirtIOMMIODevice::new(crate::virtio_mmio::VIRTIO_MMIO_0_BASE),
51                VirtIOMMIODevice::new(crate::virtio_mmio::VIRTIO_MMIO_1_BASE),
52                VirtIOMMIODevice::new(crate::virtio_mmio::VIRTIO_MMIO_2_BASE),
53                VirtIOMMIODevice::new(crate::virtio_mmio::VIRTIO_MMIO_3_BASE),
54                VirtIOMMIODevice::new(crate::virtio_mmio::VIRTIO_MMIO_4_BASE),
55                VirtIOMMIODevice::new(crate::virtio_mmio::VIRTIO_MMIO_5_BASE),
56                VirtIOMMIODevice::new(crate::virtio_mmio::VIRTIO_MMIO_6_BASE),
57                VirtIOMMIODevice::new(crate::virtio_mmio::VIRTIO_MMIO_7_BASE),
58            ],
59        }
60    }
61}
62
63impl InterruptService for QemuRv32VirtDefaultPeripherals<'_> {
64    unsafe fn service_interrupt(&self, interrupt: u32) -> bool {
65        match interrupt {
66            interrupts::UART0 => self.uart0.handle_interrupt(),
67            interrupts::VIRTIO_MMIO_0 => self.virtio_mmio[0].handle_interrupt(),
68            interrupts::VIRTIO_MMIO_1 => self.virtio_mmio[1].handle_interrupt(),
69            interrupts::VIRTIO_MMIO_2 => self.virtio_mmio[2].handle_interrupt(),
70            interrupts::VIRTIO_MMIO_3 => self.virtio_mmio[3].handle_interrupt(),
71            interrupts::VIRTIO_MMIO_4 => self.virtio_mmio[4].handle_interrupt(),
72            interrupts::VIRTIO_MMIO_5 => self.virtio_mmio[5].handle_interrupt(),
73            interrupts::VIRTIO_MMIO_6 => self.virtio_mmio[6].handle_interrupt(),
74            interrupts::VIRTIO_MMIO_7 => self.virtio_mmio[7].handle_interrupt(),
75            _ => return false,
76        }
77        true
78    }
79}
80
81impl<'a, I: InterruptService + 'a> QemuRv32VirtChip<'a, I> {
82    pub unsafe fn new(
83        plic_interrupt_service: &'a I,
84        timer: &'a QemuRv32VirtClint<'a>,
85        pmp: rv32i::pmp::kernel_protection_mml_epmp::KernelProtectionMMLEPMP<16, 5>,
86    ) -> Self {
87        Self {
88            userspace_kernel_boundary: rv32i::syscall::SysCall::new(),
89            pmp: rv32i::pmp::PMPUserMPU::new(pmp),
90            plic: &*addr_of!(PLIC),
91            timer,
92            plic_interrupt_service,
93        }
94    }
95
96    pub unsafe fn enable_plic_interrupts(&self) {
97        self.plic.disable_all();
98        self.plic.clear_all_pending();
99        self.plic.enable_all();
100    }
101
102    unsafe fn handle_plic_interrupts(&self) {
103        while let Some(interrupt) = self.plic.get_saved_interrupts() {
104            if !self.plic_interrupt_service.service_interrupt(interrupt) {
105                debug!("Pidx {}", interrupt);
106            }
107            self.with_interrupts_disabled(|| {
108                self.plic.complete(interrupt);
109            });
110        }
111    }
112}
113
114impl<'a, I: InterruptService + 'a> Chip for QemuRv32VirtChip<'a, I> {
115    type MPU = QemuRv32VirtPMP;
116    type UserspaceKernelBoundary = rv32i::syscall::SysCall;
117    type ThreadIdProvider = rv32i::thread_id::RiscvThreadIdProvider;
118
119    fn mpu(&self) -> &Self::MPU {
120        &self.pmp
121    }
122
123    fn userspace_kernel_boundary(&self) -> &rv32i::syscall::SysCall {
124        &self.userspace_kernel_boundary
125    }
126
127    fn service_pending_interrupts(&self) {
128        loop {
129            let mip = CSR.mip.extract();
130
131            if mip.is_set(mip::mtimer) {
132                self.timer.handle_interrupt();
133            }
134            if self.plic.get_saved_interrupts().is_some() {
135                unsafe {
136                    self.handle_plic_interrupts();
137                }
138            }
139
140            if !mip.any_matching_bits_set(mip::mtimer::SET)
141                && self.plic.get_saved_interrupts().is_none()
142            {
143                break;
144            }
145        }
146
147        // Re-enable all MIE interrupts that we care about. Since we looped
148        // until we handled them all, we can re-enable all of them.
149        CSR.mie.modify(mie::mext::SET + mie::mtimer::SET);
150    }
151
152    fn has_pending_interrupts(&self) -> bool {
153        // First check if the global machine timer interrupt is set.
154        // We would also need to check for additional global interrupt bits
155        // if there were to be used for anything in the future.
156        if CSR.mip.is_set(mip::mtimer) {
157            return true;
158        }
159
160        // Then we can check the PLIC.
161        self.plic.get_saved_interrupts().is_some()
162    }
163
164    fn sleep(&self) {
165        unsafe {
166            rv32i::support::wfi();
167        }
168    }
169
170    unsafe fn with_interrupts_disabled<F, R>(&self, f: F) -> R
171    where
172        F: FnOnce() -> R,
173    {
174        rv32i::support::with_interrupts_disabled(f)
175    }
176
177    unsafe fn print_state(&self, writer: &mut dyn Write) {
178        rv32i::print_riscv_state(writer);
179        let _ = writer.write_fmt(format_args!("{}", self.pmp.pmp));
180    }
181}
182
183fn handle_exception(exception: mcause::Exception) {
184    match exception {
185        mcause::Exception::UserEnvCall | mcause::Exception::SupervisorEnvCall => (),
186
187        mcause::Exception::InstructionMisaligned
188        | mcause::Exception::InstructionFault
189        | mcause::Exception::IllegalInstruction
190        | mcause::Exception::Breakpoint
191        | mcause::Exception::LoadMisaligned
192        | mcause::Exception::LoadFault
193        | mcause::Exception::StoreMisaligned
194        | mcause::Exception::StoreFault
195        | mcause::Exception::MachineEnvCall
196        | mcause::Exception::InstructionPageFault
197        | mcause::Exception::LoadPageFault
198        | mcause::Exception::StorePageFault
199        | mcause::Exception::Unknown => {
200            panic!("fatal exception");
201        }
202    }
203}
204
205unsafe fn handle_interrupt(intr: mcause::Interrupt) {
206    match intr {
207        mcause::Interrupt::UserSoft
208        | mcause::Interrupt::UserTimer
209        | mcause::Interrupt::UserExternal => {
210            panic!("unexpected user-mode interrupt");
211        }
212        mcause::Interrupt::SupervisorExternal
213        | mcause::Interrupt::SupervisorTimer
214        | mcause::Interrupt::SupervisorSoft => {
215            panic!("unexpected supervisor-mode interrupt");
216        }
217
218        mcause::Interrupt::MachineSoft => {
219            CSR.mie.modify(mie::msoft::CLEAR);
220        }
221        mcause::Interrupt::MachineTimer => {
222            CSR.mie.modify(mie::mtimer::CLEAR);
223        }
224        mcause::Interrupt::MachineExternal => {
225            // We received an interrupt, disable interrupts while we handle them
226            CSR.mie.modify(mie::mext::CLEAR);
227
228            // Claim the interrupt, unwrap() as we know an interrupt exists
229            // Once claimed this interrupt won't fire until it's completed
230            // NOTE: The interrupt is no longer pending in the PLIC
231            loop {
232                let interrupt = (*addr_of!(PLIC)).next_pending();
233
234                match interrupt {
235                    Some(irq) => {
236                        // Safe as interrupts are disabled
237                        (*addr_of!(PLIC)).save_interrupt(irq);
238                    }
239                    None => {
240                        // Enable generic interrupts
241                        CSR.mie.modify(mie::mext::SET);
242
243                        break;
244                    }
245                }
246            }
247        }
248
249        mcause::Interrupt::Unknown(_) => {
250            panic!("interrupt of unknown cause");
251        }
252    }
253}
254
255/// Trap handler for board/chip specific code.
256///
257/// For the qemu-system-riscv32 virt machine this gets called when an
258/// interrupt occurs while the chip is in kernel mode.
259#[export_name = "_start_trap_rust_from_kernel"]
260pub unsafe extern "C" fn start_trap_rust() {
261    match mcause::Trap::from(CSR.mcause.extract()) {
262        mcause::Trap::Interrupt(interrupt) => {
263            handle_interrupt(interrupt);
264        }
265        mcause::Trap::Exception(exception) => {
266            handle_exception(exception);
267        }
268    }
269}
270
271/// Function that gets called if an interrupt occurs while an app was running.
272///
273/// mcause is passed in, and this function should correctly handle disabling the
274/// interrupt that fired so that it does not trigger again.
275#[export_name = "_disable_interrupt_trap_rust_from_app"]
276pub unsafe extern "C" fn disable_interrupt_trap_handler(mcause_val: u32) {
277    match mcause::Trap::from(mcause_val as usize) {
278        mcause::Trap::Interrupt(interrupt) => {
279            handle_interrupt(interrupt);
280        }
281        _ => {
282            panic!("unexpected non-interrupt\n");
283        }
284    }
285}
286
287/// Array used to track the "trap handler active" state per hart.
288///
289/// The `riscv` crate requires chip crates to allocate an array to
290/// track whether any given hart is currently in a trap handler. The
291/// array must be zero-initialized.
292///
293/// While the QEMU rv32 virt target supports multiple harts, Tock
294/// currently always runs on the first hart, with ID zero. Hence, we
295/// allocate an array of `usizes` with length one for this purpose,
296/// intialized to zero:
297#[export_name = "_trap_handler_active"]
298static mut TRAP_HANDLER_ACTIVE: [usize; 1] = [0; 1];