[][src]Macro sunrise_kernel::trap_gate_asm

macro_rules! trap_gate_asm {
    (has_errorcode: true) => { ... };
    (has_errorcode: false) => { ... };
}

The exception/syscall handler asm wrapper.

When the cpu handles a Trap/Interrupt gate, it:

  1. decides if it must switch stacks. If it does, we configured it to switch to the current thread's kernel stack. It will then push userspace ss and esp.
  2. pushes eflags, cs, eip.
  3. optionally pushes an errorcode, depending on the exception.

This is just enough for the cpu to restore the context on iret and optionally switch back to the userspace stack.

On those cpu-pushed registers, we push the rest of the hardware context, so we can restore it at the end of isr. By doing so, we're constructing a UserspaceHardwareContext on the stack, but whose eflags, cs and eip fields point to words that were actually pushed by cpu.

We then call the isr, passing it a pointer to this structure. The isr is free to modify the backed-up registers, including the cpu-pushed ones, and those modification will be popped into the registers at the end of the isr, effectively changing context after we iret.

Diagram

     Privilege changed            Privilege unchanged
    (e.g. int, syscall)           (e.g. kernel fault,
                                   int during kernel)

        Page fault
    +----------------+            +----------------+
    |       SS       |            |                |
    +----------------+            +----------------+
    |      ESP       |            |                |
    +----------------+            +----------------+
    |     EFLAGS     | <+      +> |     EFLAGS     | <-+ <-+
    +----------------+  |      |  +----------------+   |   |
    |       CS       |  |      |  |       CS       |   |   |
    +----------------+  |      |  +----------------+   |   | Registers pushed by CPU
    |      EIP       |  |      |  |      EIP       |   |   |
    +----------------+  |      |  +----------------+   |   |
    |   Error code   |  |      |  |   Error code   | <-+   |
    +****************+  |      |  +****************+       |
    |   Pushed eax   |  |      |  |   Pushed eax   |       |
    +----------------+  |      |  +----------------+       |
    |   Pushed ebx   |  |      |  |   Pushed ebx   |       |
    +----------------+  |      |  +----------------+       | struct UserspaceHardwareContext
    |   Pushed ecx   |  |      |  |   Pushed ecx   |       |      passed as an argument
    +----------------+  |      |  +----------------+       |
    |   Pushed edx   |  |      |  |   Pushed edx   |       |
    +----------------+  |      |  +----------------+       |
    |   Pushed esi   |  |      |  |   Pushed esi   |       |
    +----------------+  |      |  +----------------+       |
    |   Pushed edi   |  |      |  |   Pushed edi   |       |
    +----------------+  |      |  +----------------+       |
    |   Pushed ebp   |  |      |  |   Pushed ebp   |       |
    +----------------+  |      |  +----------------+       |
    |   Pushed gs    |  |      |  |   Pushed gs    |       |
    +----------------+  |      |  +----------------+       |
    | Pushed esp cpy |  |      |  | Pushed esp cpy |     <-+
    +----------------+  |      |  +----------------+
    | Pushed arg ptr | -+      +- | Pushed arg ptr |
    +----------------+            +----------------+
ESP

The only register that can't be modified by the isr is the esp register.

Because this register is only pushed by the cpu when Privilege changed, we must take extra precautions when reading/writting it from the stack, if we don't want to page fault.

When reading it we use the pushed cs to determine if we did change privilege, in which case we proceed to read it, otherwise we can assume we're running on the same stack, and deduce it from our current esp value.

If the isr modifies esp and we're in the Privilege Unchanged situation, there is no way for us to make the cpu use this esp after we iret, that is make the change effective. For this reason we never bother to copy the esp from the UserspaceHardwareContext back to the stack.

Usage

This macro is intended to be inserted in an llvm_asm!() block, like this:

extern "C" fn my_isr_function(userspace_context: &mut UserspaceHardwareContext) {
    // irq handling here
}

unsafe {
    llvm_asm!(trap_gate_asm!(has_errorcode: false)
        :: "i"(my_isr_function as *const u8) :: "volatile", "intel");
}

Because llvm_asm!() expects a literal, trap_gate_asm needs to be macro.

Error code

Some exceptions push an additional errcode on the stack and some don't.

When one is pushed by the cpu, the isr is still expected to pop it before calling iret.

Because we want to handle both cases in a similar way, for exceptions that are errorcode-less we push a fake error code on the stack as if the cpu did it, and handle everything else in one code path.

When returning from the exception, the isr will unconditionally pop the errcode, with no regards for whether it was real or not, and call iret.