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(this: Option<&Self>, writer: &mut dyn Write) {
178        rv32i::print_riscv_state(writer);
179        if let Some(t) = this {
180            let _ = writer.write_fmt(format_args!("{}", t.pmp.pmp));
181        }
182    }
183}
184
185fn handle_exception(exception: mcause::Exception) {
186    match exception {
187        mcause::Exception::UserEnvCall | mcause::Exception::SupervisorEnvCall => (),
188
189        mcause::Exception::InstructionMisaligned
190        | mcause::Exception::InstructionFault
191        | mcause::Exception::IllegalInstruction
192        | mcause::Exception::Breakpoint
193        | mcause::Exception::LoadMisaligned
194        | mcause::Exception::LoadFault
195        | mcause::Exception::StoreMisaligned
196        | mcause::Exception::StoreFault
197        | mcause::Exception::MachineEnvCall
198        | mcause::Exception::InstructionPageFault
199        | mcause::Exception::LoadPageFault
200        | mcause::Exception::StorePageFault
201        | mcause::Exception::Unknown => {
202            panic!("fatal exception");
203        }
204    }
205}
206
207unsafe fn handle_interrupt(intr: mcause::Interrupt) {
208    match intr {
209        mcause::Interrupt::UserSoft
210        | mcause::Interrupt::UserTimer
211        | mcause::Interrupt::UserExternal => {
212            panic!("unexpected user-mode interrupt");
213        }
214        mcause::Interrupt::SupervisorExternal
215        | mcause::Interrupt::SupervisorTimer
216        | mcause::Interrupt::SupervisorSoft => {
217            panic!("unexpected supervisor-mode interrupt");
218        }
219
220        mcause::Interrupt::MachineSoft => {
221            CSR.mie.modify(mie::msoft::CLEAR);
222        }
223        mcause::Interrupt::MachineTimer => {
224            CSR.mie.modify(mie::mtimer::CLEAR);
225        }
226        mcause::Interrupt::MachineExternal => {
227            // We received an interrupt, disable interrupts while we handle them
228            CSR.mie.modify(mie::mext::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!(PLIC)).next_pending();
235
236                match interrupt {
237                    Some(irq) => {
238                        // Safe as interrupts are disabled
239                        (*addr_of!(PLIC)).save_interrupt(irq);
240                    }
241                    None => {
242                        // Enable generic interrupts
243                        CSR.mie.modify(mie::mext::SET);
244
245                        break;
246                    }
247                }
248            }
249        }
250
251        mcause::Interrupt::Unknown(_) => {
252            panic!("interrupt of unknown cause");
253        }
254    }
255}
256
257/// Trap handler for board/chip specific code.
258///
259/// For the qemu-system-riscv32 virt machine this gets called when an
260/// interrupt occurs while the chip is in kernel mode.
261#[export_name = "_start_trap_rust_from_kernel"]
262pub unsafe extern "C" fn start_trap_rust() {
263    match mcause::Trap::from(CSR.mcause.extract()) {
264        mcause::Trap::Interrupt(interrupt) => {
265            handle_interrupt(interrupt);
266        }
267        mcause::Trap::Exception(exception) => {
268            handle_exception(exception);
269        }
270    }
271}
272
273/// Function that gets called if an interrupt occurs while an app was running.
274///
275/// mcause is passed in, and this function should correctly handle disabling the
276/// interrupt that fired so that it does not trigger again.
277#[export_name = "_disable_interrupt_trap_rust_from_app"]
278pub unsafe extern "C" fn disable_interrupt_trap_handler(mcause_val: u32) {
279    match mcause::Trap::from(mcause_val as usize) {
280        mcause::Trap::Interrupt(interrupt) => {
281            handle_interrupt(interrupt);
282        }
283        _ => {
284            panic!("unexpected non-interrupt\n");
285        }
286    }
287}
288
289/// Array used to track the "trap handler active" state per hart.
290///
291/// The `riscv` crate requires chip crates to allocate an array to
292/// track whether any given hart is currently in a trap handler. The
293/// array must be zero-initialized.
294///
295/// While the QEMU rv32 virt target supports multiple harts, Tock
296/// currently always runs on the first hart, with ID zero. Hence, we
297/// allocate an array of `usizes` with length one for this purpose,
298/// intialized to zero:
299#[export_name = "_trap_handler_active"]
300static mut TRAP_HANDLER_ACTIVE: [usize; 1] = [0; 1];