rv32i/
lib.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//! Support for the 32-bit RISC-V architecture.
6
7#![crate_name = "rv32i"]
8#![crate_type = "rlib"]
9#![no_std]
10
11use core::fmt::Write;
12
13use kernel::utilities::registers::interfaces::{Readable, Writeable};
14
15pub mod clic;
16pub mod machine_timer;
17pub mod pmp;
18pub mod support;
19pub mod syscall;
20
21// Re-export the shared CSR library so that dependent crates do not have to have
22// both rv32i and riscv as dependencies.
23pub use riscv::csr;
24
25extern "C" {
26    // Where the end of the stack region is (and hence where the stack should
27    // start), and the start of the stack region.
28    static _estack: usize;
29    static _sstack: usize;
30
31    // Boundaries of the .bss section.
32    static mut _szero: usize;
33    static mut _ezero: usize;
34
35    // Where the .data section is stored in flash.
36    static mut _etext: usize;
37
38    // Boundaries of the .data section.
39    static mut _srelocate: usize;
40    static mut _erelocate: usize;
41
42    // The global pointer, value set in the linker script
43    #[link_name = "__global_pointer$"]
44    static __global_pointer: usize;
45}
46
47#[cfg(any(doc, all(target_arch = "riscv32", target_os = "none")))]
48extern "C" {
49    // Entry point of all programs (`_start`).
50    ///
51    /// This assembly does three functions:
52    ///
53    /// 1. It initializes the stack pointer, the frame pointer (needed for closures
54    ///    to work in start_rust) and the global pointer.
55    /// 2. It initializes the .bss and .data RAM segments. This must be done before
56    ///    any Rust code runs. See <https://github.com/tock/tock/issues/2222> for more
57    ///    information.
58    /// 3. Finally it calls `main()`, the main entry point for Tock boards.
59    pub fn _start();
60}
61
62#[cfg(any(doc, all(target_arch = "riscv32", target_os = "none")))]
63core::arch::global_asm!("
64            .section .riscv.start, \"ax\"
65            .globl _start
66          _start:
67
68            // Set the global pointer register using the variable defined in the
69            // linker script. This register is only set once. The global pointer
70            // is a method for sharing state between the linker and the CPU so
71            // that the linker can emit code with offsets that are relative to
72            // the gp register, and the CPU can successfully execute them.
73            //
74            // https://gnu-mcu-eclipse.github.io/arch/riscv/programmer/#the-gp-global-pointer-register
75            // https://groups.google.com/a/groups.riscv.org/forum/#!msg/sw-dev/60IdaZj27dY/5MydPLnHAQAJ
76            // https://www.sifive.com/blog/2017/08/28/all-aboard-part-3-linker-relaxation-in-riscv-toolchain/
77            //
78            // Disable linker relaxation for code that sets up GP so that this doesn't
79            // get turned into `mv gp, gp`.
80            .option push
81            .option norelax
82
83            la gp, {gp}                 // Set the global pointer from linker script.
84
85            // Re-enable linker relaxations.
86            .option pop
87
88            // Initialize the stack pointer register. This comes directly from
89            // the linker script.
90            la sp, {estack}             // Set the initial stack pointer.
91
92            // Set s0 (the frame pointer) to the start of the stack.
93            add  s0, sp, zero           // s0 = sp
94
95            // Initialize mscratch to 0 so that we know that we are currently
96            // in the kernel. This is used for the check in the trap handler.
97            csrw 0x340, zero            // CSR=0x340=mscratch
98
99            // INITIALIZE MEMORY
100
101            // Start by initializing .bss memory. The Tock linker script defines
102            // `_szero` and `_ezero` to mark the .bss segment.
103            la a0, {sbss}               // a0 = first address of .bss
104            la a1, {ebss}               // a1 = first address after .bss
105
106          100: // bss_init_loop
107            beq  a0, a1, 101f           // If a0 == a1, we are done.
108            sw   zero, 0(a0)            // *a0 = 0. Write 0 to the memory location in a0.
109            addi a0, a0, 4              // a0 = a0 + 4. Increment pointer to next word.
110            j 100b                      // Continue the loop.
111
112          101: // bss_init_done
113
114
115            // Now initialize .data memory. This involves coping the values right at the
116            // end of the .text section (in flash) into the .data section (in RAM).
117            la a0, {sdata}              // a0 = first address of data section in RAM
118            la a1, {edata}              // a1 = first address after data section in RAM
119            la a2, {etext}              // a2 = address of stored data initial values
120
121          200: // data_init_loop
122            beq  a0, a1, 201f           // If we have reached the end of the .data
123                                        // section then we are done.
124            lw   a3, 0(a2)              // a3 = *a2. Load value from initial values into a3.
125            sw   a3, 0(a0)              // *a0 = a3. Store initial value into
126                                        // next place in .data.
127            addi a0, a0, 4              // a0 = a0 + 4. Increment to next word in memory.
128            addi a2, a2, 4              // a2 = a2 + 4. Increment to next word in flash.
129            j 200b                      // Continue the loop.
130
131          201: // data_init_done
132
133            // With that initial setup out of the way, we now branch to the main
134            // code, likely defined in a board's main.rs.
135            j main
136        ",
137gp = sym __global_pointer,
138estack = sym _estack,
139sbss = sym _szero,
140ebss = sym _ezero,
141sdata = sym _srelocate,
142edata = sym _erelocate,
143etext = sym _etext,
144);
145
146/// The various privilege levels in RISC-V.
147pub enum PermissionMode {
148    User = 0x0,
149    Supervisor = 0x1,
150    Reserved = 0x2,
151    Machine = 0x3,
152}
153
154/// Tell the MCU what address the trap handler is located at, and initialize
155/// `mscratch` to zero, indicating kernel execution.
156///
157/// This is a generic implementation. There may be board specific versions as
158/// some platforms have added more bits to the `mtvec` register.
159///
160/// The trap handler is called on exceptions and for interrupts.
161pub unsafe fn configure_trap_handler() {
162    // Indicate to the trap handler that we are executing kernel code.
163    csr::CSR.mscratch.set(0);
164
165    // Set the machine-mode trap handler. By not configuing an S-mode or U-mode
166    // trap handler, this should ensure that all traps are handled by the M-mode
167    // handler.
168    csr::CSR.mtvec.write(
169        csr::mtvec::mtvec::trap_addr.val(_start_trap as usize >> 2)
170            + csr::mtvec::mtvec::mode::CLEAR,
171    );
172}
173
174// Mock implementation for tests on Travis-CI.
175#[cfg(not(any(doc, all(target_arch = "riscv32", target_os = "none"))))]
176pub extern "C" fn _start_trap() {
177    unimplemented!()
178}
179
180#[cfg(any(doc, all(target_arch = "riscv32", target_os = "none")))]
181extern "C" {
182    /// This is the trap handler function. This code is called on all traps,
183    /// including interrupts, exceptions, and system calls from applications.
184    ///
185    /// Tock uses only the single trap handler, and does not use any vectored
186    /// interrupts or other exception handling. The trap handler has to
187    /// determine why the trap handler was called, and respond
188    /// accordingly. Generally, there are two reasons the trap handler gets
189    /// called: an interrupt occurred or an application called a syscall.
190    ///
191    /// In the case of an interrupt while the kernel was executing we only need
192    /// to save the kernel registers and then run whatever interrupt handling
193    /// code we need to. If the trap happens while an application was executing,
194    /// we have to save the application state and then resume the `switch_to()`
195    /// function to correctly return back to the kernel.
196    ///
197    /// We implement this distinction through a branch on the value of the
198    /// `mscratch` CSR. If, at the time the trap was taken, it contains `0`, we
199    /// assume that the hart is currently executing kernel code.
200    ///
201    /// If it contains any other value, we interpret it to be a memory address
202    /// pointing to a particular data structure:
203    ///
204    /// ```text
205    /// mscratch           0               1               2               3
206    ///  \->|--------------------------------------------------------------|
207    ///     | scratch word, overwritten with s1 register contents          |
208    ///     |--------------------------------------------------------------|
209    ///     | trap handler address, continue trap handler execution here   |
210    ///     |--------------------------------------------------------------|
211    /// ```
212    ///
213    /// Thus, process implementations can define their own strategy for how
214    /// traps should be handled when they occur during process execution. This
215    /// global trap handler behavior is well defined. It will:
216    ///
217    /// 1. atomically swap s0 and the mscratch CSR,
218    ///
219    /// 2. execute the default kernel trap handler if s0 now contains `0`
220    /// (meaning that the mscratch CSR contained `0` before entering this trap
221    /// handler),
222    ///
223    /// 3. otherwise, save s1 to `0*4(s0)`, and finally
224    ///
225    /// 4. load the address at `1*4(s0)` into s1, and jump to it.
226    ///
227    /// No registers other than s0, s1 and the mscratch CSR are to be clobbered
228    /// before continuing execution at the address loaded into the mscratch CSR
229    /// or the _start_kernel_trap kernel trap handler.  Execution with these
230    /// second-stage trap handlers must continue in the same trap handler
231    /// context as originally invoked by the trap (e.g., the global trap handler
232    /// will not execute an mret instruction). It will not modify CSRs that
233    /// contain information on the trap source or the system state prior to
234    /// entering the trap handler.
235    ///
236    /// We deliberately clobber callee-saved instead of caller-saved registers,
237    /// as this makes it easier to call other functions as part of the trap
238    /// handler (for example to to disable interrupts from within Rust
239    /// code). This global trap handler saves the previous values of these
240    /// clobbered registers ensuring that they can be restored later.  It places
241    /// new values into these clobbered registers (such as the previous `s0`
242    /// register contents) that are required to be retained for correctly
243    /// returning from the trap handler, and as such need to be saved across
244    /// C-ABI function calls. Loading them into saved registers avoids the need
245    /// to manually save them across such calls.
246    ///
247    /// When a custom trap handler stack is registered in `mscratch`, the custom
248    /// handler is responsible for restoring the kernel trap handler (by setting
249    /// mscratch=0) before returning to kernel execution from the trap handler
250    /// context.
251    ///
252    /// If a board or chip must, for whichever reason, use a different global
253    /// trap handler, it should abide to the above contract and emulate its
254    /// behavior for all traps and interrupts that are required to be handled by
255    /// the respective kernel or other trap handler as registered in mscratch.
256    ///
257    /// For instance, a chip that does not support non-vectored trap handlers
258    /// can register a vectored trap handler that routes each trap source to
259    /// this global trap handler.
260    ///
261    /// Alternatively, a board can be allowed to ignore certain traps or
262    /// interrupts, some or all of the time, provided they are not vital to
263    /// Tock's execution. These boards may choose to register an alternative
264    /// handler for some or all trap sources. When this alternative handler is
265    /// invoked, it may, for instance, choose to ignore a certain trap, access
266    /// global state (subject to synchronization), etc. It must still abide to
267    /// the contract as stated above.
268    pub fn _start_trap();
269}
270
271#[cfg(any(doc, all(target_arch = "riscv32", target_os = "none")))]
272core::arch::global_asm!(
273    "
274            .section .riscv.trap, \"ax\"
275            .globl _start_trap
276          _start_trap:
277            // This is the global trap handler. By default, Tock expects this
278            // trap handler to be registered at all times, and that all traps
279            // and interrupts occurring in all modes of execution (M-, S-, and
280            // U-mode) will cause this trap handler to be executed.
281            //
282            // For documentation of its behavior, and how process
283            // implementations can hook their own trap handler code, see the
284            // comment on the `extern C _start_trap` symbol above.
285
286            // Atomically swap s0 and mscratch:
287            csrrw s0, mscratch, s0        // s0 = mscratch; mscratch = s0
288
289            // If mscratch contained 0, invoke the kernel trap handler.
290            beq   s0, x0, 100f      // if s0==x0: goto 100
291
292            // Else, save the current value of s1 to `0*4(s0)`, load `1*4(s0)`
293            // into s1 and jump to it (invoking a custom trap handler).
294            sw    s1, 0*4(s0)       // *s0 = s1
295            lw    s1, 1*4(s0)       // s1 = *(s0+4)
296            jr    s1                // goto s1
297
298          100: // _start_kernel_trap
299
300            // The global trap handler has swapped s0 into mscratch. We can thus
301            // freely clobber s0 without losing any information.
302            //
303            // Since we want to use the stack to save kernel registers, we
304            // first need to make sure that the trap wasn't the result of a
305            // stack overflow, in which case we can't use the current stack
306            // pointer. Use s0 as a scratch register:
307
308            // Load the address of the bottom of the stack (`_sstack`) into our
309            // newly freed-up s0 register.
310            la s0, {sstack}                     // s0 = _sstack
311
312            // Compare the kernel stack pointer to the bottom of the stack. If
313            // the stack pointer is above the bottom of the stack, then continue
314            // handling the fault as normal.
315            bgtu sp, s0, 200f                   // branch if sp > s0
316
317            // If we get here, then we did encounter a stack overflow. We are
318            // going to panic at this point, but for that to work we need a
319            // valid stack to run the panic code. We do this by just starting
320            // over with the kernel stack and placing the stack pointer at the
321            // top of the original stack.
322            la sp, {estack}                     // sp = _estack
323
324        200: // _start_kernel_trap_continue
325
326            // Restore s0. We reset mscratch to 0 (kernel trap handler mode)
327            csrrw s0, mscratch, zero    // s0 = mscratch; mscratch = 0
328
329            // Make room for the caller saved registers we need to restore after
330            // running any trap handler code.
331            addi sp, sp, -16*4
332
333            // Save all of the caller saved registers.
334            sw   ra, 0*4(sp)
335            sw   t0, 1*4(sp)
336            sw   t1, 2*4(sp)
337            sw   t2, 3*4(sp)
338            sw   t3, 4*4(sp)
339            sw   t4, 5*4(sp)
340            sw   t5, 6*4(sp)
341            sw   t6, 7*4(sp)
342            sw   a0, 8*4(sp)
343            sw   a1, 9*4(sp)
344            sw   a2, 10*4(sp)
345            sw   a3, 11*4(sp)
346            sw   a4, 12*4(sp)
347            sw   a5, 13*4(sp)
348            sw   a6, 14*4(sp)
349            sw   a7, 15*4(sp)
350
351            // Jump to board-specific trap handler code. Likely this was an
352            // interrupt and we want to disable a particular interrupt, but each
353            // board/chip can customize this as needed.
354            jal ra, _start_trap_rust_from_kernel
355
356            // Restore the registers from the stack.
357            lw   ra, 0*4(sp)
358            lw   t0, 1*4(sp)
359            lw   t1, 2*4(sp)
360            lw   t2, 3*4(sp)
361            lw   t3, 4*4(sp)
362            lw   t4, 5*4(sp)
363            lw   t5, 6*4(sp)
364            lw   t6, 7*4(sp)
365            lw   a0, 8*4(sp)
366            lw   a1, 9*4(sp)
367            lw   a2, 10*4(sp)
368            lw   a3, 11*4(sp)
369            lw   a4, 12*4(sp)
370            lw   a5, 13*4(sp)
371            lw   a6, 14*4(sp)
372            lw   a7, 15*4(sp)
373
374            // Reset the stack pointer.
375            addi sp, sp, 16*4
376
377            // mret returns from the trap handler. The PC is set to what is in
378            // mepc and execution proceeds from there. Since we did not modify
379            // mepc we will return to where the exception occurred.
380            mret
381    ",
382    estack = sym _estack,
383    sstack = sym _sstack,
384);
385
386/// RISC-V semihosting needs three exact instructions in uncompressed form.
387///
388/// See <https://github.com/riscv/riscv-semihosting-spec/blob/main/riscv-semihosting-spec.adoc#11-semihosting-trap-instruction-sequence>
389/// for more details on the three instructions.
390///
391/// In order to work with semihosting we include the assembly here
392/// where we are able to disable compressed instruction support. This
393/// follows the example used in the Linux kernel:
394/// <https://elixir.bootlin.com/linux/v5.12.10/source/arch/riscv/include/asm/jump_label.h#L21>
395/// as suggested by the RISC-V developers:
396/// <https://groups.google.com/a/groups.riscv.org/g/isa-dev/c/XKkYacERM04/m/CdpOcqtRAgAJ>
397#[cfg(any(doc, all(target_arch = "riscv32", target_os = "none")))]
398pub unsafe fn semihost_command(command: usize, arg0: usize, arg1: usize) -> usize {
399    use core::arch::asm;
400    let res;
401    asm!(
402    "
403      .balign 16
404      .option push
405      .option norelax
406      .option norvc
407      slli x0, x0, 0x1f
408      ebreak
409      srai x0, x0, 7
410      .option pop
411      ",
412    in("a0") command,
413    in("a1") arg0,
414    in("a2") arg1,
415    lateout("a0") res,
416    );
417    res
418}
419
420// Mock implementation for tests on Travis-CI.
421#[cfg(not(any(doc, all(target_arch = "riscv32", target_os = "none"))))]
422pub unsafe fn semihost_command(_command: usize, _arg0: usize, _arg1: usize) -> usize {
423    unimplemented!()
424}
425
426/// Print a readable string for an mcause reason.
427pub unsafe fn print_mcause(mcval: csr::mcause::Trap, writer: &mut dyn Write) {
428    match mcval {
429        csr::mcause::Trap::Interrupt(interrupt) => match interrupt {
430            csr::mcause::Interrupt::UserSoft => {
431                let _ = writer.write_fmt(format_args!("User software interrupt"));
432            }
433            csr::mcause::Interrupt::SupervisorSoft => {
434                let _ = writer.write_fmt(format_args!("Supervisor software interrupt"));
435            }
436            csr::mcause::Interrupt::MachineSoft => {
437                let _ = writer.write_fmt(format_args!("Machine software interrupt"));
438            }
439            csr::mcause::Interrupt::UserTimer => {
440                let _ = writer.write_fmt(format_args!("User timer interrupt"));
441            }
442            csr::mcause::Interrupt::SupervisorTimer => {
443                let _ = writer.write_fmt(format_args!("Supervisor timer interrupt"));
444            }
445            csr::mcause::Interrupt::MachineTimer => {
446                let _ = writer.write_fmt(format_args!("Machine timer interrupt"));
447            }
448            csr::mcause::Interrupt::UserExternal => {
449                let _ = writer.write_fmt(format_args!("User external interrupt"));
450            }
451            csr::mcause::Interrupt::SupervisorExternal => {
452                let _ = writer.write_fmt(format_args!("Supervisor external interrupt"));
453            }
454            csr::mcause::Interrupt::MachineExternal => {
455                let _ = writer.write_fmt(format_args!("Machine external interrupt"));
456            }
457            csr::mcause::Interrupt::Unknown(_) => {
458                let _ = writer.write_fmt(format_args!("Reserved/Unknown"));
459            }
460        },
461        csr::mcause::Trap::Exception(exception) => match exception {
462            csr::mcause::Exception::InstructionMisaligned => {
463                let _ = writer.write_fmt(format_args!("Instruction access misaligned"));
464            }
465            csr::mcause::Exception::InstructionFault => {
466                let _ = writer.write_fmt(format_args!("Instruction access fault"));
467            }
468            csr::mcause::Exception::IllegalInstruction => {
469                let _ = writer.write_fmt(format_args!("Illegal instruction"));
470            }
471            csr::mcause::Exception::Breakpoint => {
472                let _ = writer.write_fmt(format_args!("Breakpoint"));
473            }
474            csr::mcause::Exception::LoadMisaligned => {
475                let _ = writer.write_fmt(format_args!("Load address misaligned"));
476            }
477            csr::mcause::Exception::LoadFault => {
478                let _ = writer.write_fmt(format_args!("Load access fault"));
479            }
480            csr::mcause::Exception::StoreMisaligned => {
481                let _ = writer.write_fmt(format_args!("Store/AMO address misaligned"));
482            }
483            csr::mcause::Exception::StoreFault => {
484                let _ = writer.write_fmt(format_args!("Store/AMO access fault"));
485            }
486            csr::mcause::Exception::UserEnvCall => {
487                let _ = writer.write_fmt(format_args!("Environment call from U-mode"));
488            }
489            csr::mcause::Exception::SupervisorEnvCall => {
490                let _ = writer.write_fmt(format_args!("Environment call from S-mode"));
491            }
492            csr::mcause::Exception::MachineEnvCall => {
493                let _ = writer.write_fmt(format_args!("Environment call from M-mode"));
494            }
495            csr::mcause::Exception::InstructionPageFault => {
496                let _ = writer.write_fmt(format_args!("Instruction page fault"));
497            }
498            csr::mcause::Exception::LoadPageFault => {
499                let _ = writer.write_fmt(format_args!("Load page fault"));
500            }
501            csr::mcause::Exception::StorePageFault => {
502                let _ = writer.write_fmt(format_args!("Store/AMO page fault"));
503            }
504            csr::mcause::Exception::Unknown => {
505                let _ = writer.write_fmt(format_args!("Reserved"));
506            }
507        },
508    }
509}
510
511/// Prints out RISCV machine state, including basic system registers
512/// (mcause, mstatus, mtvec, mepc, mtval, interrupt status).
513pub unsafe fn print_riscv_state(writer: &mut dyn Write) {
514    let mcval: csr::mcause::Trap = core::convert::From::from(csr::CSR.mcause.extract());
515    let _ = writer.write_fmt(format_args!("\r\n---| RISC-V Machine State |---\r\n"));
516    let _ = writer.write_fmt(format_args!("Last cause (mcause): "));
517    print_mcause(mcval, writer);
518    let interrupt = csr::CSR.mcause.read(csr::mcause::mcause::is_interrupt);
519    let code = csr::CSR.mcause.read(csr::mcause::mcause::reason);
520    let _ = writer.write_fmt(format_args!(
521        " (interrupt={}, exception code={:#010X})",
522        interrupt, code
523    ));
524    let _ = writer.write_fmt(format_args!(
525        "\r\nLast value (mtval):  {:#010X}\
526         \r\n\
527         \r\nSystem register dump:\
528         \r\n mepc:    {:#010X}    mstatus:     {:#010X}\
529         \r\n mcycle:  {:#010X}    minstret:    {:#010X}\
530         \r\n mtvec:   {:#010X}",
531        csr::CSR.mtval.get(),
532        csr::CSR.mepc.get(),
533        csr::CSR.mstatus.get(),
534        csr::CSR.mcycle.get(),
535        csr::CSR.minstret.get(),
536        csr::CSR.mtvec.get()
537    ));
538    let mstatus = csr::CSR.mstatus.extract();
539    let uie = mstatus.is_set(csr::mstatus::mstatus::uie);
540    let sie = mstatus.is_set(csr::mstatus::mstatus::sie);
541    let mie = mstatus.is_set(csr::mstatus::mstatus::mie);
542    let upie = mstatus.is_set(csr::mstatus::mstatus::upie);
543    let spie = mstatus.is_set(csr::mstatus::mstatus::spie);
544    let mpie = mstatus.is_set(csr::mstatus::mstatus::mpie);
545    let spp = mstatus.is_set(csr::mstatus::mstatus::spp);
546    let _ = writer.write_fmt(format_args!(
547        "\r\n mstatus: {:#010X}\
548         \r\n  uie:    {:5}  upie:   {}\
549         \r\n  sie:    {:5}  spie:   {}\
550         \r\n  mie:    {:5}  mpie:   {}\
551         \r\n  spp:    {}",
552        mstatus.get(),
553        uie,
554        upie,
555        sie,
556        spie,
557        mie,
558        mpie,
559        spp
560    ));
561    let e_usoft = csr::CSR.mie.is_set(csr::mie::mie::usoft);
562    let e_ssoft = csr::CSR.mie.is_set(csr::mie::mie::ssoft);
563    let e_msoft = csr::CSR.mie.is_set(csr::mie::mie::msoft);
564    let e_utimer = csr::CSR.mie.is_set(csr::mie::mie::utimer);
565    let e_stimer = csr::CSR.mie.is_set(csr::mie::mie::stimer);
566    let e_mtimer = csr::CSR.mie.is_set(csr::mie::mie::mtimer);
567    let e_uext = csr::CSR.mie.is_set(csr::mie::mie::uext);
568    let e_sext = csr::CSR.mie.is_set(csr::mie::mie::sext);
569    let e_mext = csr::CSR.mie.is_set(csr::mie::mie::mext);
570
571    let p_usoft = csr::CSR.mip.is_set(csr::mip::mip::usoft);
572    let p_ssoft = csr::CSR.mip.is_set(csr::mip::mip::ssoft);
573    let p_msoft = csr::CSR.mip.is_set(csr::mip::mip::msoft);
574    let p_utimer = csr::CSR.mip.is_set(csr::mip::mip::utimer);
575    let p_stimer = csr::CSR.mip.is_set(csr::mip::mip::stimer);
576    let p_mtimer = csr::CSR.mip.is_set(csr::mip::mip::mtimer);
577    let p_uext = csr::CSR.mip.is_set(csr::mip::mip::uext);
578    let p_sext = csr::CSR.mip.is_set(csr::mip::mip::sext);
579    let p_mext = csr::CSR.mip.is_set(csr::mip::mip::mext);
580    let _ = writer.write_fmt(format_args!(
581        "\r\n mie:   {:#010X}   mip:   {:#010X}\
582         \r\n  usoft:  {:6}              {:6}\
583         \r\n  ssoft:  {:6}              {:6}\
584         \r\n  msoft:  {:6}              {:6}\
585         \r\n  utimer: {:6}              {:6}\
586         \r\n  stimer: {:6}              {:6}\
587         \r\n  mtimer: {:6}              {:6}\
588         \r\n  uext:   {:6}              {:6}\
589         \r\n  sext:   {:6}              {:6}\
590         \r\n  mext:   {:6}              {:6}\r\n",
591        csr::CSR.mie.get(),
592        csr::CSR.mip.get(),
593        e_usoft,
594        p_usoft,
595        e_ssoft,
596        p_ssoft,
597        e_msoft,
598        p_msoft,
599        e_utimer,
600        p_utimer,
601        e_stimer,
602        p_stimer,
603        e_mtimer,
604        p_mtimer,
605        e_uext,
606        p_uext,
607        e_sext,
608        p_sext,
609        e_mext,
610        p_mext
611    ));
612}