rars icon indicating copy to clipboard operation
rars copied to clipboard

Exception not delivered when interrupts disabled

Open mtvec opened this issue 5 years ago • 10 comments

Currently, exceptions are not delivered when interrupts are disabled. I this intentional? It seems not to be in accordance with the RISC-V spec.

mtvec avatar Nov 05 '20 12:11 mtvec

I this intentional? It seems not to be in accordance with the RISC-V spec.

It is intentional, but I may have misread something. Please add a quote from the privileged specification you believe RARS violates. The privileged specification is very complicated for the tiny amount RARS implements (the N extension), and it is easy to miss something.

I don't understand what you expect the behavior to be. Would causing a Illegal instruction exception inside the interrupt handler recursively call itself? Its possible I did miswrite something, but I can't tell from your question what might be wrong. Please add more information on what you would expect RARS to do vs what it does.

If you are concerned about the trap not being delegated to the M level, effectively RARS reporting the error message is the M level doing its job.

My understanding of the specification

The privileged specification seems to use "interrupt handler" and "trap handler" synonymously. I would expect an exception to be delivered to the trap handler, but I can't find any explicit language saying the "exceptions" are handled by the trap handler. And in the privileged specification, setting xIE = 0 is explicitly to ensure atomicity in the "interrupt handler".

With those pieces I conclude that UIE = 0 results in exceptions not being handled at the user level.

3.1.7 Privilege and Global Interrupt-Enable Stack in mstatus register Interrupt-enable bits, MIE, SIE, and UIE, are provided for each privilege mode. These bits are primarily used to guarantee atomicity with respect to interrupt handlers at the current privilege level.

TheThirdOne avatar Nov 06 '20 06:11 TheThirdOne

I'm sorry for the terseness in my original report. I agree the spec is very complicated and I should have been a bit more clear about what I mean.

Section 1.6 of the unprivileged spec defines trap, interrupts, and exceptions as follows:

We use the term exception to refer to an unusual condition occurring at run time associated with an instruction in the current RISC-V hart. We use the term interrupt to refer to an external asynchronous event that may cause a RISC-V hart to experience an unexpected transfer of control. We use the term trap to refer to the transfer of control to a trap handler caused by either an exception or an interrupt.

So the way I see it, trap handlers are used for both interrupts and exceptions (since both are traps) but the various "interrupt enable" bits in ustatus and uie should only mask interrupts, not exceptions. I think it makes sense that exceptions cannot be disabled since they signal an unrecoverable condition while executing an instruction.

This would also mean that exceptions during an trap handler indeed cause the trap handler to be invoked again.

mtvec avatar Nov 06 '20 08:11 mtvec

I am aware of the distinction between "trap", "interrupt" and "exception". However, I believe that "trap handler" is not used distinctly from "interrupt handler". Following that belief, setting UIE = 0 should "guarantee atomicity" and therefore not allow an exception to trap during trap handling.

I think it makes sense that exceptions cannot be disabled since they signal an unrecoverable condition while executing an instruction.

Most exceptions would not be considered "unrecoverable". In particular, it seems quite reasonable to use ecall within a user trap handler (even if delegating "environment calls from U-mode").

In the case where exceptions cause traps even when UIE = 0, it seems like it would be hard to coordinate which exceptions M-mode delegates to U-Mode.

Thanks for pointing out section 1.6, the following paragraph confirms my second point that I did have a source for.

Whether and how these are converted into traps is dependent on the execution environment, though the expectation is that most environments will take a precise trap when an exception is signaled (except for floating-point exceptions, which, in the standard floating-point extensions, do not cause traps).

TheThirdOne avatar Nov 06 '20 09:11 TheThirdOne

Following that belief, setting UIE = 0 should "guarantee atomicity" and therefore not allow an exception to trap during trap handling.

The *IE bit are used to "guarantee atomicity with respect to interrupt handlers". The way I read this, is that they want to prevent interrupts from happening during a trap handler. I think this makes sense because interrupts are asynchronous and hence out of the control of the trap handler. Exceptions, on the other hand, are synchronous and trap handlers can be implemented to prevent them from happening.

Most exceptions would not be considered "unrecoverable". In particular, it seems quite reasonable to use ecall within a user trap handler (even if delegating "environment calls from U-mode").

Looking at table 3.6 in the privileged spec, I would say most exception are unrecoverable (maybe the various environment calls could be considered recoverable). For example, what is the hart supposed to do when executing an illegal instruction during a trap handler? I think the only reasonable thing to do is jump to the trap handler again. If that causes an infinite loop, it's the handler's fault because it should have avoided the illegal instruction in the first place (and I think "illegal instruction" can be replaced here by any of the other exceptions in that table).

In the case where exceptions cause traps even when UIE = 0, it seems like it would be hard to coordinate which exceptions M-mode delegates to U-Mode.

Could you elaborate on this point? Isn't that where medeleg is for?

mtvec avatar Nov 06 '20 10:11 mtvec

For example, what is the hart supposed to do when executing an illegal instruction during a trap handler?

You can handle some "illegal instruction" exceptions by implementing extension the program uses in software rather than hardware.

Could you elaborate on this point? Isn't that where medeleg is for?

The emphasis should be on the word coordinate. I meant the making an API between the kernel and user code for when to change medeleg would have to be at least strange. Linux typically would use an environment call for such things, but you can delegate environment calls and if UIE wouldn't control catching environment call exceptions, you wouldn't have any way to signal to the kernal, you want environment calls to stop being delegated.


In any case, I better understand your position now. The current behavior is intentional, and I believe it to be in accordance with the specification.

I do not wish to argue about whether the my interpretation of the standard is right or wrong any further. If a reputable simulator handles this in an incompatible way, I will certainly change it. A piece of assembly code that shows handling exceptions with UIE = 0 and instructions on how to run it would be ideal.

Baring such evidence or a change to the wording of the specification I plan to keep the current implementation.

TheThirdOne avatar Nov 06 '20 11:11 TheThirdOne

I understand your position and I agree we shouldn't discuss any further about how to interpret the spec. It's unfortunate the whole trap handling scheme is not explained clearly in one place but scattered around the specs.

I've made a small example the delegates all interrupts and exceptions to S-mode and triggers an exception in S-mode while interrupts are disabled. When running this in qemu, the S-mode trap handler is called. I realize this is not exactly the use case in RARS, but qemu doesn't seem to support the N-extension to handle traps in U-mode. However, I believe the behavior of trap handlers in U-mode should be similar to that in S-mode (please correct me if I'm wrong).

This is the example:

    .global _start
_start:
    la t0, mmode_trap
    csrw mtvec, t0

    # Delegate all interrupts and exceptions to s-mode
    li t0, 0xffff
    csrw mideleg, t0
    csrw medeleg, t0

    # Set previous mode to s-mode (for mret)
    csrr t0, mstatus
    li t1, (1 << 11)
    or t0, t0, t1
    csrw mstatus, t0

    # set mepc to smode (for mret)
    la t0, smode
    csrw mepc, t0
    mret

smode:
    # Here we are in s-mode
    la t0, smode_trap
    csrw stvec, t0

    # All interrupts still disabled (MIE=SIE=UIE=0)
    ecall # Or any other instruction causing an exception
    li t0, 0
    j finish

    .align 4
mmode_trap:
    li t0, 1
    j finish

    .align 4
smode_trap:
    li t0, 2

finish:
    j finish

Note that the example loads 0 in t0 when no trap happens, 1 when the M-mode trap handler is called, and 2 when the S-mode trap handler is called.

Compile with

$ riscv64-unknown-elf-gcc -g -ffreestanding -nostdlib -Ttext 0x80000000 -o test.elf test.S

Run with

$ qemu-system-riscv64 -machine virt -bios none -kernel test.elf -nographic -s -S

Then, in another terminal, inspect the execution using GDB:

$ riscv64-unknown-elf-gdb
(gdb) symbol-file test.elf
(gdb) target remote localhost:1234
(gdb) break finish
(gdb) continue
(gdb) info registers t0
t0             0x2      2

mtvec avatar Nov 06 '20 13:11 mtvec

However, I believe the behavior of trap handlers in U-mode should be similar to that in S-mode (please correct me if I'm wrong).

I would expect that to be true.

Your example code demonstrating qemu's behavior is quite convincing. I am inclined to disable user mode exception handling as if medeleg = 0. That would certainly be correct and would still allow the interrupt handling.

I am open to suggestions on how you think RARS should be changed. My primary criteria is that I do not want to degrade the experience for people not intending to use the N extension. For that reason, I don't think I can reasonably delegate any exception a user would want to to see in the log.

TheThirdOne avatar Nov 07 '20 07:11 TheThirdOne

Would it be an option to make medeleg configurable? We use RARS for educational purposes and being able to handle certain exceptions helps us with explaining, for example, system calls using ecall.

mtvec avatar Nov 07 '20 12:11 mtvec

It would be an option to make the value of medeleg a setting. Alternatively it could perhaps be a register you can see in the window, but not access from user code.

My default plan would be to not delegate any exceptions and then leave an medeleg idea as a later feature request.

TheThirdOne avatar Nov 08 '20 06:11 TheThirdOne

My default plan would be to not delegate any exceptions and then leave an medeleg idea as a later feature request.

Not delegating any exceptions would unfortunately break one of our use cases (handling ecall in U-mode).

I've created a PR that implements medeleg-based delegation as well as a simple UI option for enabling it. It currently only allows to either delegate all or no exceptions.

mtvec avatar Nov 08 '20 12:11 mtvec