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