pub unsafe extern "C" fn _start_trap()
Expand description
This is the trap handler function. This code is called on all traps, including interrupts, exceptions, and system calls from applications.
Tock uses only the single trap handler, and does not use any vectored interrupts or other exception handling. The trap handler has to determine why the trap handler was called, and respond accordingly. Generally, there are two reasons the trap handler gets called: an interrupt occurred or an application called a syscall.
In the case of an interrupt while the kernel was executing we only need
to save the kernel registers and then run whatever interrupt handling
code we need to. If the trap happens while an application was executing,
we have to save the application state and then resume the switch_to()
function to correctly return back to the kernel.
We implement this distinction through a branch on the value of the
mscratch
CSR. If, at the time the trap was taken, it contains 0
, we
assume that the hart is currently executing kernel code.
If it contains any other value, we interpret it to be a memory address pointing to a particular data structure:
mscratch 0 1 2 3
\->|--------------------------------------------------------------|
| scratch word, overwritten with s1 register contents |
|--------------------------------------------------------------|
| trap handler address, continue trap handler execution here |
|--------------------------------------------------------------|
Thus, process implementations can define their own strategy for how traps should be handled when they occur during process execution. This global trap handler behavior is well defined. It will:
-
atomically swap s0 and the mscratch CSR,
-
execute the default kernel trap handler if s0 now contains
0
(meaning that the mscratch CSR contained0
before entering this trap handler), -
otherwise, save s1 to
0*4(s0)
, and finally -
load the address at
1*4(s0)
into s1, and jump to it.
No registers other than s0, s1 and the mscratch CSR are to be clobbered before continuing execution at the address loaded into the mscratch CSR or the _start_kernel_trap kernel trap handler. Execution with these second-stage trap handlers must continue in the same trap handler context as originally invoked by the trap (e.g., the global trap handler will not execute an mret instruction). It will not modify CSRs that contain information on the trap source or the system state prior to entering the trap handler.
We deliberately clobber callee-saved instead of caller-saved registers,
as this makes it easier to call other functions as part of the trap
handler (for example to to disable interrupts from within Rust
code). This global trap handler saves the previous values of these
clobbered registers ensuring that they can be restored later. It places
new values into these clobbered registers (such as the previous s0
register contents) that are required to be retained for correctly
returning from the trap handler, and as such need to be saved across
C-ABI function calls. Loading them into saved registers avoids the need
to manually save them across such calls.
When a custom trap handler stack is registered in mscratch
, the custom
handler is responsible for restoring the kernel trap handler (by setting
mscratch=0) before returning to kernel execution from the trap handler
context.
If a board or chip must, for whichever reason, use a different global trap handler, it should abide to the above contract and emulate its behavior for all traps and interrupts that are required to be handled by the respective kernel or other trap handler as registered in mscratch.
For instance, a chip that does not support non-vectored trap handlers can register a vectored trap handler that routes each trap source to this global trap handler.
Alternatively, a board can be allowed to ignore certain traps or interrupts, some or all of the time, provided they are not vital to Tock’s execution. These boards may choose to register an alternative handler for some or all trap sources. When this alternative handler is invoked, it may, for instance, choose to ignore a certain trap, access global state (subject to synchronization), etc. It must still abide to the contract as stated above.